PostgreSQL是经过MVCC(Multi-Version Concurrency Control)来保证事务的原子性和隔离性,具体MVCC机制是怎样实现的,下面举些示例来作个简单解析以加深理解。并发
PostgreSQL的每一个表中都有些系统隐藏字段,包括:性能
MVCC机制经过这些隐藏的标记字段来协同实现,下面举几个示例来解释MVCC是如何实现的spa
//seesion1: 建立表,显示指定oid字段: testdb=# create table t1(id int) with oids; CREATE TABLE 插入几条记录 testdb=# insert into t1 values(1); INSERT 17569 1 testdb=# insert into t1 values(2); INSERT 17570 1 testdb=# insert into t1 values(3); INSERT 17571 1
查询当前表中的tuple信息,xmin为建立tuple时的事务ID,xmax默认为0code
testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; 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 (3 rows)
接下来,咱们更新某个tuple的字段,将tuple中id值为1更新为4,看看会发生什么对象
testdb=# begin; BEGIN testdb=# select txid_current(); txid_current -------------- 80853360 (1 row) testdb=# update t1 set id = 4 where id = 1; UPDATE 1
查看tuple详细信息索引
testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; 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 (3 rows)
能够看到id为1的tuple(oid=17569)已经被修改了,id值被更新为4,另外ctid、xmin字段也被更新了,ctid值表明了该tuple的物理位置,xmin值是建立tuple时都已经写入,这两个字段都不该该被更改才对,另起一个seesion来看下(当前事务还未提交)事务
//seesion2: testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; 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 (3 rows)
能够看到id为1的tuple(oid=17569)还存在,只是xmax值被标记为当前事务Id。 原来更新某个tuple时,会新增一个tuple,填入更新后的字段值,将原来的tuple标记为删除(设置xmax为当前事务Id)。同理,能够看下删除一个tuple的结果ci
//seesion1: testdb=# delete from t1 where id = 2; DELETE 1 //seesion2: testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; ctid | xmin | 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 (3 rows)
删除某个tuple时也是将xmax标记为当前事务Id,并不作实际的物理记录清除操做。另外cmin和cmax值递增为1,代表了同一事务中操做的顺序性。在该事务(seesion1)未提交前,其余事务(seesion2)能够看到以前的版本信息,不一样的事务拥有各自的数据空间,其操做不会对对方产生干扰,保证了事务的隔离性。it
提交事务,查看最终结果以下:io
//seesion1: testdb=# commit; COMMIT testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; ctid | xmin | xmax | cmin | cmax | oid | id -------+----------+------+------+------+-------+---- (0,3) | 80853359 | 0 | 0 | 0 | 17571 | 3 (0,4) | 80853360 | 0 | 0 | 0 | 17569 | 4 (2 rows)
可是,若是咱们不提交事务而是回滚,结果又是如何?
testdb=# begin ; BEGIN testdb=# update t1 set id = 5 where id = 4; UPDATE 1 testdb=# rollback; ROLLBACK testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; ctid | xmin | xmax | cmin | cmax | oid | id -------+----------+----------+------+------+-------+---- (0,3) | 80853359 | 0 | 0 | 0 | 17571 | 3 (0,4) | 80853360 | 80853361 | 0 | 0 | 17569 | 4 (2 rows) xmax标记并未清除,继续新增一条记录: testdb=# insert into t1 values(5); INSERT 17572 1 testdb=# select ctid, xmin, xmax, cmin, cmax, oid, id from t1; 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 (3 rows)
发现没有清理掉新增的tuple,消除原有tuple上的xmax标记,这是为什么?处于效率的缘由,若是事务回滚时也进行清除标记,可能会致使磁盘IO,下降性能。那如何判断该tuple的是否有效呢?答案是PostgreSQL会把事务状态记录到clog(commit log)位图文件中,每读到一行时,会到该文件中查询事务状态,事务的状态经过如下四种来表示:
事务的原子性(Atomicity)要求在同一事务中的全部操做要么都作,要么都不作。根据PostgreSQL的MVCC规则,插入数据时,会将当前事务ID写入到xmin中,删除数据时,会将事务ID写入xmax中,更新数据至关于先删除原来的tuple再新增一个tuple,增删改操做都保留了事务ID,根据事务ID提交或撤销该事务中的全部操做,从而保证了事务的原子性。
事务的隔离性(Isolation)要求各个并行事务之间不能相互干扰,事务之间是隔离的。PostgreSQL可读取的数据是xmin小于当前的事务ID且已经提交。对某个tuple进行更新或删除时,其余事务读取的就是这个tuple以前的版本。
PostgreSQL也须要事务ID来肯定事务的前后顺序,PostgreSQL中,事务被称为XID,获取当前XID:
testdb=# select txid_current(); txid_current -------------- 80853335 (1 row)
事务ID由32bit数字表示,当事务ID用完时,就会出现新的事务ID会比老ID小,致使事务ID回卷问题(Transaction
ID Wraparound)。 PostgreSQL的事务ID规则:
根据MVCC机制,更新和删除的记录都不会被实际删除,操做频繁的表会积累大量的过时数据,占用磁盘空间,当扫描查询数据时,须要更多的IO,下降查询效率。PostgreSQL的解决方法是提供vacuum命令操做来清理过时的数据。