MVCC的实现方法
MVCC的实现方法有两种:
-
写新数据时,把旧数据移到一个单独的地方,如回滚段中,其他人读数据时,从回滚段中把旧的数据读出来;
-
写数据时,旧数据不删除,而是把新数据插入。
PostgreSQL MVCC
和Oracle
还有MySQL
中不同的是,PostgreSQL
中没有undo
这一概念,PostgreSQL
中的多版本并发是通过在表中数据行的多个版本来实现的,例如在一张表中我们要更新一条记录,PostgreSQL
并不是直接修改该数据,而是通过插入一条全新的数据,同时对老数据加以标识。
PostgreSQL的MVCC实现方式的优缺点如下:
- 优点:
-
事务回滚可以立即完成,无论事务进行了多少操作;
-
数据可以进行很多更新,不必像
Oracle
和MySQL
的Innodb引擎那样需要经常保证回滚段不会被用完,也不会像Oracle
数据库那样经常遇到“ORA-1555”错误的困扰;
- 缺点:
-
旧版本数据需要清理。
PostgreSQL
清理旧版本的命令成为Vacuum
; -
旧版本的数据会导致查询更慢一些,因为旧版本的数据存在于数据文件中,查询时需要扫描更多的数据块。
使用MVCC并发控制模型而不是锁定的主要优点是在MVCC中,对查询(读)数据的锁请求与写数据的锁请求不冲突,所以读不会阻塞写,而写也从不阻塞读。甚至在通过使用革新的可序列化快照隔离(SSI)级别提供最严格的事务隔离级别时,PostgreSQL
也维持这个保证。
在PostgreSQL
里也有表和行级别的锁功能,用于那些通常不需要完整事务隔离并且想要显式管理特定冲突点的应用。不过,恰当地使用MVCC通常会提供比锁更好的性能。另外,由应用定义的咨询锁
提供了一个获得不依赖于单一事务的锁的机制。
PostgreSQL的MVCC实现方式
通过HeapTupleFields:
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* inserting xact ID */
TransactionId t_xmax; /* deleting or locking xact ID */
union
{
CommandId t_cid; /* inserting or deleting command ID, or both */
TransactionId t_xvac; /* old-style VACUUM FULL xact ID */
} t_field3
} HeapTupleFields;
PostgreSQL
的MVCC实现是比较简单的。只需要通过对比tuple header中xmin,xmax,cmin,cmax与当前的xid,就可以得到在scan tuple时,此tuple对于当前查询的可视性。
几个关键的字段:
- t_xmin:插入该元组的事务的txid;
- t_xmax:删除或更新该元组的事务的txid,如果尚未删除或更新该元组,则t_xmax设置为0;
- t_cid:命令ID(cid),这表示从0开始在当前事务中执行此命令之前已执行了多少个SQL命令;
- t_ctid:指向自身或新元组的元组标识符(tid),当该元组被更新时,该元组的t_ctid指向新的元组,否则,t_ctid指向自身。
我们可以通过一个简单的例子来看看:
select xmin,xmax,ctid,* FROM accounts;
xmin |xmax |ctid |id|user_name|balance |
------+------+------+--+---------+---------+
101231|0 |(0,4) | 2|UserB | 0.0000|
139206|139206|(0,14)| 1|UserA |8000.0000|
2 row(s) fetched.
更新该表中的数据:
UPDATE accounts SET balance = 5000 WHERE id = 1;
可以看到id=1
新记录的xmin
被置为202860,ctid
指向新的记录。
select xmin,xmax,ctid,* FROM accounts;
xmin |xmax|ctid |id|user_name|balance |
------+----+------+--+---------+---------+
101231|0 |(0,4) | 2|UserB | 0.0000|
202860|0 |(0,15)| 1|UserA |5000.0000|
2 row(s) fetched.