1. 首页 > 国产数据库教程 > TiDB教程 > 正文

tidb教程FG057-TiDB死锁检测与处理方法

本文档风哥主要介绍TiDB死锁检测与处理方法,包括死锁的概念与特点、死锁产生的原因、死锁检测的原理、死锁预防策略、死锁避免策略、死锁监控配置、死锁处理方法等内容,风哥教程参考TiDB官方文档死锁相关内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。更多视频教程www.fgedu.net.cn

Part01-基础概念与理论知识

1.1 死锁的概念与特点

死锁是指两个或多个事务相互等待对方释放锁,导致所有事务都无法继续执行的现象。死锁的特点:学习交流加群风哥微信: itpux-com

死锁的特点:

  • 相互等待:多个事务相互等待对方释放锁
  • 循环等待:事务之间形成循环等待链
  • 无法自动解决:死锁不会自动解除,需要外部干预
  • 资源浪费:死锁中的事务占用系统资源但无法继续执行
  • 系统影响:死锁会导致系统性能下降,甚至系统不稳定

1.2 死锁产生的原因

死锁产生的原因主要包括以下几个方面:

  • 资源竞争:多个事务同时竞争同一资源
  • 循环等待:事务之间形成循环等待链
  • 锁顺序不一致:不同事务以不同的顺序获取锁
  • 长事务:事务执行时间过长,持有锁的时间过长
  • 锁粒度不当:使用了过粗的锁粒度,增加了锁冲突的可能性
  • 并发度过高:系统并发度过高,增加了锁冲突的可能性

1.3 死锁检测的原理

TiDB的死锁检测原理:

# 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)

风哥提示:TiDB的死锁检测是自动进行的,当检测到死锁时,会自动选择一个事务回滚,以解除死锁。学习交流加群风哥QQ113257174

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’;

生产环境建议:根据系统的实际情况,合理配置死锁检测参数。对于高并发系统,可能需要调整死锁检测间隔和锁等待超时时间,以平衡系统性能和死锁检测的及时性。更多学习教程公众号风哥教程itpux_com

Part03-生产环境项目实施方案

3.1 死锁监控配置

3.1.1 TiDB Dashboard死锁监控

# 访问TiDB Dashboard
http://192.168.1.10:2379/dashboard

# 查看死锁信息
1. 点击左侧菜单”事务” -> “死锁”
2. 查看死锁历史记录
3. 分析死锁原因

# 死锁记录内容
– 死锁发生时间
– 涉及的事务ID
– 涉及的表和索引
– 锁类型和锁范围
– 回滚的事务

3.1.2 Prometheus死锁监控指标

# 死锁相关的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

# 使用TiDB Dashboard分析死锁

## 1. 访问死锁页面
http://192.168.1.10:2379/dashboard/#/deadlocks

## 2. 查看死锁详情
– 死锁ID
– 发生时间
– 涉及的事务
– 涉及的SQL语句
– 锁信息

## 3. 分析死锁原因
– 查看事务执行的SQL语句
– 分析锁的获取顺序
– 确定死锁的根本原因

3.3.2 SQL语句分析

# 使用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;

风哥提示:TiDB的死锁分析需要综合使用多种工具和方法,包括TiDB Dashboard、SQL语句和日志分析,以便全面了解死锁的情况和原因。from tidb视频:www.itpux.com

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 优化索引

# 场景:SQL语句缺少索引,导致锁范围过大

## 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执行效率

生产环境建议:建立死锁监控和分析机制,及时发现和处理死锁问题。建议优化事务设计和SQL语句,减少死锁的发生,提高系统的稳定性和性能。

Part05-风哥经验总结与分享

5.1 死锁处理最佳实践

死锁处理最佳实践:

  • 按顺序获取锁:所有事务按相同的顺序获取锁,避免循环等待
  • 减少事务长度:缩短事务的执行时间,减少持有锁的时间
  • 合理使用锁粒度:使用行锁而非表锁,减少锁的范围
  • 合理设置隔离级别:根据业务需求选择合适的隔离级别
  • 避免长事务:拆分长事务为多个短事务,避免在事务中执行耗时操作
  • 监控死锁:建立死锁监控机制,及时发现和处理死锁问题
  • 应用层重试:在应用层实现事务重试机制,当检测到死锁时自动重试

5.2 死锁预防技巧

死锁预防技巧:

  • 标准化锁顺序:制定统一的锁获取顺序规范,所有开发人员遵循
  • 使用乐观锁:对于并发冲突较少的场景,使用乐观锁替代悲观锁
  • 合理设计业务逻辑:避免在业务逻辑中产生循环依赖
  • 优化索引:确保SQL语句使用索引,减少锁范围
  • 控制并发度:根据系统资源和业务需求控制并发度
  • 定期分析死锁:定期分析死锁日志,找出死锁的根本原因
  • 培训开发人员:提高开发人员对死锁的认识,掌握死锁预防技巧

5.3 死锁对性能的影响

# 死锁对性能的影响

## 1. 直接影响
– 事务回滚:死锁导致事务回滚,需要重新执行
– 资源浪费:死锁中的事务占用系统资源但无法继续执行
– 响应时间延长:死锁导致事务执行时间延长
– 系统吞吐量下降:死锁导致系统处理能力下降

## 2. 间接影响
– 增加系统负载:死锁检测和处理需要消耗系统资源
– 影响其他事务:死锁可能导致其他事务被阻塞
– 系统不稳定:严重的死锁可能导致系统不稳定

## 3. 性能优化建议
– 减少死锁的发生:通过优化事务设计和SQL语句
– 快速检测和处理死锁:合理配置死锁检测参数
– 监控死锁:及时发现和处理死锁问题
– 优化系统资源:确保系统有足够的资源处理并发事务

风哥提示:TiDB的死锁检测与处理是数据库运维的重要组成部分,需要综合使用多种方法进行优化。建议建立完善的监控机制,及时发现和处理死锁问题,提高系统的稳定性和性能。

持续改进:死锁的预防和处理是一个持续优化的过程,需要根据业务需求和系统变化不断调整和改进。建议定期分析死锁日志,优化事务设计和SQL语句,减少死锁的发生。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息