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
}
我的问题如下:
- 万一同时对这个函数进行了两次单独的调用,这两个调用都有在任何一点相交的“什么”数组,如果我理解正确,
REPEATABLE_READ
将防止我的数据被破坏,因为所有的一旦第一个更新开始执行,行将被锁定在 table 中,因此无论哪个函数调用更新被执行,都将被完全回滚。我是对的吗?根据 official documentation 上的示例,似乎会检查行的 where 条件并逐一锁定。是这样吗?如果是,是否有可能在并发调用该函数时,两个查询都会回滚?或者更糟的是,这里有没有可能发生死锁?
- 在这个具体示例中,我可以安全地将事务的隔离级别降低到
READ_COMMITED
,这也可以防止数据损坏,但不会在更新期间保留锁不受更新影响,我说得对吗?
- MariaDB 中手动
TRANSACTIONS
的锁保留是在创建这些锁的查询操作期间,还是在整个事务操作期间? (即,直到事务被回滚或提交?)
跟进问题
我是不是记错了,如果使用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
上有一个索引,这样它就可以只检查符合条件的行。我认为避免锁定额外的行而不是锁定它们然后释放锁会更好(如果只是一点点)。
我不认为事务隔离是性能优化的重要因素。在大多数情况下,选择索引来缩小检查行的范围是一个更好的策略。
我遇到了一个问题,我正在努力思考隔离级别。为了了解这些隔离级别,我阅读了 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
}
我的问题如下:
- 万一同时对这个函数进行了两次单独的调用,这两个调用都有在任何一点相交的“什么”数组,如果我理解正确,
REPEATABLE_READ
将防止我的数据被破坏,因为所有的一旦第一个更新开始执行,行将被锁定在 table 中,因此无论哪个函数调用更新被执行,都将被完全回滚。我是对的吗?根据 official documentation 上的示例,似乎会检查行的 where 条件并逐一锁定。是这样吗?如果是,是否有可能在并发调用该函数时,两个查询都会回滚?或者更糟的是,这里有没有可能发生死锁? - 在这个具体示例中,我可以安全地将事务的隔离级别降低到
READ_COMMITED
,这也可以防止数据损坏,但不会在更新期间保留锁不受更新影响,我说得对吗? - MariaDB 中手动
TRANSACTIONS
的锁保留是在创建这些锁的查询操作期间,还是在整个事务操作期间? (即,直到事务被回滚或提交?)
跟进问题
我是不是记错了,如果使用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
上有一个索引,这样它就可以只检查符合条件的行。我认为避免锁定额外的行而不是锁定它们然后释放锁会更好(如果只是一点点)。
我不认为事务隔离是性能优化的重要因素。在大多数情况下,选择索引来缩小检查行的范围是一个更好的策略。