【问题标题】:MySQL locking in Duplicate Key ErrorMySQL锁定重复键错误
【发布时间】:2016-10-24 16:25:25
【问题描述】:

来自docs

如果发生重复键错误,重复索引上的共享锁 记录被设定。这种共享锁的使用可能会导致死锁 如果另一个会话尝试插入同一行,则有多个会话 session 已经有一个独占锁。如果另一个 session 删除该行。

使用文档中的示例,

假设 InnoDB 表 t1 具有以下结构:

CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

现在假设三个会话依次执行以下操作:

第 1 节:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

第 2 节:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

第三节:

START TRANSACTION;
INSERT INTO t1 VALUES(1);

第 1 节:

ROLLBACK;

会话 1 的第一个操作为 排。会话 2 和 3 的操作都导致重复键 错误,他们都请求该行的共享锁。当会话 1 回滚,它释放对行和排队的排他锁 会话 2 和 3 的共享锁请求被授予。在此刻, 会话 2 和 3 死锁:两者都无法获得独占锁 该行是因为对方持有的共享锁。

我有一些问题:

1) 插入查询在它正在插入的行上获取一个排他锁。因此,假设 T1 在第 1 行插入,它将锁定第 1 行。现在当 T2 开始写入时,INNODB 将在执行之前评估查询并发现它将写入相同的 PK(i = 1 的行)让T2等待?或者它会开始执行 T2 并发现它给出了重复的密钥错误或 PK 违规。

2) 为什么 T2 和 T3 使用共享锁?共享锁如何在插入过程中出现?

【问题讨论】:

  • 添加一些SLEEP(10)函数调用来做一个测试用例;然后我们可以进一步讨论这个问题。包括SHOW ENGINE INNODB STATUS;的相关部分。

标签: mysql concurrency transactions locking


【解决方案1】:
  1. 对于会话 2 和 3:INNODB 在执行前评估查询并确定行是否已锁定(i = 1 的行),事务将等待解锁隐含行。

    当您在第一个会话中插入之后以及在会话 2 和 3 中运行插入之后执行 SHOW ENGINE INNODB STATUS

    ------------
    TRANSACTIONS
    ------------
    Trx id counter 2079155
    Purge done for trx's n:o < 2079150 undo n:o < 0 state: running but idle
    History list length 594
    LIST OF TRANSACTIONS FOR EACH SESSION:
    ---TRANSACTION 2079154, ACTIVE 21 sec inserting
    mysql tables in use 1, locked 1
    LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
    MySQL thread id 540, OS thread handle 0x7ff989386700, query id 1683 localhost root update
    INSERT INTO t1 VALUES(1)
    ------- TRX HAS BEEN WAITING 21 SEC FOR THIS LOCK TO BE GRANTED:
    RECORD LOCKS space id 4190 page no 3 n bits 72 index `PRIMARY` of table `temp`.`t1` trx id 2079154 lock mode S locks rec but not gap waiting
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
     0: len 4; hex 80000001; asc     ;;
     1: len 6; hex 0000001fb9af; asc       ;;
     2: len 7; hex 9c000001d30110; asc        ;;
    
    ------------------
    ---TRANSACTION 2079153, ACTIVE 43 sec inserting
    mysql tables in use 1, locked 1
    LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
    MySQL thread id 541, OS thread handle 0x7ff989355700, query id 1680 localhost root update
    INSERT INTO t1 VALUES(1)
    ------- TRX HAS BEEN WAITING 43 SEC FOR THIS LOCK TO BE GRANTED:
    RECORD LOCKS space id 4190 page no 3 n bits 72 index `PRIMARY` of table `temp`.`t1` trx id 2079153 lock mode S locks rec but not gap waiting
    Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
     0: len 4; hex 80000001; asc     ;;
     1: len 6; hex 0000001fb9af; asc       ;;
     2: len 7; hex 9c000001d30110; asc        ;;
    

    解锁行后(从会话 1 回滚)会话 2 将收到错误:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction,会话 3:Query OK, 1 row affected

  2. 会话 2 和 3 没有共享锁,它们在队列中获取一个,因为第一个会话的锁是独占的,需要等待。在这种情况下,会话 3 将得到一个并插入记录。

    共享锁:一种锁,它允许其他事务读取被锁定的对象,也可以获取它上面的其他共享锁,但是 不要给它写信。排他锁的反义词。

    独占锁:一种防止任何其他事务锁定同一行的锁。取决于事务隔离 级别,这种锁可能会阻止其他事务写入 到同一行,或者也可能阻止其他事务读取 同一行。默认 InnoDB 隔离级别,REPEATABLE READ, 通过允许事务读取的行来实现更高的并发性 拥有排他锁,这种技术称为一致性读。

【讨论】:

  • 按照逻辑,会话 1 解锁该行,会话 3 获得它,那么在这种情况下会话 2 必须获得锁定超时异常而不是死锁。
  • 超时异常将在配置的超时秒数后发生...但如果会话 1 完成回滚或提交则显示死锁错误。
  • 为什么会显示死锁?假设 Session1 完成,会话 3 获得锁并且既没有提交也没有回滚,那么 session2 应该超时,因为它无法获得锁。死锁意味着无论会话 2 和 3 一起运行,都永远不会获得锁。
  • @PriyanshGoel 根据InnoDB locking第二个会话是对X事务的IS意图锁,看到意图锁的表被认为是conflict...会话 3 在 IS 的请求队列中(共享意图)我使用的是 MySQL 5.6.19。
  • 但是当X锁被释放时,IS锁不会转化为X锁吗?
【解决方案2】:

1) 插入查询在它所在的行上获取一个排他锁 插入。因此,假设 T1 在第 1 行插入,它将锁定第 1 行。 现在当 T2 来写时,INNODB 是否会评估之前的查询 执行它并发现它将写入相同的PK(行 i = 1) 并让 T2 等待?或者它会开始执行 T2 和 发现它给出了重复的密钥错误或PK冲突。

我认为您正在简化术语/流程。在查询被解析之后执行之前,它需要获取必要的锁。正是在这一点上确定:

  • 会话 1 获得排他锁,因为它正在插入并且没有其他锁
  • 会话 2 和 3 排队等待共享锁,因为会话 1 已持有独占锁,并且会话 2 和 3 存在重复密钥错误

2) 为什么 T2 和 T3 使用共享锁?共享锁是怎么来的 在插入过程中进入图片?

根据上述,会话 2 和 3 排队等待共享锁,因为它们存在重复密钥错误。但是,当会话 1 删除密钥并释放独占锁时,现在会话 2 和 3 都获得了共享锁。此时两者都尝试获取排他锁以完成插入。但是,两者都不能,因为另一个已经持有共享锁。所以独占锁都没有被授予,他们死锁了。

【讨论】:

  • 对于您的第一点:它们不是在缓冲区中执行的吗?他们为什么要排队等待共享锁?他们不应该排队等待 IX 锁吗?
  • (1) 我不确定您的意思是“在缓冲区中执行” - 他们必须首先获得所需的锁。 (2) 对于很可能出现重复键错误的查询,IX/X 锁定是一种矫枉过正的做法。更有可能的是,这会产生比解决更多的死锁。在这里查看这个确切的讨论 - bugs.mysql.com/bug.php?id=35821
【解决方案3】:

问题2:

2) 为什么 T2 和 T3 使用共享锁?共享锁如何在插入过程中出现?


它需要对现有条目进行锁定,以便后续插入重复记录的尝试始终失败。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多