Postgresql支持对元组加锁,为了减少锁的内存占用,直接在xmax字段记录加锁的事务XID。
Postgresql元组锁有四种类型,分成不同的等级:
FOR UPDATE 对整个元组加排他锁;
FOR NO KEY UPDATE 对非关键列加排他锁;
FOR SHARE 对整个元组加共享锁;
FOR KEY SHARE 对关键列加共享锁;
元组锁的相容性视图如下:
Mode FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE FOR UPDATE
FOR KEY SHARE Х
FOR SHARE Х Х
FOR NO KEY UPDATE Х Х Х
FOR UPDATE Х Х Х Х
其中关键点是FOR NO KEY UPDATE和FOR KEY SHARE是可以兼容的。
当一个元组同时加多个互相兼容的锁时,PostgreSQL将与该元组相关联的多个事务ID组合起来,用一个MultiXactID记录到元组的xmax字段。
Multixact创建
举例如下:
Session 1
postgres=# begin;
BEGIN
postgres=# select *,xmax from course for share;
cno | cname | credit | priorcourse | xmax
-----+---------+--------+-------------+------
1 | Chinese | 5 | | 633
3 | English | 4 | | 633
2 | Physics | 5 | | 633
postgres=# select txid_current();
txid_current
--------------
633
(1 row)
Session 2
postgres=# begin;
BEGIN
postgres=# select *,xmax from course for share;
cno | cname | credit | priorcourse | xmax
-----+---------+--------+-------------+------
1 | Chinese | 5 | | 633
3 | English | 4 | | 633
2 | Physics | 5 | | 633
(3 rows)
postgres=# select txid_current();
txid_current
--------------
634
(1 row)
在compute_new_xmax_infomask函数
Session1的事务已经在xmax字段记录了自己的事务XID,因此会进入TransactionIdIsInProgress(xmax)分支,最终调用MultiXactIdCreate函数创建出一个multi_xact_id = 5。
由于加锁的tuple总共有3个元组,为了避免创建多个multi_xact_id,每个事务会创建一个mcache,当需要创建一个新的multi_xact_id,先遍历mcache中每个组合事务,比对组合事务中的每个成员。如果有完全相同的成员,直接复用已有的multi_xact_id=5。
Session 3
postgres=# select *,xmax from course;
cno | cname | credit | priorcourse | xmax
-----+---------+--------+-------------+------
1 | Chinese | 5 | | 5
3 | English | 4 | | 5
2 | Physics | 5 | | 5
Multixact等待
Session 4
postgres=# begin;
BEGIN
postgres=# select * from course for update;
Session4
Do_MultiXactIdWait
对multixactId成员,循环调用XactLockTableWait,直到所有的加锁事务成员都已经提交或者回滚,才能尝试对元组加update锁。