写新数据时,旧数据不删除,而是把新数据插入,将旧数据标记为无效,PostgreSQL就是使用的这种实现方法,新老数据存放在一起,在被清理之前,会一直占据着空间,所以会导致膨胀。 两种方法各有利弊,相对于第一种来说,PostgreSQL的MVCC实现方式优缺点如下:
优点 1.无论事务进行了多少操作,事务回滚可以立即完成,Oracle中使用了回滚段,如Oracle数据库宕机时如果有很多事务正在运行,这时数据库再启动后,需要把之前的事务做回滚,当没有回滚完成时,数据行上仍然有锁的,这时业务仍然不能正常操作,如果恰好碰到要回滚一些很大的事务,情况会更坏。 2.数据可以进行很多更新,不必像Oracle和MySQL的Innodb引擎那样需要经常保证回滚段不会被用完,也不会像oracle数据库那样经常遇到“ORA-1555”错误的困扰
1.旧版本的数据需要清理。好在在v8.3中,PostgreSQL引入了自动化的autovacuum,采用多进程架构,支持多表同时操作2.旧版本的数据可能会导致查询需要扫描的数据块增多,从而导致查询变慢 3.空间持续上涨,存储没有被有效利用
postgres=# begin; BEGIN postgres=*# select txid_current(); txid_current -------------- ? ? ? ? 756 (1 row) ?
0:InvalidTransactionId,表示无效的事务ID 1:BootstrapTransactionId,表示系统表初始化时的事务ID,比任何普通的事务ID都旧。 2:FrozenTransactionId,冻结的事务ID,比任何普通的事务ID都旧。 大于2的事务ID都是普通的事务ID。
/* *TransactionIdPrecedes---isid1logically<id2? */ bool TransactionIdPrecedes(TransactionIdid1,TransactionIdid2){
*IfeitherIDisapermanentXIDthenwecanjustdounsigned *comparison.Ifbotharenormal,doamodulo-2^32comparison.*/ ? int32 diff; ? if(!TransactionIdIsNormal(id1)||!TransactionIdIsNormal(id2)) return(id1<id2);diff=(int32)(id1-id2); return(diff<0); ? txid可以相互比较大小。例如对于txid=100的事务,大于100的txid属于“未来”,且对于txid=100的事务而言都是不可见(invisible)的;小于100的txid属于“过去”,且对该事务可见,如图5.1(a)所示。 ? 因为txid在逻辑上是无限的,而实际系统中的txid空间不足(4字节取值空间约42亿),因此PostgreSQL将txid空间视为一个环。对于某个特定的txid,其前约21亿个txid属于过去,而其后约21亿个txid属于未来。如图5.1(b)所示。 ? 所谓的txid回卷问题将在5.10.1节中介绍。
虽然HeapTupleHeaderData结构包含七个字段,但后续部分中只需要了解四个字段即可。 ? t_xmin保存插入此元组的事务的txid。 t_xmax保存删除或更新此元组的事务的txid。如果尚未删除或更新此元组,则t_xmax设置为0,即无效。 t_cid保存命令标识(command id, cid),cid意思是在当前事务中,执行当前命令之前执行了多少SQL命令,从零开始计数。例如,假设我们在单个事务中执行了三条INSERT命令BEGIN;INSERT;INSERT;INSERT;COMMIT;。如果第一条命令插入此元组,则该元组的t_cid会被设置为0。如果第二条命令插入此元组,则其t_cid会被设置为1,依此类推。 t_ctid保存着指向自身或新元组的元组标识符(tid)。如第1.3节中所述,tid用于标识表中的元组。在更新该元组时,其t_ctid会指向新版本的元组;否则t_ctid会指向自己。
插入操作的过程和结果分析: t_xmin被设置为99,表示插入该元组的txid t_xmax被设置为0,因为该元组还未被更新或删除过 t_cid被设置为0,因为这是该事务的第一条命令 t_ctid指向自身,被设置为(0,1),表示该元组位于0号page的第1个位置上
删除操作的过程和结果分析 t_xmin不变,也是99, t_xmax变成为111,因为是删除操作 t_cid被设置为0,因为这是该事务的第一条命令 t_ctid指向自身,被设置为(0,1),表示该元组位于0号page的第1个位置上 当txid=111的事务提交时,tuple_1就不再需要了,称为dead tuple.但是这个dead tuple依然还是在这个page上,随着数据库的运行,这种死元组越来越多,他们会在vacuum时最终被清理掉。
更新操作过程和结果分析 看第一条tuple t_xmin不变,也是99, t_xmaix变成为100,这个是删除操作, t_cid t_cid被设置为0,因为这是该事务的第一条命令 t_ctid指向自身,被设置为(0,2),表示该元组位于0号page的第2个位置上 接着插入一条新数据 t_xmin,也是100, t_xmaix变成为0,因为没有删除操作 t_cid被设置为0,因为这是该事务的第一条命令 t_ctid指向自身,被设置为(0,2),表示该元组位于0号page的第2个位置上 再来看第二天tuple Tuple_2 t_xmin 不变,表示插入该元组的txid t_xmax 被设置为100,即删除该元组的txid t_cid 被设置为1,因为这是该事务的第二条命令 t_ctid 指向新版本元组,被设置为(0,3),表示新元组位于0号page的第3个位置上 Tuple_3 t_xmin 被设置为100,表示插入该元组的txid t_xmax 被设置为0,因为该元组还未被更新或删除过 t_cid 被设置为1,因为这是该事务的第二条命令
? 1、xmin,xmax 最早事务和最晚事务 , select txid_current_snapshot(); 使用场景:MVCC事务可见性场景用到。 2、t_xmin,t_xmax 插入改元组的txid,和删除更新改元组的txid,那请问txid是什么呢?txid也就是当前事务 ID ,通过 select txid_current(); 获取 使用场景:DML 语句中用到。 ? 补充一点:只读事务使用的是虚拟ID,用 select txid_current_if_assigned();
参考博客:https://www.cnblogs.com/flying-tiger/p/9567213.html
作者公众号