为什么 MVCC 需要对 DML 语句进行锁定
Why does MVCC require locking for DML statements
在 PostgreSQL 中,MVCC concurrency control 机制表示:
MVCC locks acquired for querying (reading) data do not conflict with
locks acquired for writing data, and so reading never blocks writing
and writing never blocks reading
因此,even for READ_COMMITTED,UPDATE 语句将锁定当前受影响的行,因此没有其他事务可以修改它们,直到当前事务提交或回滚。
如果并发事务对锁定的行发出更新,则第二个事务将阻塞,直到第一个事务释放它的锁。
在 READ_COMMITTED 中仍然会发生更新丢失,因为在第一个事务提交后,第二个事务将覆盖该行(即使数据库在 UPDATE 查询开始和查询结束)。那么如果仍然有可能丢失更新,为什么第二个事务要等待呢?难道不能使用行级快照来存储未提交的事务更改,以避免事务必须等待写锁被释放吗?
第一个问题的答案是肯定的。没有 DBMS 可以支持脏写;如果两个事务 T1 和 T2 同时执行并且 T2 覆盖了 T1 的更新,那么系统无法处理 T1 随后发出 ROLLBACK 的情况,因为 T2 的更新已经发生。
为了避免脏写,快照隔离的原始定义是"first committer wins" - 即允许发生冲突写入,但只有第一个发出 COMMIT 的事务能够 - 所有其他冲突事务将不得不回滚。但是这种编程模型即使不是浪费也是有问题的,因为事务可能会更新数据库的很大一部分,但最终会拒绝提交 COMMIT 的能力。因此,大多数支持 MVCC 的 DBMS 系统 "first updater wins" 使用相当传统的两阶段锁定来代替 "first committer wins"。
在 PostgreSQL 中,MVCC concurrency control 机制表示:
MVCC locks acquired for querying (reading) data do not conflict with locks acquired for writing data, and so reading never blocks writing and writing never blocks reading
因此,even for READ_COMMITTED,UPDATE 语句将锁定当前受影响的行,因此没有其他事务可以修改它们,直到当前事务提交或回滚。
如果并发事务对锁定的行发出更新,则第二个事务将阻塞,直到第一个事务释放它的锁。
在 READ_COMMITTED 中仍然会发生更新丢失,因为在第一个事务提交后,第二个事务将覆盖该行(即使数据库在 UPDATE 查询开始和查询结束)。那么如果仍然有可能丢失更新,为什么第二个事务要等待呢?难道不能使用行级快照来存储未提交的事务更改,以避免事务必须等待写锁被释放吗?
第一个问题的答案是肯定的。没有 DBMS 可以支持脏写;如果两个事务 T1 和 T2 同时执行并且 T2 覆盖了 T1 的更新,那么系统无法处理 T1 随后发出 ROLLBACK 的情况,因为 T2 的更新已经发生。
为了避免脏写,快照隔离的原始定义是"first committer wins" - 即允许发生冲突写入,但只有第一个发出 COMMIT 的事务能够 - 所有其他冲突事务将不得不回滚。但是这种编程模型即使不是浪费也是有问题的,因为事务可能会更新数据库的很大一部分,但最终会拒绝提交 COMMIT 的能力。因此,大多数支持 MVCC 的 DBMS 系统 "first updater wins" 使用相当传统的两阶段锁定来代替 "first committer wins"。