Database 为什么在可重复读取中会发生写倾斜?

Database 为什么在可重复读取中会发生写倾斜?,database,isolation-level,acid,Database,Isolation Level,Acid,说 可重复读取: 在此隔离级别中,基于锁的 并发控制DBMS实现保持读写锁 (根据所选数据获取)直到事务结束。然而, 范围锁不受管理,因此可能会发生幻象读取 在这个隔离级别上,写倾斜是可能的,在这种情况下 允许两个不同的用户写入表中的同一列 作者(以前阅读过他们正在更新的专栏的人), 导致列包含这两种数据的混合 交易 我很好奇为什么可重复读取中会发生写倾斜? 它表示它将保持读写锁定,直到事务结束,并且当以前读取它们正在更新的列时发生写倾斜,那么,当读锁被锁定时,如何才能锁定写锁呢?可重复读隔离级

可重复读取:
在此隔离级别中,基于锁的 并发控制DBMS实现保持读写锁 (根据所选数据获取)直到事务结束。然而, 范围锁不受管理,因此可能会发生幻象读取

在这个隔离级别上,写倾斜是可能的,在这种情况下 允许两个不同的用户写入表中的同一列 作者(以前阅读过他们正在更新的专栏的人), 导致列包含这两种数据的混合 交易

我很好奇为什么
可重复读取
中会发生
写倾斜

它表示它将保持读写锁定,直到事务结束,并且当
以前读取它们正在更新的列时发生
写倾斜
,那么,当读锁被锁定时,如何才能锁定写锁呢?

可重复读隔离级别保证每个事务都将从数据库的外部读取。换句话说,在同一事务中检索一行两次时,该行的值总是相似的

许多数据库,如Postgres、SQLServer在可重复读取隔离级别可以检测到(写倾斜的一种特殊情况),但其他数据库则不能。(即:MySQL中的InnoDB引擎)

我们回到写歪斜现象的问题上来。存在大多数数据库引擎无法在可重复读取隔离中检测到的情况。一种情况是当2个并发事务修改2个不同的对象并创建竞态条件时

我从书中举了一个例子。以下是场景:

您正在为医生编写一份管理其随叫随到服务的应用程序 在医院轮班。这家医院通常试着做几次手术 医生随时待命,但绝对必须至少有 一位随时待命的医生。医生可以放弃轮班(例如,如果 如果至少有一名同事仍在工作,则 叫那个班次

下一个有趣的问题是我们如何在数据库下实现这一点。以下是伪代码SQL代码:

BEGIN TRANSACTION;
    SELECT * FROM doctors
        WHERE on_call = true
        AND shift_id = 1234;
    if (current_on_call >= 2) {
        UPDATE doctors
        SET on_call = false WHERE name = 'Alice' AND shift_id = 1234;
    }
COMMIT;  
以下是示例:

如上图所示,我们看到Bob和Alice在SQL代码之上并发运行。但是Bob和Alice修改了不同的数据,Bob修改了Bob的记录,Alice修改了Alice的记录。处于可重复读取隔离级别的数据库无法知道和检查已违反的条件(total doctor>=2)。出现了写倾斜现象

为了解决这个问题,提出了两种方法:

  • 锁定手动调用的所有记录。所以Bob或Alice将等待其他人完成事务
  • 下面是使用
    SELECT。。用于更新
    查询

    BEGIN TRANSACTION;
        SELECT * FROM doctors
            WHERE on_call = true
            AND shift_id = 1234 FOR UPDATE; // important here: locks all records that satisfied requirements.
    
        if (current_on_call >= 2) {
            UPDATE doctors
            SET on_call = false WHERE name = 'Alice' AND shift_id = 1234;
        }
      COMMIT;  
    
  • 使用更严格的隔离级别。两者都提供序列化隔离级别

  • 谢谢你的解释。我有一句话。您说过MySQL可序列化IL在默认情况下可能会防止写倾斜现象,对吗?MySQL声明在可序列化IL
    InnoDB中隐式地将所有普通SELECT语句转换为SELECT。。。用于共享
    。我认为这意味着,即使MySQL中有可序列化的IL,纯2PL共享锁也无法防止所述场景中的写倾斜,因为两个事务仍然可以读取行并计算并行中的if语句。继续我前面的评论:实际上,您写的一切都是正确的。在MySQL中使用可序列化隔离级别的情况下,假设事务语句的交错与示例中相同,两个事务都将在每个医生记录上获取
    S
    锁,然后两个事务都将计算
    current_on_call>=2
    语句,得到
    true
    ,然后,两个事务都将被阻止,因为它们需要执行
    Update
    语句
    X
    lock,但由于另一个事务在所有医生记录上都持有
    S
    锁,因此无法获取该语句。结果-死锁。一个事务将被回滚-Write Skew prevented我已经写了一个博客来测试MariaDB/MySQL中的隔离级别。本测试的重要部分是可重复读取和可序列化的测试。在使用MySQL进行测试后,我发现:-可重复读取隔离可以检测幻象读取。但您仍然可以更新由不同事务创建的记录。-可序列化隔离可以防止这种行为:不同的事务无法修改可能会更改其他事务结果的数据。这种解释似乎很奇怪-可重复读取隔离不应该为事务中执行的select提供相同的结果吗?“当前通话”的值不应该改变吗?我用SQL Server进行的基本测试表明,更新被锁定在可重复读取中。我想我已经找到了答案——这取决于可重复读取隔离的实现细节。例如,Postgres是基于MVCC的,因此它确实不能防止所描述的写倾斜,但SQL Server是基于锁定(2PL)的,所以就我所见,它确实可以防止写倾斜。