Mysql 在什么样的情况下会产生死锁?

在 MySQL 中,死锁是指两个或多个事务相互等待对方释放锁,导致所有相关事务无法继续执行的情况。死锁会影响数据库的并发性能,因此需要及时检测并处理。假设有两个事务 T1 和 T2:

事务 T1 首先锁定 表 A 的行 1。然后尝试锁定 表 B 的行 1。

事务 T2 首先锁定 表 B 的行 1。然后尝试锁定 表 A 的行 1。

T1 锁定了表 A 的行 1,并等待获取表 B 的行 1。

T2 锁定了表 B 的行 1,并等待获取表 A 的行 1。

此时,T1 和 T2 互相等待,导致死锁。
解决方式:MySQL 会自动检测死锁,并回滚其中一个事务,释放其占有的锁,以使另一个事务得以继续执行。

MySQL 会在以下几种情况下产生死锁(Deadlock)

1. 多个事务同时锁定相同的资源(循环等待)

  • 当两个或多个事务以不同的顺序锁定相同的资源,并相互等待对方释放锁时,就会发生死锁。

  • 示例:

    sql 复制代码
    -- 事务 A
    START TRANSACTION;
    UPDATE table1 SET column1 = 'value1' WHERE id = 1;  -- 锁住 id = 1
    UPDATE table1 SET column1 = 'value2' WHERE id = 2;  -- 等待事务 B 释放 id = 2
    
    -- 事务 B
    START TRANSACTION;
    UPDATE table1 SET column1 = 'value2' WHERE id = 2;  -- 锁住 id = 2
    UPDATE table1 SET column1 = 'value1' WHERE id = 1;  -- 等待事务 A 释放 id = 1

    由于事务 A 和事务 B 互相等待对方释放锁,造成死锁。

2. 索引不合理导致的意外行锁升级

  • InnoDB 存储引擎下,行锁是基于索引 的,而不是基于物理行。如果 SQL 语句未使用索引,会导致 表锁,进而增加死锁的可能性。

  • 示例:

    sql 复制代码
    -- 假设 `column1` 没有索引
    SELECT * FROM table1 WHERE column1 = 'some_value' FOR UPDATE;

    由于 column1 没有索引,InnoDB 会对整张表加锁,可能导致多个事务争抢表锁,增加死锁风险。

3. 不同事务的更新顺序不一致

  • 当多个事务以不同的顺序修改同一张表的不同行时,容易产生死锁。

  • 示例:

    sql 复制代码
    -- 事务 A
    UPDATE orders SET status = 'shipped' WHERE id = 1;
    UPDATE orders SET status = 'shipped' WHERE id = 2;
    
    -- 事务 B
    UPDATE orders SET status = 'shipped' WHERE id = 2;
    UPDATE orders SET status = 'shipped' WHERE id = 1;
    • 事务 A 先锁住 id=1,再尝试锁 id=2
    • 事务 B 先锁住 id=2,再尝试锁 id=1
    • 结果:两个事务都在等待对方释放锁,造成死锁。

4. 外键约束导致的隐式锁

  • 由于 外键约束 可能导致级联更新/删除操作,在并发事务中可能出现死锁。

  • 示例:

    sql 复制代码
    -- 假设 child_table 具有外键约束
    DELETE FROM parent_table WHERE id = 1;
    DELETE FROM parent_table WHERE id = 2;

    由于外键约束,删除 parent_table 的数据会影响 child_table,多个事务并发执行时,可能导致死锁。

5. 间隙锁(Next-Key Lock)

  • REPEATABLE READ 隔离级别下,InnoDB 可能会对索引范围执行 间隙锁(Gap Lock),导致多个事务在并发插入或更新时出现死锁。

  • 示例:

    sql 复制代码
    SELECT * FROM users WHERE age BETWEEN 18 AND 25 FOR UPDATE;
    • 如果多个事务同时查询 18~25 之间的数据并尝试更新,就可能导致死锁。

如何解决 MySQL 死锁?

  1. 使用索引

    • 确保 WHERE 条件涉及索引,避免表锁导致的死锁。
  2. 控制事务顺序

    • 保证多个事务按相同的顺序访问数据,减少死锁发生的概率。
  3. 减少事务持有锁的时间

    • 使用 短事务 ,减少锁持有时间:

      sql 复制代码
      START TRANSACTION;
      -- 快速执行 SQL 语句
      COMMIT;
  4. 降低隔离级别

    • 在合适的业务场景下,将事务隔离级别从 REPEATABLE READ 降为 READ COMMITTED,避免间隙锁导致的死锁。
  5. 监控死锁并进行重试

    • 通过 SHOW ENGINE INNODB STATUS\G 查看死锁信息。

    • 代码层面可使用重试机制 ,如:

      python 复制代码
      for _ in range(3):  # 允许最多重试3次
          try:
              cursor.execute("UPDATE ...")  # 可能导致死锁的操作
              connection.commit()
              break  # 成功执行,跳出循环
          except MySQLdb.OperationalError as e:
              if "Deadlock" in str(e):
                  continue  # 遇到死锁,重试
              else:
                  raise  # 其他错误则抛出
相关推荐
鹏程十八少5 分钟前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker22 分钟前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 小时前
Android 协程时代,Handler 应该退休了吗?
android
NineData9 小时前
NineData 迁移评估功能正式上线
数据库·dba
火柴就是我14 小时前
让我们实现一个更好看的内部阴影按钮
android·flutter
NineData15 小时前
数据库迁移总踩坑?用 NineData 迁移评估,提前识别所有兼容性风险
数据库·程序员·云计算
赵渝强老师17 小时前
【赵渝强老师】PostgreSQL中表的碎片
数据库·postgresql
砖厂小工21 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
全栈老石21 小时前
拆解低代码引擎核心:元数据驱动的"万能表"架构
数据库·低代码
张拭心1 天前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能