PostgreSQL之MVCC

MVCC的介绍 
   MVCC技术,是multiversion concurrency control的缩写,翻译成中文是多版本并发控制的意思。目前多数DB都采用了这一技术,好比Oracle,Innodb,Berkeley DB等,采用这种方法可有效下降锁的开销。


MVCC的实现
    MVCC是干吗的呢?举例来讲,当数据库作一个更新操做时,并非将老的数据删除,再将新的数据覆盖上去,相反,它会把老的数据作一个标记隔离出去(old version),而后再新增新的数据做为一个新的版本,这个时候新老数据都是存在数据库里的。但在不一样的数据库里,其存储形式是不同的。在PostgreSQL里,新老版本数据是混在一块儿的,Oracle和Innodb里是分开存储的,像Oracle有UNDO做为区分。或者说得更简单一点,读不会阻塞写操做,反过来也是同样。两种形式各有优缺点。

    MVCC下的读事务一般以时间点或增加的事务ID来标记数据库里从个点开始读以及这个点所对应的数据。在PostgreSQL中,每个事务都会得到一个事务ID,叫 XID, 这个能够是一个insert,update,delete或者是用begin...end包裹起来的一组SQL。当这个事务开始的时候,Postgres会增长XID值并赋给当前的事务,Postgres也会对系统里的每一行附上上事务信息,这能够用来判断该行在其余事务中是否可见。

    XID在PostgreSQL中是怎么作的呢, 或者说怎么获取的呢?当插入一行的时候,Postgres存储XID值并叫它 XMIN 。以前也有介绍,这是一个隐藏字段。对当前事务来讲,每一行被提交的数据其对应的XMIN值比当前的XID小,那么都是可见的。举个很简单的例子:你能够开启一个事务,假如以begin开始,而后插入几行数据,在Commit以前,这些数据对其余事务来讲都是不可见的,直到你作了Commit。一旦咱们作了Commit操做(XID会增加),对其余事务来讲已经知足XMIN<XID,因此其余事务就能看到在该事务提交后的东西。得到当前事务的XID值比较简单:SELECT txid_current();


对于DELETE和UPDATE来讲,MVCC的机制也是相似的,略有不一样的是Postgres在执行DELETE和UPDATE操做时对每一行还存储了XMAX这一隐藏列。也是经过这个字段来决定当前的删除或者更新对其余事务来讲是否可见。

具体示例能够参考:http://my.oschina.net/Kenyon/blog/63668
sql

 当两个事务在某个时间点同时更新某一行数据时,是怎么处理的呢?这里将引入事务的隔离级别这一律念。Postgres可知足四种模式级别来处理这种场景,默认的是 READ COMMITTED ,它是完成初始化事务,而后执行语句获取行数据。若是在等待过程当中行有了变化,他将从新开始。举例来讲,你正在执行一个带where条件的UPDATE操做,这个where条件将在你初始化事务提交后返回,若是where知足条件,那么Update将会执行,不知足则会Hang住(很容易模拟这个场景,开启两个会话,对同一行数据进行UPDATE,只要不提交便可,后面的UPDATE会挂住,直到前面的事务提交或回滚)

   若是想更清楚地看清过程,能够设置事务隔离级别为 SERIALIZABLE ,在这种模式下一样的两个UPDATE其中一个将会报错:
   ERROR: could not serialize access due to concurrent update

MVCC的问题:
在Postgres中,UPDATE实际上会复制一份修改记录,而DELETE不会当即移除那条被删的记录,其新旧版本是共存的。因此UPDATE全表时,其大小一般会增加一倍,不知道内部结构的人每每很迷惑,但如今能够释然了。对于DELETE掉的数据,只是标注它被删除了,而后更新了一下XID的值。当事务结束完了之后,这些数据仍然存在,可是是不可见的,咱们称之为DEAD ROWS。这样会带来另外一个问题,事务ID(XID)是32bit的,只能支持大约40亿(2^32 = 4 294 967 296)次事务,当XID达到最大值时,它会自动回零,这会带来一个很严重的问题,由于全部行的XID都比0大,这会致使行数据不可见(就比如如今的数据成了未来的数据了)。

Postgres对此有一个进程是专门处理这个问题的,那就是VACUUM,高版本的Postgres已是自动运行的了(auto_vacuum),是内置进程之一。一般倒也不用怎么关注,可是若是该进程异常就要注意了,因此平时的监控也是很要紧的。vacuum full Obj时会对该对象的DEAD ROWS进行清理,这个能够很容易模拟,UPDATE一个大表,VACUUM先后查看表的大小可看出变化。

检查数据库对象XID值的方法,age的值异常大的话就要注意了,可做为平常监控SQL之一:
SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r' order by 2 desc limit 20;
SELECT datname, age(datfrozenxid) FROM pg_database order by 2 desc limit 20;


转译参考:
http://en.wikipedia.org/wiki/Multiversion_concurrency_control
https://devcenter.heroku.com/articles/postgresql-concurrency
相关文章
相关标签/搜索