锁等待与超时
锁等待是并发事务竞争资源时的阻塞状态,超时机制防止无限等待。
锁等待机制
锁等待过程
SQL
事务A持有锁
↓
事务B请求同一资源锁
↓
锁冲突 → 事务B进入等待队列
↓
等待事务A释放锁
↓
事务A提交 → 事务B获取锁继续执行
或
超时 → 事务B报错退出
锁等待场景
SQL
-- 事务A(先执行)
START TRANSACTION;
UPDATE orders SET amount = 100 WHERE id = 1; -- 持有X锁
-- 事务B(等待)
START TRANSACTION;
UPDATE orders SET amount = 200 WHERE id = 1; -- 等待X锁
-- 阻塞中...
-- 事务A提交
COMMIT;
-- 事务B获取锁继续执行
锁等待超时参数
innodb_lock_wait_timeout
SQL
-- 查看超时设置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 默认50秒
-- 设置超时时间
SET GLOBAL innodb_lock_wait_timeout = 30; -- 全局
SET SESSION innodb_lock_wait_timeout = 10; -- 当前会话
-- 配置文件
[mysqld]
innodb_lock_wait_timeout = 30
超时错误处理
SQL
-- 超时后抛出错误
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
-- 应用层处理超时
-- 1. 重试机制
-- 2. 回滚当前事务
-- 3. 记录日志分析
查看锁等待状态
当前锁等待
SQL
-- MySQL 8.0+
SELECT
OBJECT_NAME,
LOCK_TYPE,
LOCK_MODE,
LOCK_DATA,
THREAD_ID
FROM performance_schema.data_locks;
-- 查看锁等待关系
SELECT
requesting_thread_id,
blocking_thread_id,
requesting_lock_type,
blocking_lock_type
FROM performance_schema.data_lock_waits;
-- MySQL 5.7
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
SELECT * FROM information_schema.INNODB_LOCKS;
当前事务状态
SQL
-- 查看当前活跃事务
SELECT
trx_id,
trx_state,
trx_started,
trx_mysql_thread_id,
trx_query,
trx_rows_locked,
trx_lock_structs
FROM information_schema.INNODB_TRX;
-- trx_state:
-- RUNNING: 正在执行
-- LOCK WAIT: 锁等待中
-- ROLLING BACK: 回滚中
查看阻塞关系
SQL
-- 查找阻塞源头
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX r ON w.requesting_trx_id = r.trx_id
JOIN information_schema.INNODB_TRX b ON w.blocking_trx_id = b.trx_id;
死锁与检测
死锁概念
SQL
死锁:两个事务互相等待对方释放锁
事务A:持有资源1锁,等待资源2锁
事务B:持有资源2锁,等待资源1锁
↓
互相等待,无法继续
↓
需要检测并回滚其中一个事务
死锁示例
SQL
-- 事务A
START TRANSACTION;
UPDATE orders SET amount = 100 WHERE id = 1; -- X锁(id=1)
-- 事务B
START TRANSACTION;
UPDATE orders SET amount = 200 WHERE id = 2; -- X锁(id=2)
-- 事务A继续
UPDATE orders SET amount = 300 WHERE id = 2; -- 等待X锁(id=2)
-- 事务B继续
UPDATE orders SET amount = 400 WHERE id = 1; -- 等待X锁(id=1)
-- 死锁产生!
死锁检测参数
SQL
-- 死锁检测开关
SHOW VARIABLES LIKE 'innodb_deadlock_detect';
-- 默认ON(开启检测)
-- 关闭死锁检测(高并发场景可能关闭)
SET GLOBAL innodb_deadlock_detect = OFF;
-- 关闭后依赖超时机制
死锁处理
SQL
-- 死锁发生时,InnoDB自动回滚一个事务
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
-- 回滚的是持有锁最少的事务
-- 应用层需重试被回滚的事务
查看死锁日志
SQL
-- 查看最近死锁信息
SHOW ENGINE INNODB STATUS;
-- 在LATEST DEADLOCK部分查看详情
-- 包含:
-- 死锁发生时间
-- 涉及事务信息
-- 持有和等待的锁
-- 被回滚的事务
避免锁等待和死锁
最佳实践
| 建议 | 说明 |
|---|---|
| 保持事务短小 | 减少锁持有时间 |
| 按固定顺序访问资源 | 避免循环等待 |
| 避免长事务 | 长事务持有锁时间长 |
| 合理设计索引 | 行锁而非表锁 |
| 设置合理超时 | 防止无限等待 |
代码示例
SQL
-- ✅ 按固定顺序访问(避免死锁)
START TRANSACTION;
UPDATE orders SET amount = 100 WHERE id = 1;
UPDATE orders SET amount = 200 WHERE id = 2;
COMMIT;
-- ❌ 不同事务顺序相反(可能死锁)
-- 事务A:先1后2
-- 事务B:先2后1
-- ✅ 使用SELECT FOR UPDATE提前锁定
START TRANSACTION;
SELECT * FROM orders WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
UPDATE orders SET amount = 100 WHERE id = 1;
UPDATE orders SET amount = 200 WHERE id = 2;
COMMIT;
监控锁等待指标
状态变量
text
-- 锁等待统计
SHOW STATUS LIKE 'Innodb_row_lock%';
-- Innodb_row_lock_current_waits: 当前等待数
-- Innodb_row_lock_time: 总等待时间(毫秒)
-- Innodb_row_lock_time_avg: 平均等待时间
-- Innodb_row_lock_time_max: 最大等待时间
-- Innodb_row_lock_waits: 等待次数
监控SQL
text
-- 计算平均锁等待时间
SELECT
Innodb_row_lock_time_avg / 1000 AS avg_lock_wait_sec,
Innodb_row_lock_waits AS total_waits
FROM (
SELECT VARIABLE_VALUE AS Innodb_row_lock_time_avg
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME = 'Innodb_row_lock_time_avg'
) a,
(
SELECT VARIABLE_VALUE AS Innodb_row_lock_waits
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME = 'Innodb_row_lock_waits'
) b;
-- 建议:avg_lock_wait_sec < 1秒
要点总结
- 锁等待超时默认50秒,可按需调整
- 超时后抛出1205错误,应用层需处理重试
- 死锁检测默认开启,自动回滚一个事务
- 通过performance_schema查看锁等待状态
- 保持事务短小、固定访问顺序避免死锁
- 监控Innodb_row_lock状态变量评估锁竞争
📝 发现内容有误?点击此处直接编辑