PostgreSQL-并发控制
一、事务标识
当事务开始时,事务管理器会为其分配一个事务标识(txid)的唯一标识符。
PostgreSQL有三个特殊txid:
- 0标识无效的txid;
- 1表示初始启动的txid,仅用于数据库集群的初始化过程;
- 2表示冻结的txid。
txid在逻辑上是无限的,但实际系统的txid空间不足(4B整型的取值空间大小约42亿),因此Postgresql将txid空间视为一个环。对于某个txid,其前21亿个txid属于过去,其后约21亿个txid属于未来。
二、元组结构
1 | typedef struct HeapTupleFields |
t_xmin:代表插入此元组的事务xid;
t_xmax:代表更新或者删除此元组的事务xid,如果该元组插入后未进行更新或者删除,t_xmax=0;
t_cid:command id,代表在当前事务中,已经执行过多少条sql,例如执行第一条sql时cid=0,执行第二条sql时cid=1;
t_ctid:保存着指向自身或者新元组的元组标识(tid),由两个数字组成,第一个数字代表物理块号,或者叫页面号,第二个数字代表元组号。在元组更新后tid指向新版本的元组,否则指向自己,这样其实就形成了新旧元组之间的“元组链”,这个链在元组查找和定位上起着重要作用。
三、元组的增、删、改
1、更新元组
左图是一条新插入的元组,可以看到元组是xid=100的事务插入的,没有进行更新,所以t_xmax=0,同时t_ctid指向自己,0号页面的第一号元组。
右图是发生xid=101的事务更新该元组后的状态,更新在pg里相当于插入一条新元组,原来的元组的t_xmax变为了更新这条事务的xid=101,同时t_ctid指针指向了新插入的元组(0,2),0号页面第二号元组,第二号元组的t_xmin=101(插入该元组的xid),t_ctid=(0,2),没有发生更新,指向自己。
2、删除元组
上图代表该元组被xid=102的事务删除,将t_xmax设置为删除事务的xid,t_ctid指向自己。
四、提交日志(clog)
Postgresql在提交日志(CLOG)中保存事务的状态。CLOG分配于共享内存中,并用于事务处理过程的全过程。
每次事务提交和回滚的时候,都需要更新该状态(调用CommitTransactionCommand(void)),PostgreSQL服务器访问该文件确定事务的状态,保存在pg_xact目录中,每个文件大小为256KB,每个事务2位(bit),故1个文件可以包含131072个事务。对于第一次修改的数据行来说,因为事务状态存储在clog中,所以修改后第一次判断行的可见性需要通过访问clog来确定,而访问clog是一个非常耗费性能的过程,
1、事务状态
- IN_PROGRESS
- COMMITTED
- ABORTED
- SUB_COMMITTED
1 | /* |
2、事务特性
1 | /* We need two bits per xact, so four xacts fit in a byte */ |
-
事务id并不是在事务开始时就会被分配,而是当事务进行了数据修改之类的操作时才会分配xid,而当事务提交或回滚时,其事务状态便会被写入clog中。
-
前一个事务所有修改的数据,它没有在提交或者回滚的当时改掉所有的修改标记,而是等到下次查询时进行修改(为了提升性能)。
五、事务快照
1、快照定义
事务快照在postgresql中的文本表示格式为xmin:xmax:xip_list。100:100:意味着txid < 100的事务处于非活跃状态,而txid ≥ 100的事务处于活跃状态。
100:100:含义如下
- xmin=100,所以txid<100的事务均不活跃
- xmax=100,所以txid>=100的事务均活跃或未启动
- xip_list为空,表示[xmin,xmax)范围内无活跃事务
100:104:100,102含义如下
- xmin=100,所以txid<100的事务均不活跃
- xmax=104,所以txid>=104的事务均活跃或未启动
- xip_list为100,102,表示[xmin,xmax)范围内100,102为活跃事务
2、快照与隔离级别
pg会根据不同隔离级别设置,获取不同时刻的快照:
- 已提交读:在该事务的每条SQL执行之前都会重新获取一次快照
- 可重复读和可串行化:该事务只在第一条SQL执行之前获取一次快照
1 | // 快照相关的数据结构 |
六、可见性检查
事务并发可能出现的现象:
-
脏读:某一事务读取了另一个事务未提交的脏数据。
-
幻读:读取了前一事务提交的数据(针对的是一批数据整体,比如数据的个数 )。
-
不可重复读:读取了前一事务提交的数据(查询的都是 同一个数据项 )。
事务隔离级别:
- READ UNCOMMITTED(读未提交)------ postgresql不存在这个隔离级别,所以不会出现脏读的现象。
- READ COMMITTED(读已提交)------ 可能会发生幻读和不可重复读。
- REPEATABLE READ(可重复读)------ 不会发生幻读和不可重复读(标准sql事务隔离级别允许幻读)。
- SERIALIZABLE(序列化)------ 不会发生幻读和不可重复读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | Allowed, but not in PG |
可串行化 | 不可能 | 不可能 | 不可能 |
元组的可见性:
确定一条元组是否对一个事务可见,可见性检查规则会用到元组的t_xmin和t_xmax,提交日志ClOG,以及已获取的事务快照。
t_xmin状态为 ABORTED的元组始终不可见
t_xmin状态为IN_PROGRESS的元组基本上是不可见的(对自身事务可见)
t_xmin状态为COMMITTED的元组是可见的