您的开发人员弄错了。您需要一个 SELECT ... FOR UPDATE
或行版本控制,而不是两者都需要。
试试看。打开三个MySQL会话(A)
,(B)
并(C)
连接到同一数据库。
在(C)
问题:
CREATE TABLE test(
id integer PRIMARY KEY,
data varchar(255) not null,
version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;
在这两个版本中(A)
,(B)
发布一个UPDATE
测试和设置行版本的,并更改winner
每个版本的文本,以便您可以查看哪个会话:
-- In (A):
BEGIN;
UPDATE test SET data = 'winnerA',
version = version + 1
WHERE id = 1 AND version = 0;
-- in (B):
BEGIN;
UPDATE test SET data = 'winnerB',
version = version + 1
WHERE id = 1 AND version = 0;
现在在中(C)
,UNLOCK TABLES;
释放锁。
(A)
并(B)
争夺行锁。其中之一将获胜并获得锁。另一个将锁在锁上。获得锁的获胜者将继续更改行。假设(A)
是获胜者,您现在可以使用来查看更改的行(仍未提交,因此其他事务不可见)SELECT * FROM test WHERE id = 1
。
现在COMMIT
在获胜者会议上,说(A)
。
(B)
将获得锁并继续更新。但是,版本不再匹配,因此它不会更改任何行,如行计数结果所报告。只有一个UPDATE
有作用,并且客户端应用程序可以清楚地看到哪个UPDATE
成功和哪个失败。无需进一步锁定。
在此处查看pastebin上的会话日志。我使用mysql --prompt="A> "
etc来轻松区分会话之间的区别。我按时间顺序复制并粘贴了交错输出,因此它不是完全原始的输出,并且可能会在复制和粘贴时出错。自己测试看看。
如果您没有添加行版本字段,那么您将需要SELECT ... FOR UPDATE
能够可靠地确保订购。
如果您考虑一下,如果您立即进行操作而不重新使用中的数据,或者使用行版本控制,则a SELECT ... FOR UPDATE
是完全多余的。无论如何,它将被锁定。如果其他人更新了读取和后续写入之间的行,则您的版本将不再匹配,因此更新将失败。这就是乐观锁定的工作方式。UPDATE
SELECT
UPDATE
目的SELECT ... FOR UPDATE
是:
- 管理锁顺序以避免死锁;和
- 要扩展行锁的范围,以便在您要从行中读取数据时,可以在应用程序中进行更改,然后基于原始行写新行,而不必使用
SERIALIZABLE
隔离或行版本控制。
您无需同时使用乐观锁定(行版本控制)和SELECT ... FOR UPDATE
。使用一个或另一个。