MySQL InnoDB重复键场景下锁机制及异常行为相关问题咨询
MySQL 5.7 唯一键冲突锁机制问题解析
背景来源:mysql-refman-5.7
测试表结构与复现流程
表结构定义:
mysql> show create table t1 \G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `i` int(11) NOT NULL, PRIMARY KEY (`i`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec)
初始表数据:
mysql> select * from t1; Empty set (0.00 sec)
会话操作序列:
-- session1: 操作1 start transaction; insert into t1 values(1);
-- session2: 操作2 insert into t1 values(1);
-- session3: 操作3 start transaction; insert into t1 values(1);
-- session1: 操作4 rollback;
官方参考解释
- 操作1执行后,session1获取主键值为1的行的排他锁(Exclusive Lock)
- 操作2、3执行时会触发重复键检查逻辑,同时会申请对应行的共享锁(Shared Lock)
- 操作4执行后,session1释放该行排他锁,session2、3获取到共享锁
问题解答
Q1:若操作4不执行,操作2、3执行后两个会话是否会一直等待该行的排他锁直至超时?为何此时没有出现“重复键错误”提示?
会等待直至超时,不会永久阻塞。
未抛出重复键错误的核心原因是:唯一键冲突校验逻辑需要先拿到待插入行的锁,才能读取到行的最新提交状态,完成冲突校验。session1持有行排他锁未释放时,session2、3的请求会先进入锁等待队列,还没到执行唯一键冲突校验的步骤,因此不会触发重复键报错。等待超过innodb_lock_wait_timeout参数配置的阈值后,会抛出锁超时错误,而非重复键错误。
Q2:触发重复键错误时,为何session2和session3会申请对应行的共享锁?两个会话不应该因异常直接结束执行吗?
InnoDB设计该逻辑是为了确认唯一键冲突的最终状态:待插入位置的已存在记录,可能是其他未提交事务写入的,若持有排他锁的事务后续回滚,当前插入操作实际可以执行成功。因此会话不会直接抛出异常结束,而是先申请共享锁,待拿到锁后读取行的最终提交状态,再判定是否真的存在冲突。如果拿到锁后发现原持有排他锁的事务已经回滚,行记录不存在,就可以继续完成插入;如果确认行已提交存在,才会抛出重复键错误。
Q3:重复键错误是否会作为错误消息主动抛出提示?
会主动抛出。只要锁等待完成后,最终确认存在已提交的同唯一键记录,就会返回1062 Duplicate entry 'xxx' for key 'xxx'的错误提示。只有锁等待未结束、或者锁超时的场景下,才不会抛出重复键错误。
Q4:为何重复键错误不会终止当前会话?
重复键错误属于单SQL语句级别的运行时错误,不属于会话级别的致命错误。MySQL的错误处理机制默认只会终止当前执行失败的单条SQL,不会中断整个会话连接,也不会自动回滚当前事务,用户可以自行选择回滚事务、调整SQL重新执行,或者提交事务中其他执行成功的操作。
内容的提问来源于stack exchange,提问作者Jack Ma
相关产品推荐
相关产品推荐

