监控数据库死锁的脚本如何编写

wen 实用脚本 2

从原理到实战的完整指南

目录导读

  1. 死锁的本质与数据库监控必要性
  2. 监控脚本的核心架构设计
  3. 主流数据库的死锁捕获脚本示例(MySQL / SQL Server / Oracle)
  4. 自动告警与日志持久化方案
  5. 常见问题与优化建议(QA 问答形式)
  6. 构建可落地的死锁监控体系

死锁的本质与监控必要性

在并发数据库操作中,死锁是指两个或多个事务互相持有对方需要的资源,导致所有事务无法继续执行,若不及时监控,死锁会引发服务雪崩——用户请求超时、交易失败甚至数据不一致。

监控数据库死锁的脚本如何编写

关键指标

  • 每秒死锁次数(Deadlocks/sec)
  • 死锁涉及的等待类型(如锁等待、页面锁)
  • 死锁受害者事务回滚比例

监控脚本的核心价值

  • 自动捕获死锁发生时的完整 SQL 与锁对象
  • 生成结构化日志,便于事后分析
  • 实时告警,减少人工巡检成本

监控脚本的核心架构设计

一个标准的死锁监控脚本应包含以下模块:

模块名称 功能描述 实现要点
捕获触发器 检测死锁事件并提取现场信息 使用系统视图 / 扩展事件 / 跟踪标志
结构化记录器 将死锁图、SQL文本、时间戳写入日志表 避免递归写入,防产生新锁
告警通知器 通过邮件、钉钉或 Syslog 推送告警 设置告警阈值(如单窗口超过3次)
自动化恢复 可选:基于规则自动终止阻塞会话 需谨慎,仅适用于测试环境

伪代码流程

while true:  
    wait_for_deadlock_event()  
    capture_deadlock_graph()  
    extract_victim_session(), blocked_sessions()  
    store_to_log_table()  
    if alert_rule_triggered():  
        send_notification()  
    sleep(interval)  

主流数据库的死锁捕获脚本示例

1 MySQL / InnoDB

MySQL 的 InnoDB 引擎通过 SHOW ENGINE INNODB STATUS 暴露最近一次死锁信息,但不会主动推送,推荐方案:启用 innodb_print_all_deadlocks=1 参数后,所有死锁被写入错误日志。

脚本示例(Python + MySQL-connector)

import mysql.connector, re, time
conn = mysql.connector.connect(host='your_host', user='monitor', password='secret')
while True:
    cursor = conn.cursor()
    cursor.execute("SHOW ENGINE INNODB STATUS")
    status = cursor.fetchone()[2]
    if "LATEST DETECTED DEADLOCK" in status:
        # 使用正则提取死锁图、SQL语句
        deadlock_block = re.search(r'LATEST DETECTED DEADLOCK(.*?)(?=\-\-\-|\Z)', status, re.DOTALL)
        if deadlock_block:
            log_to_file(deadlock_block.group(0), timestamp=time.time())
    time.sleep(10)

2 SQL Server

SQL Server 提供了系统函数 fn_dblogfn_dump_dblog,但更推荐使用 扩展事件,指定 xml_deadlock_report 事件。

T-SQL 启用扩展事件

CREATE EVENT SESSION [DeadlockMonitor]
ON SERVER
ADD EVENT sqlserver.xml_deadlock_report
ADD TARGET package0.event_file(SET filename = N'C:\DEADLOCKS\deadlock.xel')
WITH (MAX_MEMORY=4096 KB, EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS);
ALTER EVENT SESSION [DeadlockMonitor] ON SERVER STATE = START;
-- 查询死锁 XML 数据:
SELECT event_data.value('(event/data/value)[1]', 'nvarchar(max)') AS deadlock_graph
FROM sys.fn_xe_file_target_read_file('C:\DEADLOCKS\deadlock*.xel', null, null, null);

3 Oracle

Oracle 通过 自动诊断信息库 (AWR)DBA_HIST_ACTIVE_SESS_HISTORY 检测死锁,快速脚本:

-- 查看当前阻塞(可能形成死锁)
SELECT blocking_session, sid, serial#, sql_id, event
FROM v$session
WHERE blocking_session IS NOT NULL;
-- 使用 DBA_HIST 分析历史死锁
SELECT sql_text, count(*) FROM dba_hist_sqltext
WHERE sql_id IN (SELECT sql_id FROM dba_hist_active_sess_history WHERE session_state='WAITING' AND event='enq: TX - row lock contention')
GROUP BY sql_text;

自动告警与日志持久化方案

仅捕获死锁还不够,必须配合告警,推荐开源的 Prometheus + Grafana 或自建脚本发送到企业微信/钉钉机器人。

代码片段(Python 发送到钉钉 Webhook)

import requests
def send_dingtalk_alert(deadlock_info):
    url = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
    data = {
        "msgtype": "text",
        "text": {"content": f"【死锁告警】 {deadlock_info}"}
    }
    requests.post(url, json=data)

告警阈值建议

  • 生产环境:单小时死锁次数 > 3 次 → 通知 DBA
  • 关键交易库:任意一次死锁 → 立即通知

持久化存储

  • 将死锁信息写入专用数据库表 deadlock_log
  • 字段:id, db_name, deadlock_time, victim_session, blocked_sessions, sql_text, xml_graph

常见问题与优化建议(QA 问答形式)

Q1:监控脚本本身会不会引发新的死锁?
A:会,尤其当脚本使用 SELECT ... FOR UPDATE 读取死锁相关信息时,可能持有系统锁。最佳实践:仅使用快照读取(READ UNCOMMITTED 或 NO_LOCK 提示),或从内存结构(如扩展事件)而非表锁获取数据。

Q2:为什么我的脚本捕获不到死锁?
A:常见原因:① MySQL 未启用 innodb_print_all_deadlocks;② SQL Server 扩展事件作用域错误(应使用 SERVER 级别,而非 DATABASE);③ Oracle 查询粒度过大,未使用 DBA_HIST 细分时间窗口。

Q3:死锁自动终止受害事务后,应用怎么处理?
A:应用层应捕获数据库重试异常(如 MySQL 的 1213 错误),实现重试机制,脚本只负责监控,不替代应用层的重试逻辑。

Q4:如何区分死锁和普通阻塞?
A:查询 sys.dm_exec_requests(SQL Server)或 v$session(Oracle)的 blocking_session 字段,若双方互相阻塞才判定为死锁。


构建可落地的死锁监控体系

监控数据库死锁的脚本不是单一工具,而是一套持续运行的机制:

  1. 低侵入:优先使用数据库内置的事件系统,避免增加额外锁
  2. 结构化输出:将死锁图、SQL、时间戳组合成可查询的元数据
  3. 分级告警:根据死锁频率区分“警告”与“严重”级别
  4. 持续迭代:定期分析积累的死锁记录,优化索引与 SQL 执行计划

如果您的域名是 yourcompany.com,建议将告警日志存储在 deadlock.yourcompany.com 子域下,通过内部 API 对接监控平台。

一个成熟的监控脚本可以让您从被动救火转变为主动防御——当死锁发生时,您不再是猜测原因,而是打开脚本生成的报告,精准定位到是哪个应用、哪条 SQL、在争抢哪个索引。

抱歉,评论功能暂时关闭!