MariaDB 事务隔离级别

MariaDB Transaction Isolation Levels

我遇到了一个问题,我正在努力思考隔离级别。为了了解这些隔离级别,我阅读了 MariaDB 网站上的文档。

InnoDB tables 使用的基本隔离级别规定为 REPEATABLE_READ

考虑以下问题。我有以下两个 tables 结构:

/** tableA **/
id INT AUTO_INCREMENT PRIMARY_KEY

/** tableB **/
id INT
claimedBy INT NULLABLE

还有一个函数,伪代码是这样的

/** should create a new row in "tableA", and update the rows from "tableB" which ids match the ones in the array from the input parameter, and that are null, to the id of this new row - in case the number of updated rows does not match the length of the input array, it should roll back everything **/
claim(array what) {
   - starts transaction
   - inserts a row into "tableA" and retrieve's it's id, storing it inside "variableA"
   - updates "claimedBy" to "variableA" on all rows from "tableB" that have "claimedBy" set to null and have "id" that is in "what"
   - counts the number of rows from "tableB", where "claimedBy" equals to "variableA"
     - if the count does not match the length of the "what" parameter, rolls back the transaction
     - if the count matches, commits the transaction 
}

我的问题如下:

跟进问题

我是不是记错了,如果使用READ_COMMITED隔离,下面两个并发调用可以同时执行(没有一个,等待另一个的锁被释放),但如果REPEATABLE_READ隔离被使用了?

/** Session #1 **/
claim(array(1,2,3));

/** Session #2 **/
claim(array(4,5,6));

在您描述的场景中,REPEATABLE-READ 和 READ-COMMITTED 之间的区别很小。

在这两种情况下获取相同的锁。锁一直保持到事务结束。

REPEATABLE-READ 查询也可能获取间隙锁以防止插入新行,如果这些行会更改某些 SELECT 查询的结果。 MySQL 手册更好地解释了间隙锁,它在 MariaDB 中的工作方式相同:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-gap-locks 无论如何,我认为这不会成为问题。

我认为您所描述的情况不会有陷入僵局的风险。您的 UPDATE 应该锁定所有检查的行。行没有被一一锁定;锁定请求是原子的。也就是说,如果由于另一个会话已锁定部分或全部行而无法锁定一组已检查的行,则新的锁定请求将等待。

一旦您的 UPDATE 成功(获取锁然后更新行),您的会话就会锁定它们并保持锁定直到事务结束。随后进行计数将仅引用锁定的行,因此另一个会话不可能滑入并导致死锁。

您可能没有在文档中注意到关于锁定的一个微妙之处:锁定 SQL 语句就像它们在 READ-COMMITTED 模式下的 运行 一样,即使您的交易是 REPEATABLE-READ.换句话说,即使 non-locking SELECT 查询不会读取该行的最新版本,也会在最近提交的行版本上获取锁。这让一些程序员感到惊讶。


回复您的评论:

我在这里的回答中写了一个 locking/nonlocking 奇怪行为的演示:

关于释放锁,是的,这是正确的,在 READ-COMMITTED 模式下,如果您的 UPDATE 没有对行进行任何净更改,则会释放锁。也就是说,如果您的更新将一列设置为它已有的值。但在您的情况下,您正在更改符合条件的行的值。您专门 select 用于 claimedBy 为 NULL 的行,并且您将该列设置为 non-NULL 值。

关于您的后续问题,您在 claimedBy 列上没有索引,因此您的查询必须至少检查所有行。在 READ-COMMITTED 模式下,它将能够非常迅速地释放与搜索条件不匹配的行的锁。但是最好在 claimedBy 上有一个索引,这样它就可以只检查符合条件的行。我认为避免锁定额外的行而不是锁定它们然后释放锁会更好(如果只是一点点)。

我不认为事务隔离是性能优化的重要因素。在大多数情况下,选择索引来缩小检查行的范围是一个更好的策略。