数据库死锁场景如何复现和解决?

需了解死锁先看这一篇

死锁是如何被发现和解决的?这篇文章告诉你

一、死锁的产生原因

  • 死锁发生在两个或多个事务相互等待对方释放锁,导致它们都无法继续执行的情况,形成死锁。
  • 这种情况在并发高的系统中比较常见,尤其是在多个事务同时操作相同的数据时。

常见场景包括:

  1. 不同顺序访问资源:事务A先操作表1再操作表2,事务B先操作表2再操作表1。
  2. 索引缺失:全表扫描导致锁范围扩大,增加冲突概率。
  3. 长事务:事务长时间未提交,导致锁持有时间过长。

二、死锁场景复现(以MySQL为例)

  • 复现死锁可以帮助理解问题发生的条件,从而更好地预防和解决。
1. 准备测试表和数据
sql 复制代码
CREATE TABLE account (
    id INT PRIMARY KEY,
    balance DECIMAL(10,2)
);
INSERT INTO account VALUES (1, 1000.00), (2, 2000.00);
2. 模拟两个事务交叉更新
  • 事务A:先更新id=1,再更新id=2。

    sql 复制代码
    BEGIN;
    UPDATE account SET balance = balance - 100 WHERE id = 1;
    -- 等待事务B执行后再继续
    UPDATE account SET balance = balance + 100 WHERE id = 2;
    COMMIT;
  • 事务B:先更新id=2,再更新id=1。

    sql 复制代码
    BEGIN;
    UPDATE account SET balance = balance - 200 WHERE id = 2;
    -- 等待事务A执行后再继续
    UPDATE account SET balance = balance + 200 WHERE id = 1;
    COMMIT;
3. 观察死锁
  1. 按顺序执行事务A和事务B的UPDATE语句。

  2. 事务A尝试更新id=2时,因事务B持有锁而等待。

  3. 事务B尝试更新id=1时,因事务A持有锁而等待。

  4. 数据库检测到死锁,自动回滚其中一个事务:

    sql 复制代码
    1205 - Lock wait timeout exceeded; try restarting transaction

三、解决死锁的核心方法

  • 常见的解决策略包括设置合理的事务隔离级别、优化事务逻辑、使用超时机制、以及数据库自动检测和处理死锁
1. 数据库自动处理
  • 死锁检测:数据库自动检测死锁并回滚代价较小的事务(如MySQL默认开启)。

  • 查看死锁日志 (MySQL):

    sql 复制代码
    SHOW ENGINE INNODB STATUS;  -- 查看LATEST DETECTED DEADLOCK部分
2. 代码层优化
  • 统一资源访问顺序 :所有事务按相同顺序操作表或记录。

    sql 复制代码
    -- 所有事务先更新id=1,再更新id=2
    UPDATE account SET ... WHERE id = 1;
    UPDATE account SET ... WHERE id = 2;
  • 减少事务粒度 :避免长事务,尽快提交或回滚。

    java 复制代码
    // 示例:使用Spring的@Transactional设置超时
    @Transactional(timeout = 5)  // 事务5秒未完成则回滚
    public void transfer() { ... }
  • 使用乐观锁 :通过版本号避免行锁竞争。

    sql 复制代码
    UPDATE account 
    SET balance = 900, version = version + 1 
    WHERE id = 1 AND version = 1;
3. 数据库配置调优
  • 降低隔离级别 :从REPEATABLE READ改为READ COMMITTED,减少锁冲突。

    sql 复制代码
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • 索引优化 :为WHERE条件字段添加索引,缩小锁范围。

    sql 复制代码
    ALTER TABLE account ADD INDEX idx_id (id);
4. 重试机制
  • 捕获死锁异常 :代码中捕获DeadlockException并重试。

    java 复制代码
    int retryCount = 0;
    while (retryCount < 3) {
        try {
            executeTransaction();
            break;
        } catch (DeadlockException e) {
            retryCount++;
            Thread.sleep(100); // 等待后重试
        }
    }

四、死锁分析工具

  1. MySQL死锁日志

    • 查看SHOW ENGINE INNODB STATUS输出,分析TRANSACTIONWAITING FOR THIS LOCK部分。
  2. pt-deadlock-logger (Percona工具):

    实时监控死锁事件并记录。

    bash 复制代码
    pt-deadlock-logger --user=root --password=123456 --run-time=10
  3. 性能模式(Performance Schema)

    MySQL 5.6+开启性能模式,监控锁等待事件。

    sql 复制代码
    SELECT * FROM performance_schema.events_transactions_current;

五、预防死锁

  1. 事务设计原则
    • 短事务优先,及时提交。
    • 避免交叉更新多张表。
  2. 统一操作顺序
    所有业务逻辑按固定顺序访问资源(如按主键升序操作)。
  3. 监控与报警
    配置Prometheus监控死锁次数,超过阈值触发告警。
  4. 压力测试
    使用JMeter模拟并发事务,验证死锁概率。

六、总结

  • 复现死锁:通过交叉更新不同顺序的资源,观察数据库自动回滚。
  • 解决方案
    • 统一资源访问顺序,减少锁竞争。
    • 优化索引和事务设计,降低死锁概率。
    • 结合重试机制和数据库自动处理,提升系统容错性。
  • 关键工具:数据库日志、性能分析工具、压力测试框架。
相关推荐
微笑伴你而行15 分钟前
spring boot 发送邮件验证码
java·数据库·spring boot
爱搞技术的猫猫22 分钟前
【实战解析】smallredbook.item_get_video API:小红书视频数据获取与电商应用指南
java·数据库·音视频
huan_199329 分钟前
通过mybatis的拦截器对SQL进行打标
数据库·sql·mybatis·mybatis拦截器·sql打标
超级无敌新新手小白1 小时前
SQL--算术运算符
数据库
菜萝卜子1 小时前
【NoSql】Redis
数据库·redis·nosql
InnovatorX1 小时前
Linux 下 MySQL 8 搭建教程
linux·mysql·adb
TDengine (老段)2 小时前
TDengine 特色查询
android·大数据·数据库·物联网·时序数据库·tdengine·iotdb
临水逸2 小时前
基于CSV构建轻量级数据库:SQL与Excel操作的双模实践
数据库·sql·excel
yqcoder2 小时前
Redis 的特点
数据库·redis·缓存
kanlon2 小时前
SQL Server 与 MySQL 的区别
数据库·后端