在数据库中,并发的操作进行读写数据时,则会遇到脏读、不可重复读、幻读、串行化异常等问题。
数据库事务的特性:
数据库中存在4种事务隔离级别,读未提交、读已提交、可重复读和可序列化。
PostgreSQL在9.3版本后,已经支持了这四种标准的事务隔离级别。可以通过SET TRANSACTION命令设置当前事务的隔离级别(Transaction Isolation)。
PostgreSQL事务隔离级别和对应数据库问题的关系
隔离级别
脏读
不可重复读
幻读
串行化异常
读未提交
允许,但pg不支持
可能
可能
可能
读已提交
不可能
可能
可能
可能
可重复读
不可能
不可能
允许,但pg不支持
可能
可序列化
不可能
不可能
不可能
不可能
从上表中可以看到在PostgreSQL中,“读未提交”隔离级别,不允许脏读;“可重复读”隔离级别,不允许幻读。
在PostgreSQL中,MVCC的实现方法是:当插入或者更新一行数据时,旧数据不删除,而是插入一行新数据;通过使用事务id进行标记,把旧数据标记为过期,并保留在数据库直到垃圾收集器回收掉。
MVCC优势
MVCC缺点
在PostgreSQL中,使用元组头部信息(HeapTupleHeaderData)的字段来标示元组的版本号,元组头部信息的结构如下:
struct HeapTupleHeaderData
{
union
{
HeapTupleFields t_heap;
DatumTupleFields t_datum;
} t_choice;
ItemPointerData t\_ctid; /\* current TID of this or newer tuple (or a
\* speculative insertion token) \*/
/\* Fields below here must match MinimalTupleData! \*/
uint16 t\_infomask2; /\* number of attributes + various flags \*/
uint16 t\_infomask; /\* various flag bits, see below \*/
uint8 t\_hoff; /\* sizeof header incl. bitmap, padding \*/
/\* ^ - 23 bytes - ^ \*/
bits8 t\_bits\[FLEXIBLE\_ARRAY\_MEMBER\]; /\* bitmap of NULLs \*/
/\* MORE DATA FOLLOWS AT END OF STRUCT \*/
};
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; /* VACUUM FULL xact ID */
} t_field3;
} HeapTupleFields;
t_xmin 存储的是产生这个元组的事务ID,可能是insert或者update语句
t_xmax 存储的是删除或者锁定这个元组的事务ID
t_cid 包含cmin和cmax两个字段,分别存储创建这个元组的Command ID和删除这个元组的Command ID
t_xvac 存储的是VACUUM FULL 命令的事务ID
数据库中每一个事务中的查询仅能看到:该事务启动之前已经提交的事务所作出的数据更改;该事务之前启动的事务和该事务之后启动的事务修改的数据不可见。
Postgres中元组版本对一个事务可见,其事务TransactionID要满足以下条件:1. t_xmin
通过实际操作,观察元组头部信息中的t_xmin和t_xmax的变化。
开启事务,查看事务id,创建表并插入一条记录;再查看该记录的t_xmin。
[root@localhost ~]# su pguser
[pguser@localhost root]$ psql -d test
test=# create table t2(id int);
CREATE TABLE
test=# begin;
BEGIN
test=# select txid_current();
txid_current
( row)
test=# insert into t2(id) values();
INSERT
test=# commit;
COMMIT
test=# select ctid, xmin,xmax,cmin,cmax,id from t2;
ctid | xmin | xmax | cmin | cmax | id
-------+------+------+------+------+----
(,) | | | | |
( row)
先开启事务A,查看事务id。当事务B更新数据后,查看表数据信息,观察事务B更新数据前后的行数据信息。该行数据中t_xmax=763(事务Bid),表明该行被标记为过期,但是对该事务是可见的。
test=# -- 启动事务A
test=# begin;
BEGIN
test=# select txid_current();
txid_current
( row)
test=# select ctid, xmin,xmax,cmin,cmax,id from t2;
ctid | xmin | xmax | cmin | cmax | id
-------+------+------+------+------+----
(,) | | | | |
( row)
test= -- 事务B update 后
test=# select ctid, xmin,xmax,cmin,cmax,id from t2;
ctid | xmin | xmax | cmin | cmax | id
-------+------+------+------+------+----
(,) | | | | |
( row)
开启事务A后,再开启事务B,事务A的id比事务B的id小。在事务B中更新表数据,观察当前表的行数据信息,t_xmix=763。
test=# -- 启动事务B
test=# begin;
BEGIN
test=# select txid_current();
txid_current
( row)
test=# select ctid, xmin,xmax,cmin,cmax,id from t2;
ctid | xmin | xmax | cmin | cmax | id
-------+------+------+------+------+----
(,) | | | | |
( row)
test=# update t2 set id= where id=;
UPDATE
test=# select ctid, xmin,xmax,cmin,cmax,id from t2;
ctid | xmin | xmax | cmin | cmax | id
-------+------+------+------+------+----
(,) | | | | |
( row)
通过pageinspect的函数,查看page信息,发现表t2存在两条数据记录,与事务A、事务B的数据相对于。当事务A和事务B提交后,进行手动Vacuum清理后,只剩下一条最新的记录。
test=# select * from heap_page_items(get_raw_page('t2',));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------
| | | | | | | (,) | | | | | | \x01000000
| | | | | | | (,) | | | | | | \x02000000
( rows)
test=# -- commit 事务A 事务B
test=# vacuum full;
VACUUM
test=# select * from heap_page_items(get_raw_page('t2',));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
----+--------+----------+--------+--------+--------+----------+--------+-------------+------------+--------+--------+-------+------------
| | | | | | | (,) | | | | | | \x02000000
( row)
PostgreSQL引入了MVCC多版本机制,保证了事务的原子性和隔离性,实现不同的事务隔离级别。
PostgreSQL的MVCC实现方法有利有弊。从上面可以看到,多版本控制,会导致旧数据没有删除,最直接的问题就是导致表膨胀。PostgreSQL为了解决这个问题引入了AutoVacuum自动清理辅助进程,定时清理MVCC的过期数据。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章