可重复读隔离级别保证每个事务都会从一致的快照 https://en.wikipedia.org/wiki/Snapshot_isolation数据库的。换句话说,在同一事务中两次检索同一行始终具有相同的值。
许多数据库如Postgres、SQLServer在可重复读隔离级别下都可以检测到丢失更新 https://stackoverflow.com/questions/855105/lost-update-in-concurrency-control(写入倾斜的特殊情况)但其他人则不然。 (即:MySQL 中的 InnoDB 引擎)
我们回来写倾斜现象问题。大多数数据库引擎在可重复读隔离中无法检测到某些情况。一种情况是当2个并发交易修改2个不同的物体并制定竞争条件。
我举一个书上的例子设计数据密集型应用程序 https://rads.stackoverflow.com/amzn/click/com/1449373321。这是场景:
您正在为医生编写一个应用程序来管理他们的值班人员
在医院轮班。医院通常会尝试安排几
随时待命的医生,但绝对必须至少有
一名待命医生。医生可以放弃轮班(例如,如果他们
自己生病了),前提是至少有一名同事仍在工作
调用该轮班
下一个有趣的问题是我们如何在数据库下实现这一点。这是伪代码 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;
Here is the illustration:
如上图所示,我们看到 Bob 和 Alice 同时运行上面的 SQL 代码。然而Bob和Alice修改了不同的数据,Bob修改了Bob的记录,Alice修改了Alice的记录。可重复读隔离级别的数据库无法知道并检查条件(总医生 >= 2)是否已被违反。出现了写入倾斜现象。
为了解决这个问题,提出了2种方法:
- 锁定所有手动调用的记录。所以鲍勃或爱丽丝将等待直到其他人完成交易。
这是一些伪代码使用SELECT .. FOR UPDATE
query.
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 https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html#isolevel_serializable, Postgres https://www.postgresql.org/docs/9.1/static/transaction-iso.html T-SQL https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-2017提供序列化隔离级别。