使用MySQL的FOR UPDATE锁定时,究竟锁定了什么?

时间:2011-05-20 00:09:39

标签: mysql sql

这不是一个完整/正确的MySQL查询只有伪代码:

Select *
 from Notifications as n
 where n.date > (CurrentDate-10 days)
 limit by 1
 FOR UPDATE

http://dev.mysql.com/doc/refman/5.0/en/select.html州: 如果对使用页锁或行锁的存储引擎使用FOR UPDATE,则查询检查的行将被写入锁定,直到当前事务结束

这里只有一个由MySQL锁定的记录或者它必须扫描的所有记录才能找到单个记录吗?

6 个答案:

答案 0 :(得分:84)

为什么我们不尝试呢?

设置数据库

CREATE DATABASE so1;
USE so1;
CREATE TABLE notification (`id` BIGINT(20), `date` DATE, `text` TEXT) ENGINE=InnoDB;
INSERT INTO notification(id, `date`, `text`) values (1, '2011-05-01', 'Notification 1');
INSERT INTO notification(id, `date`, `text`) values (2, '2011-05-02', 'Notification 2');
INSERT INTO notification(id, `date`, `text`) values (3, '2011-05-03', 'Notification 3');
INSERT INTO notification(id, `date`, `text`) values (4, '2011-05-04', 'Notification 4');
INSERT INTO notification(id, `date`, `text`) values (5, '2011-05-05', 'Notification 5');

现在,启动两个数据库连接

连接1

BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;

连接2

BEGIN;

如果MySQL锁定所有行,则以下语句将被阻止。如果它只锁定它返回的行,则不应该阻塞。

SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;

确实它会阻止。

有趣的是,我们也无法添加可读的记录,即

INSERT INTO notification(id, `date`, `text`) values (6, '2011-05-06', 'Notification 6');

阻止!

在这一点上我无法确定MySQL是否会在锁定一定百分比的行时锁定整个表,或者在确保SELECT ... FOR UPDATE查询的结果时确实非常聪明在持有锁定时,永远不会被其他交易(使用INSERTUPDATEDELETE)更改。

答案 1 :(得分:20)

我知道这个问题很老了,但是我想分享一些相关测试的结果,我已经对索引列做了一些非常奇怪的结果。

表格结构:

CREATE TABLE `t1` (                       
  `id` int(11) NOT NULL AUTO_INCREMENT,                 
  `notid` int(11) DEFAULT NULL,                         
  PRIMARY KEY (`id`)                                    
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

使用INSERT INTO t1 (notid) VALUES (1), (2),..., (12)插入了12行。在连接1

BEGIN;    
SELECT * FROM t1 WHERE id=5 FOR UPDATE;

连接2 上,以下语句被阻止:

SELECT * FROM t1 WHERE id!=5 FOR UPDATE;
SELECT * FROM t1 WHERE id<5 FOR UPDATE;
SELECT * FROM t1 WHERE notid!=5 FOR UPDATE;
SELECT * FROM t1 WHERE notid<5 FOR UPDATE;
SELECT * FROM t1 WHERE id<=4 FOR UPDATE;

最奇怪的部分是SELECT * FROM t1 WHERE id>5 FOR UPDATE; 未被阻止,也不是

...
SELECT * FROM t1 WHERE id=3 FOR UPDATE;
SELECT * FROM t1 WHERE id=4 FOR UPDATE;
SELECT * FROM t1 WHERE id=6 FOR UPDATE;
SELECT * FROM t1 WHERE id=7 FOR UPDATE;
...

我还想指出,当来自连接1 的查询中的WHERE条件匹配时,整个表格似乎已被锁定一个非索引的行。例如,当连接1 执行SELECT * FROM t1 WHERE notid=5 FOR UPDATE时,来自连接2 FOR UPDATEUPDATE个查询的所有选择查询都会被阻止。< / p>

- 的修改 -

这是一个相当具体的情况,但它是我能找到的唯一表现出这种行为的情况:

连接1:

BEGIN;
SELECT *, @x:=@x+id AS counter FROM t1 CROSS JOIN (SELECT @x:=0) b HAVING counter>5 LIMIT 1 FOR UPDATE;
+----+-------+-------+---------+
| id | notid | @x:=0 | counter |
+----+-------+-------+---------+
|  3 |     3 |     0 |       9 |
+----+-------+-------+---------+
1 row in set (0.00 sec)

来自连接2

SELECT * FROM t1 WHERE id=2 FOR UPDATE;被屏蔽;

SELECT * FROM t1 WHERE id=4 FOR UPDATE; 被阻止。

答案 2 :(得分:9)

您发布的文档页面中的以下链接提供了有关locking的更多信息。在这个页面

  

SELECT ... FOR UPDATE读取最新的可用数据,在其读取的每一行上设置独占锁。因此,它设置搜索的SQL UPDATE将在行上设置的相同锁。

这似乎非常清楚,它必须扫描所有行。

答案 3 :(得分:1)

线程很旧,只想分享我对@Frans执行的上述测试的2美分

连接1

BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;

连接2

BEGIN;

SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;

肯定会阻止并发事务2,但原因是,事务1正在锁定整个表。下面解释了幕后发生的事情:

首先,InnoDB存储引擎的默认隔离级别是RR。在这种情况下,

1-当未在其中索引条件的列中使用时(如上所述):

引擎必须执行全表扫描以过滤出不符合条件的记录。 每行已被锁定在第一位。 MySQL可能会在稍后与where子句不匹配的那些记录上释放锁。这是对性能的优化,但是,这种行为违反了2PL约束。

事务2启动时,如已说明的那样,尽管仅存在一条与where子句匹配的记录(id = 2),但它仍需要为检索到的每一行获取X锁。最终,事务2将等待第一行(id = 1)的X锁,直到事务1提交或回滚为止。

2-当条件为主索引的列使用

只有满足条件的索引条目才被锁定。这就是为什么有人在评论中说某些测试未被阻止的原因。

3-当条件为索引但不唯一的列使用时

这种情况更加复杂。 1)索引条目已锁定。 2)一个X锁附加到相应的主索引。 3)在与搜索条件匹配的记录的前后,不存在的条目上附加了两个间隙锁。

答案 4 :(得分:1)

就像其他人提到的那样,SELECT ... FOR UPDATE锁定在默认隔离级别遇到的所有行。尝试将运行此查询的会话的隔离设置为READ COMMITTED,例如,在查询之前输入:set session transaction isolation level read committed;

答案 5 :(得分:0)

来自mysql官方文档:

  

锁定读取,UPDATE或DELETE通常会对在处理SQL语句时扫描的每个索引记录设置记录锁定。语句中是否存在排除行的条件都没关系。

对于Frans答案中讨论的情况,所有行均被锁定,因为在sql处理期间进行了表扫描:

  

如果您没有适合您的语句的索引,并且MySQL必须扫描整个表以处理该语句,则表的每一行都将被锁定,从而阻塞其他用户对表的所有插入。创建良好的索引很重要,这样您的查询就不必不必要地扫描很多行。

在此处查看最新文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html