这不是一个完整/正确的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锁定的记录或者它必须扫描的所有记录才能找到单个记录吗?
答案 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
查询的结果时确实非常聪明在持有锁定时,永远不会被其他交易(使用INSERT
,UPDATE
或DELETE
)更改。
答案 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 UPDATE
和UPDATE
个查询的所有选择查询都会被阻止。< / 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