![PostgreSQL技术内幕:事务处理深度探索](https://wfqqreader-1252317822.image.myqcloud.com/cover/876/40107876/b_40107876.jpg)
2.8 行锁
PostgreSQL数据库采用MVCC方式进行并发控制,读写不会互相阻塞,而写和写之间仍然存在冲突,因此如果并发写同一个元组,就只能有一个事务进行修改,其他事务必须等待,直到先修改的事务提交或回滚。虽然用常规锁来对元组加锁也能防止多个事务同时修改同一个元组,但是常规锁被保存在锁表中,锁表在共享内存中的大小是有限的。如果一个事务修改了大量的元组,那么就需要在事务中申请大量的行级常规锁,锁表的规模就会增大,导致性能劣化,这是难以接受的。PostgreSQL采用了常规锁(元组级常规锁)和行锁(xmax)结合的方式来实现行锁。
在PostgreSQL数据库中,通常有两种方式对元组进行加锁,第一种方式是对元组做UPDATE/DELETE操作,另一种方式是通过显式地指定行锁,例如SELECT查询时指定了FOR UPDATE子句。在老版本的PostgreSQL中,只有FOR UPDATE和FOR SHARE两种方式显式地增加行锁;为了提高并发,PostgreSQL又增加了FOR KEY SHARE和FOR NO KEY UPDATE两种“弱”一些的行锁,因此目前显式加行锁有4个等级,这4个等级和LockTupleMode中的锁类型一一对应,如表2-7所示。
表2-7 显式加行锁的子句
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-086-1.jpg?sign=1738982632-rpbVIvahfK8KCrSuRdiM19BcUQ7rmkQE-0-2f8c4bc5eaaa27230fb5525a0b1eaaaf)
行锁的锁相容性矩阵如表2-8所示。
表2-8 行锁的相容性矩阵
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-086-2.jpg?sign=1738982632-EaP0dxzqorLKjIzP2uhxQipKF6XVMHE8-0-ee4e1084787db726d3c031178e29d2a1)
UPDATE、DELETE操作同样需要对元组进行加锁,它们的加锁类型如表2-9所示。
表2-9 DELETE和UPDATE对应的行锁
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-086-3.jpg?sign=1738982632-2RdLcf1Z4H9yOHn88SfQurUyfMX08nau-0-3cc27f2a38e35e72e51846ecc579dd93)
行级锁是由常规锁和xmax相结合实现的。对于常规锁,它需要建立LockTupleMode和常规锁之间的映射关系。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-086-4.jpg?sign=1738982632-EnKuLPx73iDUHmpQ8yb79XWbKo6KNdcZ-0-17081a2ff5bd95da0ecf0955d62b4d8a)
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-087-1.jpg?sign=1738982632-YW5kvWwTzDELqnzJ90nYri21n3SZUG5h-0-2ef43ba0e843ced98ad09f145d887c76)
tupleLockExtraInfo中除了保存常规锁对应的锁模式,还保存了MultiXact中的锁模式。通常如果只有一个事务增加行锁,那么直接在xmax处设置这个事务的ID,并且在infomask中设置对应的锁类型即可。但是在显式加行锁的SELECT…FOR…子句中,可以加行级共享锁。这时多个事务可以对一个元组加共享锁,xmax无法同时表达多个事务,因此将多个事务组合在一起形成一个mXactCacheEnt。并为其指定一个唯一的MultiXactId,在xmax处保存的就是MultiXactId。为了区分事务ID和MultiXactId,在使用MultiXactId时会在元组上增加HEAP_XMAX_IS_MULTI标记。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-087-2.jpg?sign=1738982632-fJDSp1xAclULCQcInl7ugjmVNZmY4Ol4-0-1c7ca4c7e2ad41da549d4e4cb2315cb2)
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-088-1.jpg?sign=1738982632-dYySpUEb0MRYgauHOWf0QeVtBNiX8ZGV-0-27225ef3fdf5e7b8ffff5a1a368c6ce4)
如果元组的xmax是事务ID,则还需要通过元组infomask中的标记位来区分当前元组的加锁情况,行锁的标记位和说明如表2-10所示。
表2-10 行锁的标记位和说明
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-088-2.jpg?sign=1738982632-71yzeYCVwATm8e5rcrIzQ99mqLjfqbd8-0-bb49a285ed3895821b15611d33922ec0)
行锁和标记位的反向对应关系如表2-11所示。
表2-11 行锁和标记位的反向对应关系
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-089-1.jpg?sign=1738982632-rdC7wqkg805QRC26U1fH5y3GTh11XNZd-0-bce6271430013a956484c9bd078204ca)
如果xmax中是MultiXactId,则每种子句都对应一种锁模式(它们的对应关系通过tupleLockExtraInfo也可以看出来)。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-089-2.jpg?sign=1738982632-DMZFLTXYOOuvCCipXVQrMw8aKEdyzMh4-0-618f89312d241cc5dbc74774bb238e5a)
为了保存MultiXactId和事务的映射关系,PostgreSQL使用两个SLRU进行分层映射,它们位于$PGDATA/pg_multixact目录下,分别是offsets目录和members目录,如图2-19所示。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-089-01.jpg?sign=1738982632-xad7LBD3bYoCNCD6JQCa9QELuTYMCOzI-0-0c8a911a29ea0598a5d816e4fcbd8890)
图2-19 pg_multixact中的文件结构
我们以更新操作为例来说明PostgreSQL数据库在更新操作时的可见性判断过程,实际上删除操作和更新操作同理。在并发更新时,会判断当前元组的状态,元组的状态类型如表2-12所示。根据不同的元组状态决定继续进行何种操作,状态的判断是由HeapTupleSatisfiesUpdate函数来完成的。
表2-12 元组的状态类型
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-090-1.jpg?sign=1738982632-CQmHIH4ov0V51K2EhhQVVPXjEN4Or6g3-0-45d0676c213fea4c434dd844626c5f9a)
元组是否能够被更新,取决于是否可见。不可见的元组显然是无须做更新操作的,例如事务A插入元组t之后异常终止,元组t仍会在数据页面中存在(还没有做Vacuum清理);当事务B做更新操作时,需要判断元组t的可见性,由于事务A最终没有提交,元组t是不可见的,所以事务B也就无须更新元组t。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-090-2.jpg?sign=1738982632-adSMSNLeF3dtxX2IJFY3KBhmToUuOGik-0-879b1db3eb9e056bfe1542de1238316a)
一个更新操作代表的是一个Command,自然这个更新操作也会有自己事务内部的CommandID。假如更新操作在扫描元组的过程中,发现一个元组的事务ID是当前事务,而它的CommandID却大于快照的CommandID,则代表在启动更新操作之后,又有本事务的其他操作修改了这个元组,此时这个元组的状态是TM_SelfModified。
例如,有一个触发器,在更新每个元组之前,都尝试删除元组,就会导致元组的实际CommandID大于更新操作的CommandID。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-091-1.jpg?sign=1738982632-8tLrjdwLZNRkV7Med5E9xgYeRBYozsfW-0-98fd6ce51ea2c090aacad17e5e9cf4dc)
假如元组正在被其他事务更新,那么这个元组的状态就是TM_BeingModified。在这个状态下,我们需要等待那个正在操作的事务完成之后,才能继续修改元组。更新元组的事务会在元组的头部设置元组的xmax,那么目前xmax就成了各个进程之间进行通信的行锁。先设置xmax的更新事务将xmax设置为自己的事务ID,也就获得了这个元组资源,其他事务必须等待xmax事务结束,如图2-20所示。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-091-01.jpg?sign=1738982632-qQVDqox9sSPTbDWh5bxosFsPM6bzuzJr-0-3bb3c260ad54dd74b6bb1c98dcf05134)
图2-20 行锁的等待关系
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-092-1.jpg?sign=1738982632-f9eeCf4hWFnTuPgqJf6iYjzJXt03ozDe-0-a72b8c8fc281b91758b60f51e63c414e)
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-093-1.jpg?sign=1738982632-qJOwCYIFd9epg6soaKgrdpCXaznVS9UC-0-e847aa9d6661dbc7707209582549b24b)
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-094-1.jpg?sign=1738982632-D2m0v4nYGXshIAxpBd3jxI0zcaVFGtmj-0-6537cd18e0d92fb81964ff62e101a530)
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-095-1.jpg?sign=1738982632-FBfV0ZSvqvOtr8KMIoiKPS6ctTtwMxk7-0-72365a2b5a806f79d0ef6bf47dc7400a)
如果元组已经被更新,那么就会进入TM_Updated状态,元组的更新可能导致新元组不再满足之前的过滤条件,因此需要重新检查新元组是否还满足我们的更新要求(PostgreSQL采用EPQ即EvalPlanQual机制进行条件检查)。
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-095-2.jpg?sign=1738982632-3RIESuUnXZdnucRS2yJ6NjtTRuDcNGCv-0-46a1638403cecbd7e13f00e088057c4f)
![](https://epubservercos.yuewen.com/F4E379/20862583708966906/epubprivate/OEBPS/Images/41561-00-096-1.jpg?sign=1738982632-k65Xqa9vkBLy6c38hZyDDAOh9JMwaoHs-0-b312fc44c8126cb35fe133f6f33943f6)