事务
事务的正确执行使得数据库从一种状态转换为另一种状态
事务的特性(ACID)
A atomicity : 要么成功要么失败--undo,mvcc
C consistency : 状态一致,数据完整性约束--对开发者的要求
I isolation : 事务间相互隔离,不能影响--lock,undo,mvcc
D durability : 事务正确提交,必须永久保留--redo,wal
并发事务下的问题
脏读:读未提交,数据不一样(update)
不可重复读*:同一个事务读取到不同数据
幻读:记录不一样(delete/insert)
事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommit | 是 | 是 | 是 |
read commit--pg默认 | 否 | 是 | 是 |
repeatable read | 否 | 否 | 是 |
serialiable | 否 | 否 | 否 |
----pg实现了三种,READ UNCOMMITTED和READ COMMITTED 一样
pg中的事务控制
- begin :开启一个事务
- commit : 提交
- rollback :回滚
- psql : 缺省autocommit-- \set AUTOCOMMIT off 关闭
- 默认autocommit
MVCC
Multi-Version Concurrency Control:多版本并发控制
pg的实现方法:update/delete保留原有行,重新insert一条记录,旧的数据由vacuum做清理
事务号TXID
- 每当事务开启时,事务管理器都会分配一个唯一的标识符,称为事务ID(TXID)
- txid 32位无符号整数,最大2^32
- 事务启动会执行内置txid_current(),返回当前txid
- txid_current() 获取当前事务ID,当前无事务,分配一个
- txid_current_if_assigned() 同上,当前无事务,返回null
事务的相关信息
存在于tuple head
- xmin :插入该行版本的事务ID
- xmax:删除此行的事务ID(delete/update),查询出来>0,说明未commit or rollback
- cmin:insert,该事务中dml的序列,从0开始
- cmax:update/delete,该事务中dml的序列,从0开始
- ctid:行版本在其表中物理位置,类似于oracle rowid(),update和vacuum full会被改变
事务实现
每行有xmin和xmax两个字段
- insert:xmin为当前事务ID,xmax为0
- update:实际上insert一个新行,同上。同时,旧行的xmax置为当前事务ID
- delete:将当前行的xmax设置为当前事务ID
读到一行时,查询xmin和xmax对应的事务状态:committed、aborted、in progress,判断次行对当前行是否可见
事务ID的增长
- 1.不会无限增长,最大2^32
- 2.txid到最大值,又会从最小值3开始
- 0:InvalidXID,无效事务ID
- 1:BootstrapXID,表示系统表初始化时的事务ID,比任何普通的事务ID都旧
- 2:FrozenXID,冻结的事务ID,比任何普通事务ID都旧
- 3.同一个数据库中,存在的最旧和最新两个事务之间的年龄允许最大为2^31
- pg的事务号最多只占2^32 序号中的一半
- pg定期清理数据文件中过老的事务ID,使他们比所有普通事务ID都旧
事务可见性判定
- 1.普通事务的比较方法
((int32)(id1 - id2)) < 0
表达式为真,则id1比id2旧,为假则id1比id2新
最大事务ID为2^32 ,最小事务id为2^31 +1
2^32 - (2^31+2) = 2147483646
二进制
111 1111 1111 1111 1111 1111 1111 1110 > 0
最大事务ID为2^32,此时再来一个新事务,回卷之后为3
2^32 - 3 = 4294967293
二进制
1111 1111 1111 1111 1111 1111 1111 1101 < 0
- 2.BootstrapXID比所有其他事务都旧,包括FrozenXID
- 3.FrozenXID比普通事务旧
- 4.数据中的每个sql都会有自己的事务号,这个事务号会记入SnapshotData,通过快照中的txid和tuple中的xmin和xmax对比,判断出当前行对当前事务是否可见.
事务快照相关函数
txid_current_snapshot() 获取当前快照
txid_snapshot_xip(txid_snapshot) 获取在快照中进行中的事务ID
txid_snapshot_xmax(txid_snapshot) 获取快照的 xmax
txid_snapshot_xmin(txid_snapshot) 获取快照的xmin
txid_visible_in_snapshot(bigint,txid_current_snapshot) 在快照中事务ID是否可见(不使用子事务ID)
txid_status(bigint) 获取当前事务的状态,committed、aborted、in progress 或者如果事务ID太老,则是null
获取当前快照
txid_current_snapshot()
格式xmin:xmax:xip_list
- xmin:最早仍活跃的事务ID(以下简称XID),早于此XID的事务要么被提交并可见,要么回滚要么丢弃。
- xmax:最后已完结事务(COMMITTED/ABORTED)的事务ID + 1。
- xip_list:在”拍摄”快照时仍进行中的事务ID。该列表包含xmin和xmax之间的活动事务ID。
总结一下,简单来说,对于给定的XID:
XID ∈ [1,xmin),过去的事务,对此快照均可见;
XID ∈ [xmin,xmax),不考虑子事务的情况,仍处于IN_PROGRESS状态的,不可见;COMMITED状态,可见;ABORTED状态,不可见;
XID ∈ [xmax,∞),未来的事务,对此快照均不可见;
--session 1
testdb=# begin;
BEGIN
testdb=# select txid_current();
txid_current
--------------
1303
testdb=# select txid_current_snapshot();
txid_current_snapshot
-----------------------
1303:1303:
--session 2
testdb=# begin;
BEGIN
testdb=# select txid_current();
txid_current
--------------
1304
--session 3
testdb=# begin;
BEGIN
testdb=# select txid_current();
txid_current
--------------
1305
--session 4
testdb=# begin;
BEGIN
testdb=# select txid_current();
txid_current
--------------
1306
--session 1
testdb=# select txid_current_snapshot();
txid_current_snapshot
-----------------------
1303:1303:
--session 4
testdb=# rollback;
ROLLBACK
--session 1
testdb=# select txid_current_snapshot();
txid_current_snapshot
-----------------------
1303:1307:1304,1305
--session 3
testdb=# rollback;
ROLLBACK
--session 1
testdb=# select txid_current_snapshot();
txid_current_snapshot
-----------------------
1303:1307:1304
网友评论