8.事务浅析

事务

事务的正确执行使得数据库从一种状态转换为另一种状态

事务的特性(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
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容