MariaDB `FOR UPDATE` 子句是否正常工作?
Does MariaDB `FOR UPDATE` clause works fine?
我试图摆脱写入偏差并尝试使用可序列化隔离级别,但我却遇到了死锁。我发现可序列化隔离级别会导致死锁,因为 this:
This level is like REPEATABLE READ, but InnoDB implicitly converts all plain SELECT statements to SELECT ... LOCK IN SHARE MODE
所以,我尝试像这样使用 REPEATABLE READ
(没有 id 为“some_id”的行):
-- connection 1:
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
select * from some_table where id="some_id" for update;
-- connection 2:
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
select * from some_table where id="some_id" for update;
insert into some_table values("some_id");
因此,我在连接 2 中收到此消息:Lock wait timeout exceeded; try restarting transaction
。
The FOR UPDATE clause of SELECT applies only when autocommit is set to 0 or the SELECT is enclosed in a transaction. A lock is acquired on the rows, and other transactions are prevented from writing the rows, acquire locks, and from reading them (unless their isolation level is READ UNCOMMITTED).
但看起来它并没有阻止其他事务获取锁或读取行。
我做错了什么?
这个 post 回答了我的问题:How do I lock on an InnoDB row that doesn't exist yet?
While the answer above is true in that a SELECT ... FOR UPDATE will
prevent concurrent sessions / transactions from inserting the same
record, that is not the full truth. I am currently fighting with the
same problem and have come to the conclusion that the SELECT ... FOR
UPDATE is nearly useless in that situation for the following reason:
A concurrent transaction / session can also do a SELECT ... FOR UPDATE
on the very same record / index value, and MySQL will happily accept
that immediately (non-blocking) and without throwing errors. Of
course, as soon as the other session has done that, your session as
well can't insert the record any more. Nor your nor the other session
/ transaction get any information about the situation and think they
can safely insert the record until they actually try to do so. Trying
to insert then either leads to a deadlock or to a duplicate key error,
depending on circumstances.
In other words, SELECT ... FOR UPDATE prevents other sessions from
inserting the respective record(s), BUT even if you do a SELECT ...
FOR UPDATE and the respective record is not found, chances are that
you can't actually insert that record. IMHO, that renders the "first
query, then insert" method useless.
The cause of the problem is that MySQL does not offer any method to
really lock non-existent records. Two concurrent sessions /
transactions can lock non-existent records "FOR UPDATE" at the same
time, a thing which really should not be possible and which makes
development significantly more difficult.
The only way to work around this seems to be using semaphore tables or
locking the whole table when inserting. Please refer to the MySQL
documentation for further reference on locking whole tables or using
semaphore tables.
Just my 2 cents ...
我没有先找到问题,所以我不会删除这个问题(我知道,实际上是重复的),以便其他人更容易搜索。
因此,我创建了唯一索引,并将其与可重复读取隔离级别(没有“FOR UPDATE”)一起使用。它允许我检测并发插入并在我的代码中处理这种情况(在这种情况下我决定 return 一个错误)。
我在尝试回答这个问题时找到的相关资源:
https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-example.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
https://mariadb.com/kb/en/set-transaction/
https://mariadb.com/kb/en/lock-in-share-mode/
https://mariadb.com/kb/en/for-update/
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
How to avoid MySQL 'Deadlock found when trying to get lock; try restarting transaction'
我试图摆脱写入偏差并尝试使用可序列化隔离级别,但我却遇到了死锁。我发现可序列化隔离级别会导致死锁,因为 this:
This level is like REPEATABLE READ, but InnoDB implicitly converts all plain SELECT statements to SELECT ... LOCK IN SHARE MODE
所以,我尝试像这样使用 REPEATABLE READ
(没有 id 为“some_id”的行):
-- connection 1:
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
select * from some_table where id="some_id" for update;
-- connection 2:
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
select * from some_table where id="some_id" for update;
insert into some_table values("some_id");
因此,我在连接 2 中收到此消息:Lock wait timeout exceeded; try restarting transaction
。
The FOR UPDATE clause of SELECT applies only when autocommit is set to 0 or the SELECT is enclosed in a transaction. A lock is acquired on the rows, and other transactions are prevented from writing the rows, acquire locks, and from reading them (unless their isolation level is READ UNCOMMITTED).
但看起来它并没有阻止其他事务获取锁或读取行。
我做错了什么?
这个 post 回答了我的问题:How do I lock on an InnoDB row that doesn't exist yet?
While the answer above is true in that a SELECT ... FOR UPDATE will prevent concurrent sessions / transactions from inserting the same record, that is not the full truth. I am currently fighting with the same problem and have come to the conclusion that the SELECT ... FOR UPDATE is nearly useless in that situation for the following reason:
A concurrent transaction / session can also do a SELECT ... FOR UPDATE on the very same record / index value, and MySQL will happily accept that immediately (non-blocking) and without throwing errors. Of course, as soon as the other session has done that, your session as well can't insert the record any more. Nor your nor the other session / transaction get any information about the situation and think they can safely insert the record until they actually try to do so. Trying to insert then either leads to a deadlock or to a duplicate key error, depending on circumstances.
In other words, SELECT ... FOR UPDATE prevents other sessions from inserting the respective record(s), BUT even if you do a SELECT ... FOR UPDATE and the respective record is not found, chances are that you can't actually insert that record. IMHO, that renders the "first query, then insert" method useless.
The cause of the problem is that MySQL does not offer any method to really lock non-existent records. Two concurrent sessions / transactions can lock non-existent records "FOR UPDATE" at the same time, a thing which really should not be possible and which makes development significantly more difficult.
The only way to work around this seems to be using semaphore tables or locking the whole table when inserting. Please refer to the MySQL documentation for further reference on locking whole tables or using semaphore tables.
Just my 2 cents ...
我没有先找到问题,所以我不会删除这个问题(我知道,实际上是重复的),以便其他人更容易搜索。
因此,我创建了唯一索引,并将其与可重复读取隔离级别(没有“FOR UPDATE”)一起使用。它允许我检测并发插入并在我的代码中处理这种情况(在这种情况下我决定 return 一个错误)。
我在尝试回答这个问题时找到的相关资源:
https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-example.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
https://mariadb.com/kb/en/set-transaction/
https://mariadb.com/kb/en/lock-in-share-mode/
https://mariadb.com/kb/en/for-update/
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
How to avoid MySQL 'Deadlock found when trying to get lock; try restarting transaction'