为啥在可重复读取中会发生写入偏斜?
Posted
技术标签:
【中文标题】为啥在可重复读取中会发生写入偏斜?【英文标题】:Why write skew can happen in Repeatable reads?为什么在可重复读取中会发生写入偏斜? 【发布时间】:2018-07-03 05:05:31 【问题描述】:Wiki 说;
可重复阅读: 在这个隔离级别,一个基于锁的 并发控制 DBMS 实现保持读写锁 (获取所选数据)直到交易结束。然而, 范围锁不受管理,因此可能发生幻读。
在此隔离级别可能会出现写入偏差,这种现象会导致两个 允许两个不同的写入表中的相同列 作家(之前已经阅读过他们正在更新的专栏), 导致该列的数据是两者的混合 交易。
我很好奇为什么write skew
会发生在Repeatable reads
中?
它说它将保持读写锁直到事务结束并且write skew
发生在previously read the columns they are updating
时,那么当读锁被锁定时如何锁定写锁?
【问题讨论】:
【参考方案1】:可重复读隔离级别保证每个事务都会从数据库的consistent snapshot 中读取。换句话说,在同一事务中检索两次的行总是具有相同的值。
许多数据库(如 Postgres、可重复读取隔离级别的 SQLServer)可以检测到lost update(写入倾斜的一种特殊情况),但其他数据库则不能。 (即:mysql 中的 InnoDB 引擎)
我们回来写歪斜现象问题。在可重复读隔离中,存在大多数数据库引擎无法检测到的情况。一种情况是 2 个并发事务 修改 2 个不同的对象 并产生竞争条件。
我以Designing Data-Intensive Application 书中的一个例子为例。这是场景:
您正在为医生编写一个应用程序来管理他们的待命情况 在医院轮班。医院通常会尝试有几个 随时待命的医生,但绝对必须至少有 一名随叫随到的医生。医生可以放弃轮班(例如,如果他们 自己生病了),前提是至少有一名同事留在 调用那个班次
下一个有趣的问题是我们如何在数据库下实现它。这是伪代码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 的记录。处于可重复读取隔离级别的数据库无法知道并检查是否违反了条件(总医生> = 2)。发生了写歪斜现象。
为了解决这个问题,提出了2种方法:
-
锁定所有手动调用的记录。所以 Bob 或 Alice 都会等到其他人完成交易。
这是一些使用SELECT .. FOR UPDATE
查询的伪代码。
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、Postgres T-SQL 都提供序列化隔离级别。
【讨论】:
感谢您提供非常清晰的解释。我有一点意见。您说 MySQL Serializeable IL 默认情况下可能会阻止 Write Skew 现象,对吧?MySQL manual 在 Serializeable ILInnoDB implicitly converts all plain SELECT statements to SELECT ... FOR SHARE
中声明。我认为这意味着即使在 MySQL 中使用 Serializeable IL,纯 2PL 共享锁也无法防止所述场景中的写入倾斜,因为两个事务仍然可以并行读取行并评估 if 语句
继续我之前的评论:实际上你写的一切都是正确的。在 MySQL 中的 Serializable 隔离级别的情况下,假设事务语句的交错与您的示例中相同,两个事务都将在每个医生记录上获取 S
锁定,然后两者都将评估 current_on_call >= 2
语句导致 true
,然后两者事务将被阻止,因为执行Update
语句他们需要X
锁,但他们无法获取它,因为其他事务持有S
锁定所有医生记录。结果 - 死锁。一项事务将被回滚 - 防止写入倾斜
我写了一篇博客来测试 MariaDB/MySQL 中的隔离级别。 medium.com/@huynhquangthao/…本次测试的重要部分是可重复读取和可序列化的测试。用 MySQL 测试后,我看到: - 可重复读隔离可以检测幻读。但是您仍然可以更新由不同事务创建的记录。 - 可序列化隔离可以防止这种行为:不同的事务不能修改可能改变其他事务结果的数据。
这种解释似乎很奇怪——可重复的读取隔离不应该为在事务中执行的选择提供相同的结果吗?那么“current_on_call”的值不应该改变吗?我对 SQL Server 的基本测试表明更新被锁定在可重复读取中。
"在同一事务中检索两次的行总是具有相似的值。" - 相似的?不一样? (假设事务中没有 UPDATE - 但它可能根本不相似)以上是关于为啥在可重复读取中会发生写入偏斜?的主要内容,如果未能解决你的问题,请参考以下文章