Mje*_*OsX 6 sql database transactions snapshot-isolation
有人可以向我解释一下数据库事务理论中“写偏斜”和“丢失更新”之间的确切区别吗?有人可以举个例子吗?
非正式地,丢失更新和写入倾斜是并发写入事务可能相互干扰的方式。
当在基于陈旧数据的事务中进行更新时,就会发生写倾斜。 陈旧数据是事务读取的值,该值由于并发事务的后续提交写入而变得陈旧。
当一个事务写入的提交值被并发事务的后续提交写入覆盖时,就会发生丢失更新。实际上,丢失更新确实是写倾斜的特例;更新应用于已过时的数据。
考虑零售商店的数据库维护库存表的情况。数据库没有实现事务隔离。
在库存表中有一个“产品编号”栏和“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 更新20至13. |
|
| 步骤6 | COMMIT; |
T1 提交。 | |
| 步骤7 | UPDATE product SET stock = 16 WHERE id = 2; |
T2 更新13到16T1 提交后。 |
|
| 步骤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 插入Lisa表event。 |
|
| 步骤6 | COMMIT; |
T1 提交。 | |
| 步骤7 | INSERT INTO event values ('Make Sushi', 'Kai'); |
T2 插入Kai表event。 |
|
| 步骤8 | COMMIT; |
T2 提交。4 位用户加入。*发生写入倾斜。 |
下表是我在MySQL和PostgreSQL中每个隔离级别的丢失更新和写入偏差的实验结果。*表示发生,表示不发生,表示SELECT FOR UPDATE。YesNoSFU
MySQL:
| 隔离级别 | 丢失更新 | SFU 丢失更新 | 写入偏差 | 使用 SFU 写入偏差 |
|---|---|---|---|---|
| 未提交的阅读 | 是的 | 不 | 是的 | 不 |
| 已提交读 | 是的 | 不 | 是的 | 不 |
| 可重复读取 | 是的 | 不 | 是的 | 不 |
| 可串行化 | 不 | 不 | 不 | 不 |
PostgreSQL:
| 隔离级别 | 丢失更新 | SFU 丢失更新 | 写入偏差 | 使用 SFU 写入偏差 |
|---|---|---|---|---|
| 未提交的阅读 | 是的 | 不 | 是的 | 不 |
| 已提交读 | 是的 | 不 | 是的 | 不 |
| 可重复读取 | 不 | 不 | 是的 | 不 |
| 可串行化 | 不 | 不 | 不 | 不 |
| 归档时间: |
|
| 查看次数: |
1152 次 |
| 最近记录: |