数据库事务:“写偏斜”和“丢失更新”之间的区别

Mje*_*OsX 6 sql database transactions snapshot-isolation

有人可以向我解释一下数据库事务理论中“写偏斜”和“丢失更新”之间的确切区别吗?有人可以举个例子吗?

Joe*_*ick 6

非正式地,丢失更新写入倾斜是并发写入事务可能相互干扰的方式。

当在基于陈旧数据的事务中进行更新时,就会发生写倾斜陈旧数据是事务读取的值,该值由于并发事务的后续提交写入而变得陈旧。

当一个事务写入的提交值被并发事务的后续提交写入覆盖时,就会发生丢失更新。实际上,丢失更新确实是写倾斜的特例;更新应用于已过时的数据。

考虑零售商店的数据库维护库存表的情况。数据库没有实现事务隔离

库存表中有一个“产品编号”栏和“INSTOCK”列数了当前库存特定产品项目的数量。每次购买(交易)都会根据购买的商品数量减少“InStock”值。

想象一下,商店有两台电动剃须刀(特定型号)库存。

两名顾客每人同时购买其中一款剃须刀。

每个并发购买(交易)从剃须刀的“InStock”记录中读取相同的值(两个)。每个事务都会递减“InStock”计数器并将更新的值(一)提交到数据库。在两个并发事务都提交后,计数器将错误地指示剃须刀仍有库存(仅剩一件)。

其中的更新丢失

假设数据库实现了快照隔离(具有丢失更新检测),在这种情况下不会发生丢失更新。这是因为快照隔离检测何时发生丢失更新。事务提交数据后,尝试提交相同数据写入的并发事务将被数据库中止。在我们的示例中,事务被中止的进程启动一个新事务以重新读取“InStock”列,递减它,并提交更新的值。假设没有其他冲突,此更新记录的尝试成功提交并且“Instock”列包含(正确)值零。

事务隔离是一个很深的话题

此外假设数据库在InventoryHistory表中记录库存历史。该InventoryHistory表具有列“时间戳”,“产品编号”和“INSTOCK”(剩余购买)。按照设计,对InventoryHistory表的更新是购买交易中的最后一个操作。在两个事务提交后,相应的InventoryHistory记录将分别反映“Instock”值为 1——这是不正确的,因为其中一个记录应反映“Instock”值为零。不正确的InventoryHistory记录是write skew 的一个例子。

在这种情况下快照隔离并没有阻止异常的数据被写入到数据库中,因为没有更新丢失。相反,写入的数据是异常的,因为事务读取的值已经过时——这就是写倾斜快照隔离不能防止写倾斜。为了防止写倾斜,数据库必须实现可序列化隔离。

阅读这篇文章,对写倾斜、可序列化和快照隔离进行严格的讨论。


小智 5

  • 丢失更新是指两个事务读取同一行并对其进行更新,但第一个提交的更新被第二个提交的更新覆盖。

  • 写倾斜是指两个事务读取相同的数据来插入、更新或删除行,但提交的数据不一致。

首先,这是下面丢失更新的示例。有一个带有,和product的表格,如下所示。idnamestock

product桌子:

ID 姓名 库存
1 苹果 10
2 橙子 20

以下这些步骤显示丢失的更新。*当顾客购买产品时,产品库存减少:

流动 交易1(T1) 交易2(T2) 解释
步骤1 BEGIN; T1开始。
第2步 BEGIN; T2开始。
步骤3 SELECT stock FROM product WHERE id = 2;

20
T1 读取20内容稍后更新为,13因为客户购买了 7 个橙子。
步骤4 SELECT stock FROM product WHERE id = 2;

20
T2 读取20稍后更新为,16因为客户购买了 4 个橙子。
步骤5 UPDATE product SET stock = 13 WHERE id = 2; T1 更新2013.
步骤6 COMMIT; T1 提交。
步骤7 UPDATE product SET stock = 16 WHERE id = 2; T2 更新1316T1 提交后。
步骤8 COMMIT; T2 提交。

*发生丢失更新。

其次,这是写倾斜的例子。有一个带有,和doctor的表格,如下所示。idnameon_call

doctor桌子:

ID 姓名 随传随到
1 约翰 真的
2 丽莎 真的

下面的这些步骤显示了写入偏差。*至少必须有一名医生待命,但最终没有医生待命:

流动 约翰交易 (JT) 丽莎的交易(LT) 解释
步骤1 BEGIN; JT开始。
第2步 BEGIN; LT 开始。
步骤3 SELECT count(*) FROM doctor WHERE on_call = True;

2
JT 阅读2以便约翰可以休息一下。
步骤4 SELECT count(*) FROM doctor WHERE on_call = True;

2
LT 读到2这样丽莎就可以休息了。
步骤5 UPDATE doctor SET on_call = False WHERE id = 1; JT 更新True意味着False约翰休息了。
步骤6 COMMIT; JT 承诺。
步骤7 UPDATE doctor SET on_call = False WHERE id = 2; LT 更新True表示False丽莎正在休息。
步骤8 COMMIT; LT 承诺。

约翰和丽莎都休息了。

*发生写入倾斜。

接下来,这也是写倾斜的例子。有一个带有,和event的表格,如下所示。idnameuser

event桌子:

ID 姓名 用户
1 制作寿司 约翰
2 制作寿司 汤姆

下面的这些步骤显示了写入偏差。*只有3位用户可以参加“制作寿司”活动,最终有4位用户参加:

流动 交易 (JT) 交易(LT) 解释
步骤1 BEGIN; T1开始。
第2步 BEGIN; T2开始。
步骤3 SELECT count(*) FROM event WHERE name = 'Make Sushi';

2
T1 读取2,因此只有一个用户可以加入它。
步骤4 SELECT count(*) FROM event WHERE name = 'Make Sushi';

2
T2 读取2后只有一名用户可以加入。
步骤5 INSERT INTO event values ('Make Sushi', 'Lisa'); T1 插入Lisaevent
步骤6 COMMIT; T1 提交。
步骤7 INSERT INTO event values ('Make Sushi', 'Kai'); T2 插入Kaievent
步骤8 COMMIT; T2 提交。

4 位用户加入。

*发生写入倾斜。

下表是我在MySQLPostgreSQL中每个隔离级别的丢失更新写入偏差的实验结果。*表示发生,表示不发生,表示SELECT FOR UPDATEYesNoSFU

MySQL

隔离级别 丢失更新 SFU 丢失更新 写入偏差 使用 SFU 写入偏差
未提交的阅读 是的 是的
已提交读 是的 是的
可重复读取 是的 是的
可串行化

PostgreSQL

隔离级别 丢失更新 SFU 丢失更新 写入偏差 使用 SFU 写入偏差
未提交的阅读 是的 是的
已提交读 是的 是的
可重复读取 是的
可串行化