我在MySQL 5.5.34(在Ubuntu 12.04上)使用InnoDB引擎时遇到了以下行为。
执行INSERT ... SELECT
语句时,一些意外的行似乎被锁定在正在读取的表中。
让我举个例子。假设两个表table_source
和table_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
2:
group_id
的源表
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
隔离级别?)
答案 0 :(得分:4)
这是对的。正在读取的表中的行使用共享锁进行锁定(SELECT
隐式LOCK IN SHARE MODE
)。没有办法避免这种情况。这是您要求系统执行的操作:复制符合条件的所有行。确保这一事实的唯一方法实际上是所有符合条件的行,并且该列表在执行该语句期间或之后不会立即更改,就是锁定行。
作为对INSERT
与group_id = 2
:
WHERE group_id = 3 AND created < '2014-01-04'
的原因的澄清
这与您在KEY group_id_created (group_id, created)
上专门针对group_id = 3 AND created < '2014-01-04'
的查询有关。为了搜索匹配(3, '2014-01-14')
的所有行,索引将从第一行开始向后遍历超过该条件的上限,即created
并继续直到找到与该行不匹配的行条件,因为group_id < 3
没有下限,所以group_id = 2
当然是group_id = 2
的第一行。
这意味着created
遇到的第一行也已锁定,这将是具有最大INSERT
值的行。这将使(2, MAX(created))
无法进入(3, MIN(created))
和{{1}}之间的“差距”(当然不是正确的SQL,只是伪SQL),尽管这不是“缺口锁定”具体