本文档风哥主要介绍TiDB死锁检测与处理方法,包括死锁的概念与特点、死锁产生的原因、死锁检测的原理、死锁预防策略、死锁避免策略、死锁监控配置、死锁处理方法等内容,风哥教程参考TiDB官方文档死锁相关内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。更多视频教程www.fgedu.net.cn
Part01-基础概念与理论知识
1.1 死锁的概念与特点
死锁是指两个或多个事务相互等待对方释放锁,导致所有事务都无法继续执行的现象。死锁的特点:学习交流加群风哥微信: itpux-com
- 相互等待:多个事务相互等待对方释放锁
- 循环等待:事务之间形成循环等待链
- 无法自动解决:死锁不会自动解除,需要外部干预
- 资源浪费:死锁中的事务占用系统资源但无法继续执行
- 系统影响:死锁会导致系统性能下降,甚至系统不稳定
1.2 死锁产生的原因
死锁产生的原因主要包括以下几个方面:
- 资源竞争:多个事务同时竞争同一资源
- 循环等待:事务之间形成循环等待链
- 锁顺序不一致:不同事务以不同的顺序获取锁
- 长事务:事务执行时间过长,持有锁的时间过长
- 锁粒度不当:使用了过粗的锁粒度,增加了锁冲突的可能性
- 并发度过高:系统并发度过高,增加了锁冲突的可能性
1.3 死锁检测的原理
TiDB的死锁检测原理:
## 1. 死锁检测算法
– 使用等待图(Wait-For Graph, WFG)算法
– 每个事务作为图中的节点
– 事务之间的等待关系作为图中的边
– 检测图中是否存在环,环即表示死锁
## 2. 死锁检测时机
– 当事务等待锁超过一定时间时触发
– 定期检测:默认每100ms检测一次
– 主动检测:当事务等待锁时主动触发
## 3. 死锁处理策略
– 选择一个事务作为牺牲品回滚
– 选择原则:
– 事务执行时间短的
– 事务影响范围小的
– 事务优先级低的
## 4. 死锁检测配置
– tidb_deadlock_detection_interval:死锁检测间隔(默认100ms)
– tidb_deadlock_detection_factor:死锁检测因子(默认3)风哥提示:
– tidb_lock_wait_timeout:锁等待超时时间(默认30000ms)
Part02-生产环境规划与建议
2.1 死锁预防策略
死锁预防策略:
## 1. 按顺序获取锁
– 所有事务按相同的顺序获取锁
– 例如:先获取表A的锁,再获取表B的锁
– 避免循环等待
## 2. 减少事务长度
– 缩短事务的执行时间
– 减少事务持有锁的时间
– 避免在事务中执行耗时操作
## 3. 合理使用锁粒度
– 使用行锁而非表锁
– 减少锁的范围
– 提高并发度
## 4. 合理设置隔离级别
– 根据业务需求选择合适的隔离级别
– 例如:使用READ COMMITTED减少锁竞争
## 5. 避免长事务
– 拆分长事务为多个短事务
– 避免在事务中执行网络请求、文件IO等耗时操作
## 6. 合理控制并发度
– 根据系统资源和业务需求控制并发度
– 避免并发度过高导致锁冲突
2.2 死锁避免策略
死锁避免策略:
## 1. 资源分配策略
– 使用银行家算法等资源分配策略
– 在分配资源前检查是否会导致死锁
– 避免不安全状态
## 2. 超时机制
– 设置合理的锁等待超时时间
– 当锁等待超过阈值时自动放弃
– 避免无限期等待
## 3. 事务重试机制
– 在应用层实现事务重试机制
– 当检测到死锁时自动重试
– 提高系统的可用性
## 4. 监控与预警
– 监控死锁的发生频率
– 设置死锁告警阈值
– 及时发现和处理死锁问题
## 5. 应用层优化
– 在应用层避免并发冲突
– 使用乐观锁替代悲观锁
– 合理设计业务逻辑
2.3 死锁检测配置
死锁检测配置:
## 1. TiDB配置文件设置
$ vim /tidb/app/tidb/conf/tidb.toml
[performance]
# 死锁检测间隔(毫秒)
deadlock-detection-interval = 100
# 死锁检测因子
deadlock-detection-factor = 3
[server]
# 锁等待超时时间(毫秒)
lock-wait-timeout = 30000
## 2. 会话级设置
mysql> SET SESSION tidb_deadlock_detection_interval = 100;
mysql> SET SESSION tidb_lock_wait_timeout = 30000;
## 3. 全局级设置
mysql> SET GLOBAL tidb_deadlock_detection_interval = 100;学习交流加群风哥QQ113257174
mysql> SET GLOBAL tidb_lock_wait_timeout = 30000;
## 4. 查看当前配置
mysql> SHOW VARIABLES LIKE ‘tidb_deadlock%’;
mysql> SHOW VARIABLES LIKE ‘tidb_lock_wait_timeout’;
Part03-生产环境项目实施方案
3.1 死锁监控配置
3.1.1 TiDB Dashboard死锁监控
http://192.168.1.10:2379/dashboard
# 查看死锁信息
1. 点击左侧菜单”事务” -> “死锁”
2. 查看死锁历史记录
3. 分析死锁原因
# 死锁记录内容
– 死锁发生时间
– 涉及的事务ID
– 涉及的表和索引
– 锁类型和锁范围
– 回滚的事务
3.1.2 Prometheus死锁监控指标
– tidb_transaction_deadlock_count:死锁次数
# 查看死锁次数
sum(rate(tidb_transaction_deadlock_count[5m])) by (instance)
# 配置Grafana面板
1. 登录Grafana
2. 创建新面板
3. 添加上述指标
4. 设置告警阈值
# 告警配置
– 当死锁次数超过阈值时告警
– 例如:5分钟内死锁次数超过10次
3.2 死锁处理方法
3.2.1 自动处理
## 1. TiDB自动检测和处理
– TiDB会自动检测死锁
– 当检测到死锁时,会自动选择一个事务回滚
– 回滚的事务会收到死锁错误
## 2. 应用层处理
– 捕获死锁错误(错误码1213)
– 自动重试事务
– 例如:
try {
// 执行事务
} catch (SQLException e) {
if (e.getErrorCode() == 1213) {
// 死锁,重试事务
retryTransaction();
}
}
3.2.2 手动处理
## 1. 识别死锁
– 查看死锁日志
– 使用TiDB Dashboard查看死锁信息
– 使用SQL语句查看锁等待情况
## 2. 分析死锁原因
– 查看涉及的事务
– 分析SQL语句
– 检查锁顺序
## 3. 处理死锁
– 终止持有锁时间过长的事务
– 优化SQL语句
– 调整锁顺序
– 调整事务设计
## 4. 预防死锁
– 按顺序获取锁
– 减少事务长度
– 合理使用锁粒度
– 合理设置隔离级别
3.3 死锁分析工具
3.3.1 TiDB Dashboard
## 1. 访问死锁页面
http://192.168.1.10:2379/dashboard/#/deadlocks
## 2. 查看死锁详情
– 死锁ID
– 发生时间
– 涉及的事务
– 涉及的SQL语句
– 锁信息
## 3. 分析死锁原因
– 查看事务执行的SQL语句
– 分析锁的获取顺序
– 确定死锁的根本原因
3.3.2 SQL语句分析
## 1. 查看锁等待情况
mysql> SELECT * FROM information_schema.tidb_lock_waits;
## 2. 查看事务情况
mysql> SELECT * FROM information_schema.tidb_trx;
## 3. 查看进程列表
mysql> SHOW PROCESSLIST;
## 4. 查看死锁日志
mysql> SHOW GLOBAL VARIABLES LIKE ‘log_output’;
mysql> SHOW GLOBAL VARIABLES LIKE ‘general_log’;
## 5. 分析执行计划
mysql> EXPLAIN SELECT * FROM fgedu_users WHERE id = 1;
Part04-生产案例与实战讲解
4.1 死锁复现与分析
4.1.1 死锁复现
# 事务1:更新表A,然后更新表B
mysql> BEGIN;
mysql> UPDATE fgedu_users SET name = ‘test1’ WHERE id = 1;
# 事务2:更新表B,然后更新表A
mysql> BEGIN;
mysql> UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
mysql> UPDATE fgedu_users SET name = ‘test2’ WHERE id = 1;
# 事务1:更新表B
mysql> UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
# 输出示例
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
# 说明:事务1和事务2相互等待对方释放锁,导致死锁
4.1.2 死锁分析
## 1. 查看TiDB Dashboard死锁信息
– 登录TiDB Dashboard
– 点击左侧菜单”事务” -> “死锁”
– 查看死锁详情
## 2. 分析死锁日志
$ tail -f /tidb/app/tidb/log/tidb.log | grep “deadlock”
# 输出示例
[2026/04/09 10:00:00.000 +08:00] [INFO] [deadlock.go:183] [“deadlock detected”] [txn1=12345] [txn2=12346] [waitChain=12345->12346->12345]
## 3. 分析SQL语句
– 事务1的SQL:
UPDATE fgedu_users SET name = ‘test1’ WHERE id = 1;
UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
– 事务2的SQL:
UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
UPDATE fgedu_users SET name = ‘test2’ WHERE id = 1;
## 4. 分析锁顺序
– 事务1:先获取fgedu_users的锁,再获取fgedu_orders的锁
– 事务2:先获取fgedu_orders的锁,再获取fgedu_users的锁
– 形成循环等待:事务1等待事务2释放fgedu_orders的锁,事务2等待事务1释放fgedu_users的锁
4.2 死锁解决实战
4.2.1 按顺序获取锁
## 1. 修改事务1
mysql> BEGIN;
mysql> — 先获取fgedu_users的锁
mysql> UPDATE fgedu_users SET name = ‘test1’ WHERE id = 1;
mysql> — 再获取fgedu_orders的锁
mysql> UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
mysql> COMMIT;
## 2. 修改事务2
mysql> BEGIN;
mysql> — 先获取fgedu_users的锁
mysql> UPDATE fgedu_users SET name = ‘test2’ WHERE id = 1;
mysql> — 再获取fgedu_orders的锁
mysql> UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
mysql> COMMIT;
## 3. 测试结果
– 事务1和事务2不再发生死锁
– 虽然可能会有锁等待,但不会形成死锁
4.2.2 减少事务长度
## 1. 原事务(长事务)
mysql> BEGIN;
mysql> UPDATE fgedu_users SET name = ‘test1’ WHERE id = 1;
— 执行耗时操作(如网络请求、文件IO等)
mysql> UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
mysql> COMMIT;
## 2. 修改后的事务(短事务)
— 事务1:更新用户信息
mysql> BEGIN;
mysql> UPDATE fgedu_users SET name = ‘test1’ WHERE id = 1;
mysql> COMMIT;
— 执行耗时操作
— 事务2:更新订单信息
mysql> BEGIN;
mysql> UPDATE fgedu_orders SET status = ‘paid’ WHERE user_id = 1;
mysql> COMMIT;
## 3. 测试结果
– 事务持有锁的时间缩短
– 减少了锁冲突的可能性
– 降低了死锁的风险
4.3 死锁优化案例
4.3.1 优化锁顺序
## 1. 原代码(容易产生死锁)
# 事务1
begin;
update tableA set col1 = ‘value1’ where id = 1;
update tableB set col1 = ‘value1’ where id = 1;
commit;
# 事务2
begin;
update tableB set col1 = ‘value2’ where id = 1;
update tableA set col1 = ‘value2’ where id = 1;
commit;
## 2. 优化后代码(避免死锁)
# 所有事务按相同的顺序更新表
# 事务1
begin;
update tableA set col1 = ‘value1’ where id = 1;
update tableB set col1 = ‘value1’ where id = 1;
commit;
# 事务2
begin;
update tableA set col1 = ‘value2’ where id = 1;
update tableB set col1 = ‘value2’ where id = 1;
commit;
## 3. 优化效果
– 避免了循环等待
– 减少了死锁的发生
– 提高了系统的稳定性
4.3.2 优化索引
## 1. 原SQL(缺少索引)
mysql> UPDATE fgedu_users SET name = ‘test’ WHERE name = ‘old_name’;
# 执行计划
+————————-+———-+———–+—————+——————————–+————————-+———+——+————————–+———————–+
| id | estRows | task | access object | operator info | actRows | execution info | memory | disk | transaction info | operator info |
+————————-+———-+———–+—————+——————————–+————————-+———+——+————————–+———————–+
| Update_1 | N/A | root | | N/A | 1000 | time:0.1s | N/A | N/A | ttl:5000ms | N/A |
| └─TableReader_6 | 1000.00 | root | | data:TableScan_5 | 1000 | time:0.1s | 1.00 KB | N/A | | N/A |
| └─TableScan_5 | 1000.00 | cop[tikv] | table:fgedu_users | range:[-inf,+inf], keep order:false | 1000 | time:0.1s | N/A | N/A | | N/A |
+————————-+———-+———–+—————+——————————–+————————-+———+——+————————–+———————–+
## 2. 优化后(添加索引)
mysql> ALTER TABLE fgedu_users ADD INDEX idx_name (name);
mysql> UPDATE fgedu_users SET name = ‘test’ WHERE name = ‘old_name’;
# 执行计划
+————————-+———-+———–+—————+——————————–+————————-+———+——+————————–+———————–+
| id | estRows | task | access object | operator info | actRows | execution info | memory | disk | transaction info | operator info |
+————————-+———-+———–+—————+——————————–+————————-+———+——+————————–+———————–+
| Update_1 | N/A | root | | N/A | 1 | time:0.01s | N/A | N/A | ttl:5000ms | N/A |
| └─IndexReader_6 | 1.00 | root | | index:IndexRangeScan_5 | 1 | time:0.01s | 1.00 KB | N/A | | N/A |
| └─IndexRangeScan_5 | 1.00 | cop[tikv] | table:fgedu_users, index:idx_name(name) | range:[“old_name”,”old_name”], keep order:false | 1 | time:0.01s | N/A | N/A | | N/A |
+————————-+———-+———–+—————+——————————–+————————-+———+——+————————–+———————–+
## 3. 优化效果
– 锁范围减小
– 减少了锁冲突的可能性
– 降低了死锁的风险
– 提高了SQL执行效率
Part05-风哥经验总结与分享
5.1 死锁处理最佳实践
死锁处理最佳实践:
- 按顺序获取锁:所有事务按相同的顺序获取锁,避免循环等待
- 减少事务长度:缩短事务的执行时间,减少持有锁的时间
- 合理使用锁粒度:使用行锁而非表锁,减少锁的范围
- 合理设置隔离级别:根据业务需求选择合适的隔离级别
- 避免长事务:拆分长事务为多个短事务,避免在事务中执行耗时操作
- 监控死锁:建立死锁监控机制,及时发现和处理死锁问题
- 应用层重试:在应用层实现事务重试机制,当检测到死锁时自动重试
5.2 死锁预防技巧
死锁预防技巧:
- 标准化锁顺序:制定统一的锁获取顺序规范,所有开发人员遵循
- 使用乐观锁:对于并发冲突较少的场景,使用乐观锁替代悲观锁
- 合理设计业务逻辑:避免在业务逻辑中产生循环依赖
- 优化索引:确保SQL语句使用索引,减少锁范围
- 控制并发度:根据系统资源和业务需求控制并发度
- 定期分析死锁:定期分析死锁日志,找出死锁的根本原因
- 培训开发人员:提高开发人员对死锁的认识,掌握死锁预防技巧
5.3 死锁对性能的影响
## 1. 直接影响
– 事务回滚:死锁导致事务回滚,需要重新执行
– 资源浪费:死锁中的事务占用系统资源但无法继续执行
– 响应时间延长:死锁导致事务执行时间延长
– 系统吞吐量下降:死锁导致系统处理能力下降
## 2. 间接影响
– 增加系统负载:死锁检测和处理需要消耗系统资源
– 影响其他事务:死锁可能导致其他事务被阻塞
– 系统不稳定:严重的死锁可能导致系统不稳定
## 3. 性能优化建议
– 减少死锁的发生:通过优化事务设计和SQL语句
– 快速检测和处理死锁:合理配置死锁检测参数
– 监控死锁:及时发现和处理死锁问题
– 优化系统资源:确保系统有足够的资源处理并发事务
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
