Mysql 为什么MariaDB上的REPETEABLE_READ不能生成幻影读取?

Mysql 为什么MariaDB上的REPETEABLE_READ不能生成幻影读取?,mysql,spring,jdbc,mariadb,isolation-level,Mysql,Spring,Jdbc,Mariadb,Isolation Level,在我的测试中,我已经看到,当使用MariaDB时,在REPETEABLE_READ隔离中执行相同的查询不会产生幻影读取,而应该是这样 例如: 我在银行账户表中有两行: ID | OWNER | MONEY ------------------------ 1 | John | 1000 2 | Louis | 2000 预期流量应如下所示: THREAD 1 (REPETEABLE_READ) THREAD 2 (READ_UNCOMMIT

在我的测试中,我已经看到,当使用MariaDB时,在REPETEABLE_READ隔离中执行相同的查询不会产生幻影读取,而应该是这样

例如:

我在银行账户表中有两行:

  ID |  OWNER | MONEY
------------------------
  1  |  John  | 1000
  2  |  Louis | 2000
预期流量应如下所示:

THREAD 1 (REPETEABLE_READ)                THREAD 2 (READ_UNCOMMITED)
  |                                         |
findAll()->[1|John|1000,2|Louis|2000]       |          
  |                                         |
  |                                       updateAccount(1, +100)
  |                                       createAccount("Charles", 3000)                 
  |                                       flush()
  |                                         |
  |                                         commitTx()
  |                                         |_
  |                                         
findAll()->[1|John|1000,2|Louis|2000,       
  |         3|Charles|3000]                 
  |                                         
  |                                         
 commitTx()                               
  |_                                        
综上所述,Thread2.0之后,3000;Thread1将搜索所有行并获取

  ID |  OWNER   | MONEY
------------------------
  1  |  John    | 1000
  2  |  Louis   | 2000
  3  |  Charles | 3000
Thread1受到保护,不受未限制更改的影响,可以看到[1,John,1000],而不是[1,John,1100],但它应该看到新插入的行

但是,Thread1在第二个findAll中检索的结果与第一个findAll中检索的结果完全相同:

  ID |  OWNER   | MONEY
------------------------
  1  |  John    | 1000
  3  |  Charles | 3000
它没有幻觉读数。为什么??

这是Thread1执行的代码:

@Transactional(readOnly=true, isolation=Isolation.REPEATABLE_READ)
@Override
public Iterable<BankAccount> findAllTwiceRepeteableRead(){
    printIsolationLevel();
    Iterable<BankAccount> accounts = baDao.findAll();
    logger.info("findAllTwiceRepeteableRead() 1 -> {}", accounts);
    //PAUSE HERE
    ...
}
然后Thread1恢复:

//PAUSE HERE
...
Iterable<BankAccount> accounts = baDao.findAll();
logger.info("findAllTwiceRepeteableRead() 2 -> {}", accounts);

您所描述的实际行为实际上是可重复读取的正确行为。您期望的行为可以通过使用read_committed来实现

正如mariadb关于bolding的文件所说,bolding是我的:

与读提交隔离有一个重要区别 级别:同一事务中的所有一致读取读取 由第一次读取建立的快照

在线程1中,返回John和Louis的第一个FindAll调用建立了快照。第二个FindAll只是使用了相同的快照

Percona的一篇博客文章进一步证实了这一点:

在可重复读取中,“读取视图”trx\u no没有看到trx\u id>=ABC, 在事务开始时创建,并且 在Oracle术语中,read view consistent snapshot为 交易的持续时间。如果在凌晨5点执行SELECT语句, 下午5点,当你运行相同的程序时,以开放式交易返回 选择,则您将看到与在上看到的完全相同的结果集 早上5点。这称为MVCC多版本并发控制和 它是使用行版本控制和撤消信息完成的

更新

注意:以下引用来自MySQL文档。然而,由于这些参考文献涉及innodb存储引擎,我坚信它们也适用于mariadb的innodb存储引擎

因此,在innodb存储引擎中,在可重复读取隔离级别下,非锁定在从第一次读取建立的快照读取的同一事务中进行选择。无论在并发提交的事务中插入/更新/删除了多少条记录,读取都是一致的。句号

这是OP在问题中描述的场景。这意味着可重复读取隔离级别中的非锁定读取将无法生成幻象读取,对吗?嗯,不完全是这样

正如MySQL上的文档所述:

数据库状态的快照应用于中的SELECT语句 事务,不一定是DML语句。如果插入或 修改一些行,然后提交该事务、删除或更新 从另一个并发可重复读取事务发出的语句 可能会影响那些刚刚提交的行,即使会话可能 不要质疑他们。如果事务确实更新或删除提交的行 通过一个不同的事务,这些更改对用户是可见的 当前交易。例如,您可能会遇到如下情况 以下是:

SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: no rows match. DELETE FROM t1 WHERE c1 = 'xyz';
-- Deletes several rows recently committed by other transaction.

SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: no rows match. UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: another txn just committed 10 rows with 'abc' values. 
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.
总而言之:如果将innodb与可重复读取隔离模式一起使用,那么如果并发提交事务中的数据修改语句与当前事务中的数据修改语句交互,则可能会发生幻象读取

维基百科关于隔离级别的链接文章描述了一个通用的理论模型。您始终需要阅读实际的产品手册,了解特定功能是如何实现的,因为可能存在差异

在维基百科的文章中,只有锁被描述为防止幻象读取的一种手段。但是,在大多数情况下,innodb使用创建快照来防止幻象读取,因此不需要依赖锁

Transaction 1                             Transaction 2
/* Query 1 */
SELECT * FROM users
WHERE age BETWEEN 10 AND 30;
                                          /* Query 2 */
                                          INSERT INTO users(id,name,age) VALUES ( 3, 'Bob', 27 );
                                          COMMIT;
/* Query 1 */
SELECT * FROM users
WHERE age BETWEEN 10 AND 30;
COMMIT;
SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: no rows match. DELETE FROM t1 WHERE c1 = 'xyz';
-- Deletes several rows recently committed by other transaction.

SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: no rows match. UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: another txn just committed 10 rows with 'abc' values. 
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.