PG中的MVCC(多版本并发控制)

0    546    5

👉 本文共约3865个字,系统预计阅读时间或需15分钟。

多版本并发控制(Multi-version Concurrency Control, MVCC)是每一个写操作都创建一个新版本数据,并保留旧版本数据。当事务读取数据时,系统会选择一个合适的版本呈现出来,通过这种方式实现各事务之间相互隔离。比如MySQLOracle,新版本数据写入时,将旧版本数据写入到回滚段,用新版本数据项覆盖原有数据区域。PostgreSQL是将新旧版本数据都写入到数据文件中,当其他事物读取数据项时,根据可见性校验规则,选择合适的版本呈现出来。

一、事物ID

在PostgreSQL中,每一个事务都有一个唯一(并非真正唯一)标识符(txid)。txid是一个32位无符号整数,约42亿个,在事务启动后执行txid_current()函数,即可获得当前事务ID。txid可以比较大小,比当前txid大的事务,认为是未来的事务,在当前事务中不可见。比当前txid小的事务,认为是过去的事务,在当前的事务中可见。

PostgreSQL保留以下三个特殊txid

  • 0表示无效的txid
  • 1表示初始启动的txid,仅用于数据库集群的初始化initdb过程。
  • 2表示冻结的txid

二、事务ID大小比较

txid是理论上是无限大的,而实际上实现无限大不现实。PostgreSQL将txid空间视为一个环,可以无限循环。对于某个特定的txid,其环内前约21亿个txid属于过去的,而其环内后约21亿个txid属于未来的。txid在环内循环(txid随增长环内循环,至于如何保证过去事务不变成未来事务,这个是另一个冻结txid的话题,本文不做讨论)

三、隐藏列

在PostgreSQL中,表中每一行数据称为一个元组tuple,每个tuple中除了用户自定义的数据外,还有其他隐藏列,本文只介绍用于并发控制的几个列t_ctid, t_xmin, t_xmax, t_cid,用来展示如何实现新旧版本数据都写入到数据文件中。

在PG中,可以使用pageinspect这个外部扩展来观察数据库页面的内容。

在展示之前,需要安装插件pageinspect,如下:

t_xmin:保存插入该元组事务txid。

t_xmax:保存删除或跟新该元组事务txid。

t_cid:保存事务内的第几条SQL命令,从0开始。如事务内第一条SQL记录为0,第二条SQL记录为1。

t_ctid:保存着指向自身或新元组的元组标识符tidtid用于标识表中的元组。在更新该元组时,其t_ctid会指向新版本的元组;否则t_ctid会指向自己。

本人提供Oracle、MySQL、PG等数据库的培训和考证业务,私聊QQ646634621或微信db_bao,谢谢!

四、数据insert、update、delete

insert数据

insert数据的事务txid为3809,共insert两条数据。所以t_xmin为3809,t_ctid分别为(0,1),(0,2)代表0号数据块的第一条数据和第二条数据,t_cid代表事务内的第几条SQL命令。

update数据

id=2的数据将新旧版本数据都写入到数据文件中。旧版本数据t_xmax等于3810,表明3810号事务已将该数据更新或删除。新版本数据t_xmin等于3810,表明该条数据是3810号事务写入的。再次查询a表,只select到了新版本数据,旧版本数据在没有其他事务使用时,变成了dead tupledead tuple数据占用的磁盘空间会被vacuum进程回收再利用,本文不做讨论)

delete数据

id=2的数据已经全部被删除。但是在数据文件中并没有被删除,只是在t_xmax上打了一个标志。

PostgreSQL多版本并发控制MVCC (Multi-version Concurrency Control)是在写数据时创建一个新版本数据,并将新旧版本数据都保存在数据文件中。而并行的其他事务查询数据时,应该选择哪一个版本的数据来呈现呢?

接下来进行解释。

五、事务快照

事务快照是在某个时间点看到的事务状态信息,包括哪些事务已经完成,那些事务还未开始,哪些事务正在进行中。在PostgreSQL中,用txid_current_snapshot()函数来获得当前的事务快照。

事务快照的文本表现形式:xmin:xmax:xip_list 具体含义如下:

事务快照项解释说明
xminEarliest transaction ID (txid) that is still active. All earlier transactions will either be committed and visible, or rolled back and dead.最早的活跃事务的txid,txid<xmin的事务要么提交,要么回滚
xmaxFirst as-yet-unassigned txid. All txids greater than or equal to this are not yet started as of the time of the snapshot, and thus invisible.第一个尚未分配的txid,txid>=xmin的事务没有开启,所以不可见。
xip_listActive txids at the time of the snapshot. The list includes only those active txids between xmin and xmax; there might be active txids higher than xmax. A txid that is xmin <= txid < xmax and not in this list was already completed at the time of the snapshot, and thus either visible or dead according to its commit status. The list does not include txids of subtransactions.xmin<=txid<xmax中仍然活跃的事务。

例1: 100:100: xmin为100,因此txid < 100的事务是非活跃的,要么提交,要么回滚。xmax为100,因此txid ≥ 100的事务尚未开始。

例2: 100:104:100,102 xmin为100,因此txid < 100的事务是非活跃的,要么提交,要么回滚。xmax为104,因此txid ≥ 104的事务尚未开始。xip_list为100,102,所以100,102号事务仍然活跃,101,103号事务不活跃,要么提交、要么回滚。

非活跃事务是回滚还是提交?需要查看PostgreSQL中clog记录,本文不做解释。

六、事务快照与事务隔离级别

PostgreSQL中事务隔离级别是通过事务快照来实现的,获取事务快照用来检查元组的可见性。对read committed隔离级别,每执行一条语句都会获得事务快照;对repeatable read,事务只会在执行第一条SQL时获取一次快照。注意,事务快照不是在事务开始时获取,而是在事务中执行语句时获取,看例子:

时间事务一事务二事务三说明
Time_1begin transaction isolation level repeatable read;事务二首先开始,并设置为repeatable read,此时事务二并没有获取事务快照。
Time_2begin;事务一开始,默认read committed隔离级别
Time_3select count(1) from a;结果是0条insert into a values (1 , 'a');事务一插入一条记录。
Time_4commit;事务一提交
Time_5select count(1) from a; 结果是1条事务二获取事务快照,此时事务一已经提交,事务一的结果可以查询
Time_6begin;insert into a values (2 , 'b');commit ;事务三又插入一条记录,默认read committed隔离级别。
Time_7select count(1) from a; 结果是1条事务二仍然使用Time_5获取是事务快照,只看到一条记录。
Time_8commit;事务二结束
    头像

    小麦苗

    学习或考证,均可联系麦老师,请加微信db_bao或QQ646634621

    您可能还喜欢...

    发表评论

    您的电子邮箱地址不会被公开。

    4 × 5 =

     

    嘿,我是小麦,需要帮助随时找我哦
    • 18509239930
    • 个人微信

    • 麦老师QQ聊天
    • 个人邮箱
    • 点击加入QQ群
    • 个人微店

    • 回到顶部
    返回顶部