面试官:MySQL 唯一索引为什么会导致死锁?

面试官:MySQL 唯一索引为什么会导致死锁?
最新回答
七年觐汐

2026-10-23 07:36:21

MySQL唯一索引导致死锁的原因主要与并发事务执行INSERT ... ON DUPLICATE KEY UPDATE语句时,对同一行记录的加锁顺序不一致有关。以下是详细解释:

1. 唯一索引与死锁的关系

唯一索引确保数据的唯一性,但在并发环境下,多个事务可能同时尝试插入或更新相同唯一键值的记录。当使用INSERT ... ON DUPLICATE KEY UPDATE语句时,MySQL会先尝试插入,如果发现唯一键冲突,则转为更新操作。这个过程中涉及到的加锁机制可能导致死锁。

2. 死锁产生的具体场景

假设有两个事务T1和T2,并发执行以下操作:

-- T1INSERT INTO table (unique_key, other_column) VALUES ('key1', 'value1') ON DUPLICATE KEY UPDATE other_column = 'new_value1';-- T2INSERT INTO table (unique_key, other_column) VALUES ('key1', 'value2') ON DUPLICATE KEY UPDATE other_column = 'new_value2';

3. 加锁顺序与死锁形成

  • T1执行过程:

    尝试插入记录,获取该行的排他锁(X锁)。

    如果发现唯一键冲突(如'key1'已存在),则转为更新操作。此时会先释放X锁,然后获取共享锁(S锁)读取现有记录。

    根据读取到的记录,再次获取X锁进行更新。

  • T2执行过程:与T1类似,但在时间上与T1交错执行。

4. 死锁形成的关键点

当T1和T2以不同的顺序请求锁时,可能形成循环等待:

  1. T1插入记录,获取X锁(假设记录不存在)。
  2. T2尝试插入相同记录,发现冲突,等待T1释放锁。
  3. T1发现冲突,转为更新操作,释放X锁,尝试获取S锁。
  4. 在T1获取S锁的过程中,T2可能已经获取了S锁(取决于具体实现)。
  5. 当T1尝试获取X锁进行更新时,发现T2持有S锁,等待T2释放。
  6. 同时,T2也尝试获取X锁进行更新,发现T1持有S锁,等待T1释放。

这样就形成了T1等待T2,T2又等待T1的循环等待局面,导致死锁。

5. 避免死锁的策略

  • 统一访问顺序:确保所有事务以相同的顺序访问表和行。
  • 减少锁持有时间:尽快提交或回滚事务,减少锁的持有时间。
  • 使用较低的隔离级别:如READ COMMITTED,减少锁的范围。
  • 避免长事务:长时间运行的事务会增加死锁的可能性。
  • 使用乐观锁:通过版本号或时间戳实现,而不是依赖数据库锁。

6. 示例代码与解释

-- 创建带有唯一索引的表CREATE TABLE example ( id INT AUTO_INCREMENT PRIMARY KEY, unique_key VARCHAR(100) UNIQUE, other_column VARCHAR(100), version INT DEFAULT 0);-- 使用乐观锁避免死锁UPDATE example SET other_column = 'new_value', version = version + 1 WHERE unique_key = 'key1' AND version = 0;

在这个乐观锁的例子中,通过版本号控制并发更新,避免了数据库锁的使用,从而减少了死锁的可能性。

7. 结论

唯一索引本身并不直接导致死锁,但在并发环境下,特别是使用INSERT ... ON DUPLICATE KEY UPDATE这类语句时,由于加锁机制的复杂性,可能导致死锁。理解死锁的形成原因,并采取适当的避免策略,是确保数据库并发性能的关键。