INSERT ... SELECT、InnoDB 和锁定

Posted

技术标签:

【中文标题】INSERT ... SELECT、InnoDB 和锁定【英文标题】:INSERT ... SELECT, InnoDB and locking 【发布时间】:2014-01-31 15:48:00 【问题描述】:

我在 mysql 5.5.34(在 Ubuntu 12.04 上)下使用 InnoDB 引擎遇到了以下行为。

在执行INSERT ... SELECT 语句时,一些意外的行似乎被锁定在正在读取的表中。

让我举个例子。假设两个表 table_sourcetable_dest 具有以下结构(特别注意索引):

CREATE TABLE table_source (
  id int(11) unsigned NOT NULL AUTO_INCREMENT,
  group_id int(11) NOT NULL,
  data text NOT NULL,
  created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY group_id_created (group_id,created)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

CREATE TABLE table_dest (
  id int(11) unsigned NOT NULL AUTO_INCREMENT,
  group_id int(11) NOT NULL,
  data text NOT NULL,
  created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY group_id_created (group_id,created)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

假设我现在执行以下事务:

BEGIN;
INSERT INTO table_dest 
    SELECT * FROM table_source WHERE group_id = 3 AND created < '2014-01-04';
....

那么源表似乎被INSERT 锁定为group_id 2:

INSERT INTO table_source (group_id, data, created) 
    VALUES (2, 'data', NOW()); --< This locks

以下是一些其他语句,以及它们是否锁定:

INSERT INTO table_source (group_id, data, created) 
    VALUES (3, 'data', NOW()); --< Does not lock

INSERT INTO table_source (group_id, data, created) 
    VALUES (1, 'data', NOW()); --< Does not lock

INSERT INTO table_source (group_id, data, created) 
    VALUES (3, 'data', '2014-01-01'); --< Does lock

有人能解释一下为什么会发生这种情况(我想这与间隙锁有关)吗?有没有办法避免这种情况(我仍然想保持REPEATABLE READ 隔离级别)?

【问题讨论】:

【参考方案1】:

没错。正在读取的表中的行被共享锁锁定(SELECT 隐含为LOCK IN SHARE MODE)。没有办法避免这种情况。这有点像您向系统提出的要求:复制所有符合条件的行。确保实际上所有符合条件的行并且该列表在该语句执行期间或之后不会立即更改的唯一方法是锁定这些行。

澄清一下为什么您无法使用INSERTgroup_id = 2

这与您的查询在KEY group_id_created (group_id, created) 上特别是WHERE group_id = 3 AND created &lt; '2014-01-04' 有关。为了搜索与group_id = 3 AND created &lt; '2014-01-04'匹配的所有行,索引将从超过该条件上限的第一行开始向后遍历,即(3, '2014-01-14')并继续直到找到与条件不匹配的行,因为created 没有下限将是第一行group_id &lt; 3 当然是group_id = 2

这意味着遇到group_id = 2 的第一行被锁定,这将是created 值最大的行。这将使INSERT 无法进入(2, MAX(created))(3, MIN(created)) 之间的“间隙”(当然不是正确的SQL,只是伪SQL),尽管这不是专门的“间隙锁”。

【讨论】:

谢谢,我知道 SELECT 语句被隐式转换为 SELECT ... LOCK IN SHARE MODE。但是,我仍然不太明白为什么要尝试插入带有 group_id = 2 锁的行(共享锁不应该只在 group_id = 3 的行上吗?) 对不起,我错过了你问题的一些意图。我刚刚添加了一些说明。 感谢您的解释,现在清楚多了。

以上是关于INSERT ... SELECT、InnoDB 和锁定的主要内容,如果未能解决你的问题,请参考以下文章

InnoDB RR隔离级别下INSERT SELECT两种死锁案例剖析

如何优化MySQL insert性能

MySQL insert语句锁分析

insert …select …带来的死锁问题

Mysql存储引擎中InnoDB与Myisam的区别

Mysql 存储引擎中InnoDB与MyISAM差别(网络整理)