【问题标题】:Unexpected Locking for Table with Primary Key & Unique Key使用主键和唯一键意外锁定表
【发布时间】:2011-06-18 16:19:59
【问题描述】:

对于同时具有主键和单独唯一索引的表上的事务,我遇到了 innodb 锁定问题。似乎如果 TX 使用唯一键删除记录,然后重新插入相同的记录,这将导致下一个键锁定而不是预期的记录锁定(因为键是唯一的)。请参阅下面的测试用例以及我希望拥有哪些锁的记录的细分:

DROP TABLE IF EXISTS foo; 
CREATE TABLE `foo` ( 
  `i` INT(11) NOT NULL, 
  `j` INT(11) DEFAULT NULL, 
  PRIMARY KEY (`i`), 
  UNIQUE KEY `jk` (`j`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; 
INSERT INTO foo VALUES (5,5), (8,8), (11,11); 

(注意:只需在 TX1 sql 之后运行 TX2 sql,在单独的连接中)

TX1

START TRANSACTION; 
DELETE FROM foo WHERE i=8; 

导致 i=8 上的排他锁(没有间隙锁,因为 i 是主键且唯一)

INSERT INTO foo VALUES(8,8); 

导致 i=8 和 j= 8 的排他锁,以及 i=6 和 i=7 以及 j=6 和 j=7 的共享意图锁

TX2

START TRANSACTION; 
INSERT INTO foo VALUES(7,7); 

导致 i=7 和 j=7 的排他锁,以及 i=6 和 j=6 上的共享意向锁

我希望 TX2 不会被 TX1 阻止,但事实确实如此。奇怪的是,阻塞似乎与 TX1 的插入有关。我这样说是因为如果 TX1 的插入语句在删除之后没有运行,则 TX2 的插入不会被阻塞。就好像 TX1 重新插入 (8,8) 会导致索引 j 上的下一个键锁定 (6,8]。

任何见解将不胜感激。

【问题讨论】:

    标签: mysql database performance locking innodb


    【解决方案1】:

    https://bugs.mysql.com/bug.php?id=68021

    这个错误问题回答了你的问题。

    这是 InnoDB 的设计缺陷,上游用于修复此问题以避免在 read-committed 隔离中 row_ins_scan_sec_index_for_duplicate 中的间隙锁定。但是它带来了另一个问题,修复导致二级索引唯一键违规,所以上游恢复了这个修复..

    【讨论】:

      【解决方案2】:

      问题似乎在于 InnoDB 索引很奇怪。

      主键(集群)是i,并且会有一个rowid与之关联。

      j(非集群)上的唯一键具有与索引中j 的值相关联的irowid

      i 的相同键值执行DELETE 后跟INSERT 应该会为主键(集群)生成即将到来的不同rowid,同样,即将到来的不同rowid 关联值为j(非集群)。

      这需要在 MVCC 机制中进行一些奇怪的内部锁定。

      您可能需要更改事务隔离级别以允许脏读(即没有可重复的读取)

      在会话中使用tx_isolation 变量玩一些游戏
      试试READ_COMMITTEDREAD_UNCOMMITTED

      Click here to see syntax for setting Isolation Level in a Session
      Click here to see how there was once a bug concerning this within a Session and the warning on how to use it carefully

      否则,只需在 /etc/my.cnf 中永久设置以下内容(示例)

      [mysqld]
      transaction_isolation=read-committed

      试试看!!!

      【讨论】:

        【解决方案3】:

        您遇到的问题是因为 MySQL 不只是锁定表行以获取您要插入的值,它会按顺序锁定前一个 id 和下一个 id 之间的所有可能值,因此,重用您的示例如下:

        DROP TABLE IF EXISTS foo;
        CREATE TABLE `foo` (
          `i` INT(11) NOT NULL,
          `j` INT(11) DEFAULT NULL,
          PRIMARY KEY (`i`),
          UNIQUE KEY `jk` (`j`) 
        ) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
        INSERT INTO foo VALUES (5,5), (8,8), (11,11);
        

        假设您从交易 TX1 开始:

        START TRANSACTION;
        REPLACE INTO foo VALUES(8,8);
        

        然后,如果您启动事务TX2,无论INSERTREPLACE 使用介于5 和11 之间的id,都将被锁定:

        START TRANSACTION;
        REPLACE INTO foo VALUES(11,11);
        

        看起来 MySQL 使用这种锁定来避免这里描述的“幻像问题”:http://dev.mysql.com/doc/refman/5.0/en/innodb-next-key-locking.html,MySQL 使用“下一个键锁定”,它结合了索引行锁定和间隙锁定,这对我们来说意味着它会在前一个和下一个 id 之间锁定很多可能的 id,并且也会锁定前一个和下一个 id。

        为避免这种情况,请尝试创建一个插入记录的服务器算法,以便插入到不同事务中的记录不会重叠,或者至少不要同时执行所有事务,这样TX 就不会必须互相等待。

        【讨论】:

        • 不,mySQL 不会为聚集索引插入发出间隙锁(它发出插入意图间隙锁,它不会阻止间隙中的并发插入)。这似乎与删除和随后插入聚集和非聚集索引(删除标记和随后的删除取消标记)导致双间隙锁(这是你注意到的)但似乎没有必要对我来说。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-01-27
        • 1970-01-01
        • 1970-01-01
        • 2014-08-24
        • 1970-01-01
        • 1970-01-01
        • 2015-04-18
        相关资源
        最近更新 更多