SQL数据库的锁机制是保证多事务并发操作下数据一致性的重要手段,当多个事务同时操作同一批数据时,锁会协调事务的执行顺序避免数据冲突。但如果事务持有锁的时间过长或者加锁顺序不合理,就可能出现死锁,导致部分事务无法继续执行。掌握锁监控和死锁排查的技巧,是数据库开发和运维人员的必备能力。

数据库锁的基础概念
在了解监控和排查技巧前,需要先明确几个核心的锁相关概念,不同数据库的实现细节略有差异,但核心逻辑基本一致。
常见锁类型
- 共享锁(S锁):事务读取数据时加的锁,多个事务可以同时持有同一资源的共享锁,互不冲突。
- 排他锁(X锁):事务修改数据时加的锁,同一时间只能有一个事务持有资源的排他锁,会阻塞其他事务的共享锁和排他锁请求。
- 意向锁:为了快速判断表中是否有行级锁而存在的表级锁,分为意向共享锁和意向排他锁。
事务隔离级别与锁的关系
不同的事务隔离级别会影响锁的加锁范围和持有时间,常见隔离级别对应的锁行为如下:
| 隔离级别 | 锁相关行为 | |
|---|---|---|
| 读未提交 | 很少加锁,可能出现脏读 | |
| 读已提交 | 读取时加短期共享锁,写入时加排他锁 | |
| 可重复读 | 读取时加范围锁,避免幻读 | |
| 串行化 | 所有操作加表级锁,完全串行执行 |
SQL数据库锁监控方法
不同数据库的锁监控方式有所区别,下面分别介绍MySQL和SQL Server两种常用数据库的监控方法。
MySQL锁监控
MySQL可以通过系统表和信息函数查看当前锁的状态,常用的查询语句如下:
-- 查看当前正在等待锁的事务 SELECT * FROM information_schema.INNODB_LOCK_WAITS; -- 查看当前持有的锁信息 SELECT * FROM information_schema.INNODB_LOCKS; -- 查看当前运行的事务 SELECT * FROM information_schema.INNODB_TRX; -- 查看元数据锁等待情况(MySQL 8.0+) SELECT * FROM performance_schema.metadata_locks WHERE LOCK_STATUS = 'PENDING';
如果需要持续监控锁的状态,可以定期执行上述查询,记录锁的持有时间、等待事务ID等信息,方便后续分析。
SQL Server锁监控
SQL Server提供了动态管理视图来查看锁相关信息,常用查询如下:
-- 查看当前所有锁的信息
SELECT
request_session_id AS 会话ID,
resource_type AS 资源类型,
resource_database_id AS 数据库ID,
request_mode AS 锁模式,
request_status AS 锁状态
FROM sys.dm_tran_locks;
-- 查看阻塞的会话信息
SELECT
blocking_session_id AS 阻塞会话ID,
wait_duration_ms AS 等待时间毫秒,
session_id AS 被阻塞会话ID
FROM sys.dm_os_waiting_tasks
WHERE blocking_session_id IS NOT NULL;
死锁排查完整流程
当数据库出现死锁时,可以按照以下步骤逐步排查定位问题:
第一步:获取死锁日志
不同数据库都有死锁日志记录功能,开启后可以自动记录死锁发生时的相关信息。
MySQL开启死锁日志的方式:
-- 查看死锁日志开关状态 SHOW VARIABLES LIKE 'innodb_print_all_deadlocks'; -- 开启死锁日志(重启后失效,永久开启需要修改配置文件) SET GLOBAL innodb_print_all_deadlocks = ON;
开启后死锁信息会记录到MySQL的错误日志中,可以通过查看错误日志获取死锁发生时的具体事务和SQL语句。
SQL Server可以通过扩展事件或者跟踪功能捕获死锁图,也可以直接查询死锁相关的系统视图:
-- 查看最近的死锁信息
SELECT
xdr.value('(event/@name)[1]', 'varchar(50)') AS 事件名称,
xdr.value('(event/@timestamp)[1]', 'datetime2') AS 发生时间,
xdr.value('(event/data[@name="deadlock_cycle"]/value)[1]', 'nvarchar(max)') AS 死锁详情
FROM
(SELECT CAST(target_data AS XML) AS target_data
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s ON s.address = st.event_session_address
WHERE s.name = 'system_health') AS t
CROSS APPLY target_data.nodes('//RingBufferTarget/event') AS xdr(xdr);
第二步:分析死锁成因
拿到死锁日志后,重点分析以下几个部分:
- 参与死锁的两个或多个事务ID
- 每个事务正在执行的SQL语句
- 每个事务持有的锁和等待的锁资源
- 事务的加锁顺序
常见的死锁成因包括:两个事务以相反的顺序操作同一批数据、事务持有锁的时间过长、查询没有走索引导致锁范围扩大等。
第三步:复现死锁场景
根据日志中的SQL语句和加锁顺序,在测试环境中模拟相同的操作,确认死锁可以稳定复现,避免误判。
比如日志中显示事务1先更新表A的id=1的记录,再更新表A的id=2的记录;事务2先更新表A的id=2的记录,再更新表A的id=1的记录,就可以在测试环境同时执行这两个事务的逻辑复现死锁。
第四步:优化解决死锁
针对不同的死锁成因,可以采取对应的优化措施:
- 如果是加锁顺序不一致导致的,统一所有事务操作数据的顺序
- 如果是事务持有锁时间过长,优化事务中的SQL语句,减少不必要的操作,尽量缩短事务执行时间
- 如果是查询没有走索引导致锁表,给查询条件添加合适的索引,缩小锁的范围
- 如果业务允许,可以降低事务隔离级别,减少锁的持有时间
死锁预防的实用技巧
除了出现死锁后排查,平时也可以通过一些技巧预防死锁的发生:
- 所有事务按照相同的顺序访问数据库资源,避免交叉加锁
- 尽量让事务短小,避免在事务中执行耗时操作比如调用外部接口、处理大量数据
- 给常用的查询条件添加索引,避免全表扫描导致的大范围加锁
- 设置合理的事务超时时间,避免事务长时间等待锁资源
- 定期监控数据库的锁等待情况,提前发现潜在的锁问题
下面是一段设置事务超时时间的示例代码,以MySQL为例:
-- 设置当前会话的锁等待超时时间为5秒,超过则自动回滚事务 SET SESSION innodb_lock_wait_timeout = 5;
掌握上述锁监控和死锁排查技巧,能够帮助大家快速解决SQL数据库运行过程中出现的锁相关问题,保障业务系统的稳定运行。