也许您可以在这里为我介绍一些东西:
DB = MySQL 5.7
存储引擎:InnoDB
隔离级别:重复读取
以下表格:
---------------
| MyTable |
---------------
| PK | Concur |
---------------
| 3 | 2 |
---------------
我目前没有任何交易,我选择这样的记录
SELECT * FROM MyTable WHERE PK = 3
并将结果存储在我的程序中。
我现在开始数据库事务。
交易开始后,外部流程将Concur
= 3的记录的PK
从2增加到3。
我尚未从交易记录中的该表中再次读取。
我从交易内部发出以下查询:
UPDATE MyTable SET Concur = 3 WHERE PK = 3 AND Concur = 2
此操作将以0 records affected
成功。显然,它会根据我的交易开始后已更改的数据进行评估。
仍然在事务中,我随后查询:
SELECT * FROM MyTable WHERE PK = 3
这将返回PK = 3 and Concur = 2
的记录,这是交易之前的值。
SELECT
和UPDATE ... WHERE
的行为为何不同,我缺少什么?
我希望UPDATE ... WHERE
语句直接失败而不是成功导致0条记录失败,或者它在那里成功而受到1条记录的影响,然后在COMMIT
处失败,但这不是混搭。
这里有见识吗?
答案 0 :(得分:1)
https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html
一致的读取意味着InnoDB使用多版本在某个时间点向数据库呈现快照。该查询将看到在该时间点之前提交的事务所做的更改,而看不到以后或未提交的事务所做的更改。该规则的例外是查询可以看到同一事务中较早的语句所做的更改。此异常导致以下异常:如果更新表中的某些行,则SELECT会看到更新后的行的最新版本,但可能还会看到任何行的旧版本。如果其他会话同时更新了同一张表,则异常意味着您可能会以数据库中不存在的状态查看该表。
重要的条件是,如果您更改行,则一致的读取将被“刷新”,因此它包括您刚刚进行的更改。
但是,如果您执行UPDATE,则始终使用该行的最新版本,而不是事务的一致读取可以查看的版本。因此,如果另一个事务已经进行了更改,那么您的UPDATE可能没有任何效果。那就是你观察到的情况。
因此,您的交易发出了UPDATE,但没有更改该行。
也许这不是您希望InnoDB表现的方式,但是它仍然是如何表现的。
答案 1 :(得分:1)
用FOR UPDATE
表示您的意图:
BEGIN;
SELECT ... FOR UPDATE;
...
(no other thread can change that row until you `COMMIT` or `ROLLBACK`)
...
COMMIT;
另一方面...
BEGIN;
...
(At this point another thread modifies the row...)
...
SELECT ... FOR UPDATE; -- you are blocked until they COMMIT or ROLLBACK
答案 2 :(得分:0)
要受到“可重复读取”隔离级别的影响,您必须处于同一事务内。这意味着您的两个选择必须在事务内部,因此无论您在外部事务中更改数据库的任何位置,它们都不会受到影响。
因此,正如您所说,我目前没有任何交易,我选择SELECT * FROM MyTable WHERE PK = 3
之类的记录并将结果存储在我的程序中。仅发表一条声明。
之后,您可以使用更新开始交易。
您应该做的是
START TRANSACTION
SELECT * FROM MyTable WHERE PK = 3
------ START TRANSACTION
----- UPDATE MyTable SET Concur = 3 WHERE PK = 3 AND Concur = 2
------- END TRANSACTTION
-------
-------
SELECT * FROM MyTable WHERE PK = 3
END TRANSACTION
在左侧,您具有选择的事务,在右侧,您具有更新。这样,更新不会影响剩余的事务。