介绍PostgreSQL使用MVCC(Multi-VersionConcurrencyControl)来保证事务的原子性和隔离性。具体的MVCC机制是如何实现的?下面举几个例子做一个简单的分析,加深理解。前提表中的系统隐藏字段PostgreSQL的每个表中都有一些系统隐藏字段,包括:oid:对象标识符,生成的值是全局唯一的,表、索引、视图都有oid,如果需要创建的话表中使用了oid字段,需要明确指定“withoids”选项。ctid:表中每条记录(称为元组)的物理位置标识。xmin:创建记录(元组)时,将此值记录为当前事务ID。xmax:创建元组时,默认为0。删除元组时,记录该值作为当前事务ID。cmin/cmax:标识同一事务中多条语句命令的顺序值,从0开始,用于实现同一事务中的版本可见性判断MVCC机制。MVCC机制就是通过这些隐藏的标签字段协同实现的。下面用几个例子来解释MVCC是如何实现的//seesion1:创建一个表,显示指定的oid字段:testdb=#createtablet1(idint)withoids;CREATETABLE插入几条记录testdb=#insertintot1values(1);INSERT175691testdb=#insertintot1values(2);INSERT175701testdb=#insertintot1values(3);INSERT175711查询当前表中的tuple信息,xmin为创建tuple时的事务ID,xmax默认为0testdb=#selectctid,xmin,xmax,cmin,cmax,oid,idfromt1;ctid|xmin|xmax|cmin|cmax|oid|id------+--------+------+------+------+------+----(0,1)|80853357|0|0|0|17569|1(0,2)|80853358|0|0|0|17570|2(0,3)|80853359|0|0|0|17571|3(3rows)接下来我们更新一个元组的字段,将元组中的id值从1更新为4,看看是什么发生testdb=#begin;BEGINtestdb=#selecttxid_current();txid_current------------80853360(1row)testdb=#updatet1setid=4whereid=1;UPDATE1查看元组详情testdb=#selectctid,xmin,xmax,cmin,cmax,oid,idfromt1;ctid|xmin|xmax|cmin|cmax|oid|id------+------+------+------+------+-------+----(0,2)|80853358|0|0|0|17570|2(0,3)|80853359|0|0|0|17571|3(0,4)|80853360|0|0|0|17569|4(3rows)可以看到id为1(oid=17569)的tuple被修改了,id值更新为4,ctid和xmin字段也更新了是的,ctid值代表tuple的物理位置,xmin值在创建tuple的时候已经写入了。这两个字段不应更改。我们再启动一个seesion看看(当前事务还没有提交)//seesion2:testdb=#selectctid,xmin,xmax,cmin,cmax,oid,idfromt1;ctid|xmin|xmax|cmin|cmax|oid|id------+---------+----------+------+------+-------+----(0,1)|80853357|80853360|0|0|17569|1(0,2)|80853358|0|0|0|17570|2(0,3)|80853359|0|0|0|17571|3(3rows)可以看到id为1(oid=17569)的元组依然存在,只是xmax值被标记为当前事务Id。当一个tuple被更新时,会增加一个新的tuple,填充更新后的字段值,同时将原来的tuple标记为已删除(设置xmax为当前事务Id)。同理可以看到删除一个tuple的结果|xmax|cmin|cmax|oid|id------+----------+--------+------+------+-------+----(0,1)|80853357|80853360|0|0|17569|1(0,2)|80853358|80853360|1|1|17570|2(0,3)|80853359|0|0|0|17571|3(3rows)删除一个元组时,xmax也被标记为当前事务Id,并没有进行实际的物理记录清除操作。另外,cmin和cmax值递增为1,表示同一事务中的操作顺序。在事务(seesion1)未提交之前,其他事务(seesion2)可以看到之前的版本信息。不同的事务有自己的数据空间,它们的操作不会相互干扰,保证了事务的隔离性。提交交易,查看最终结果如下://seesion1:testdb=#commit;COMMITtestdb=#selectctid,xmin,xmax,cmin,cmax,oid,idfromt1;ctid|xmin|xmax|cmin|cmax|oid|id-------+--------+------+------+------+--------+----(0,3)|80853359|0|0|0|17571|3(0,4)|80853360|0|0|0|17569|4(2rows)但是,如果我们不提交事务但是回滚,结果是什么?testdb=#begin;BEGINtestdb=#updatet1setid=5whereid=4;UPDATE1testdb=#rollback;ROLLBACKtestdb=#selectctid,xmin,xmax,cmin,cmax,oid,idfromt1;ctid|xmin|xmax|cmin|cmax|oid|id------+----------+----------+------+------+-------+----(0,3)|80853359|0|0|0|17571|3(0,4)|80853360|80853361|0|0|17569|4(2行)xmax标记没有清除,继续添加一条记录:testdb=#insertintot1values(5);INSERT175721testdb=#selectctid,xmin,xmax,cmin,cmax,oid,idfromt1;ctid|xmin|xmax|cmin|cmax|oid|id------+----------+----------+------+------+-------+----(0,3)|80853359|0|0|0|17571|3(0,4)|80853360|80853361|0|0|17569|4(0,6)|80853362|0|0|0|17572|5(3rows)发现新加入的tuple没有清理干净,原来tuple上的xmax标记被剔除。为什么?出于效率的考虑,如果事务回滚,标记也被清除,可能会造成磁盘IO,降低性能。那么如何判断元组是否有效呢?答案是PostgreSQL会在clog(commitlog)位图文件中记录事务状态,每读取一行就会在文件中查询事务状态。事务状态通过以下四个来表示:#defineTRANSACTION_STATUS_IN_PROGRESS=0x00Inprogress#defineTRANSACTION_STATUS_COMMITTED=0x01Committed#defineTRANSACTION_STATUS_COMMITTED=0x02Rolledback#defineTRANSACTION_STATUS_SUB_COMMITTED=0x03子事务已经提交MVCC保证原子性和隔离性(atomicity)原子性)要求同一事务中的所有操作要么完成要么不完成。根据PostgreSQL的MVCC规则,插入数据时会将当前事务ID写入xmin,删除数据时会将事务ID写入xmax。更新数据相当于删除了原来的元组,然后又增加了一个元组。为所有的删除和修改操作预留了事务ID,事务中的所有操作都根据事务ID来提交或撤销,从而保证了事务的原子性。隔离性事务的隔离性(Isolation)要求并行事务不能相互干扰,事务是隔离的。PostgreSQL能读到的数据是xmin小于当前事务ID,已经提交。更新或删除元组时,其他事务会读取该元组的先前版本。MVCC的好处是读写不会互相阻塞,写操作不会阻塞其他事务的读。在写入事务提交之前,读取之前的版本,提高并发访问效率。可以快速回滚事务,操作后的元组中有当前事务ID,可以直接在clog文件中标记对应事务的状态,达到回滚的目的。MVCC带来的问题事务ID回滚问题PostgreSQL也需要事务ID来确定事务的顺序。在PostgreSQL中,事务称为XID。获取当前XID:testdb=#selecttxid_current();txid_current--------------80853335(1row)事务ID用32位数字表示。当事务ID用完后,新的事务ID会比旧的小,导致事务ID环绕问题(TransactionIDWraparound)。PostgreSQL的事务ID规则:0:InvalidXID,无效事务ID1:BootstrapXID,表示系统表初始启用时的事务2:FrozenXID,冻结事务ID,比任务正常事务ID旧。–交易ID大于2的为普通交易ID。当***与最早的交易差值达到2^31时,将旧交易替换为FrozenXID,然后使用公式((int32)(id1-id2))<0进行大小比较。垃圾数据问题根据MVCC机制,更新和删除的记录不会被真正删除。操作频繁的表会积累大量过期数据,占用磁盘空间。扫描查询数据时,需要更多的IO,降低了查询效率。PostgreSQL的解决方案是提供vacuum命令操作来清理过期数据。原文链接:https://www.qcloud.com/community/article/528634,作者:黄辉【本文为专栏作者《腾讯云技术社区》原创稿件,转载请联系原作者获得授权】点这里,查看该作者更多好文
