我在MySQL 5.5.34(在Ubuntu 12.04上)下使用InnoDB引擎遇到以下行为。
在执行INSERT ... SELECT语句时,某些意外行似乎被锁定在正在读取的表中。
INSERT ... SELECT
让我举个例子吧。假设两个表table_source,并table_dest具有下列结构(尤其要注意索引):
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以group_id2:
INSERT
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隔离级别)?
REPEATABLE READ
没错 要读取的表中的行被共享锁锁定(SELECT隐式为LOCK IN SHARE MODE)。没有办法避免这种情况。这就是您要系统询问的内容:复制与条件匹配的所有行。确保实际上所有与条件匹配的行以及该列表在执行该语句期间或之后不更改的唯一方法是锁定行。
SELECT
LOCK IN SHARE MODE
为了澄清您为何无法INSERT使用以下内容group_id = 2:
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。
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是 也 被锁定,这将是具有最大的行created值。尽管这不是专门的“间隙锁”,但这将使得不可能INSERT在(2, MAX(created))和之间插入“间隙” (3, MIN(created))(当然不是正确的SQL,只是伪SQL)。
(2, MAX(created))
(3, MIN(created))