PostgreSQL的多版本并发控制
PostgreSQL表中的系统字段#
pgsql在创建表时,除了用户定义的字段外,还会有一些由系统自动创建的隐藏字段,这些隐藏字段无法通过“\d”命令展示出来。
因为表中已经有了这些隐含的字段,用户在创建表时,必须避免自定义的字段名称和这些隐含字段重复,这与字段名是否是关键字没有关系,即便使用双引号括起来也不行。这些系统字段包括如下:
- oid:行对象标识符,每行数据都会创建一个唯一的标识id(由于会影响性能,基本已经被弃用了);
- tableoid:包含本行的表oid,在对父表进行查询时,使用该字段可以知道某一行是来自父表还是哪个子表的;
- xmin:插入此行版本的事务id;
- xmax:删除此行时的事务id,如果查询出来的此字段不为0,说明可能删除这行的事务未提交或者回滚了;
- cmin:事务内部的插入类操作的命令id,从0开始递增的值;
- cmax:事务内部删除类操作的命令id,不是删除命令时,该字段值为0;
- ctid:一个行版本在它所处的表内的物理位置。
xmin,xmax,cmin,cmax#
这四个字段用于控制数据在多版本中是否对用户可见。
xmin和xmax控制数据在事务间的可见性
- 在插入一行数据时,该行的xmin会写入当前的事务id,xmax写入0;
- 在删除一行数据时,该行的xmax会写入当前的事务id;
- 在修改一行数据时,等价于删除该行数据并新插入一条对应的数据,于是数据原位置的xmin不变,xmax写入当前事务id,然后在新的位置创建一条对应的新数据,其xmin写入当前事务id,xmax写入0。
cmin和cmax用于判断在同一个事务内的命令对行数据的变化是否可见。
如果事务中的命令都是严格按照顺序执行的,这两个字段是没有用的。但如果事务中的命令是交错执行的,比如使用游标操作表中的数据,同时又对数据进行修改,就需要这两个字段控制数据的可见性。数据库的游标并不是直接查询库中的数据,而是在创建游标时会生成一个数据库的快照,游标指向的是快照中的数据位置,所以可以在遍历数据的同时对数据进行修改。当读取到某一行的数据时,会判断该行已有的命令id是否小于当前的命令id,如果小于,那么这一行数据是可见的,否则就是不可见的。
命令id在事务开始时被创建,一个事务中会由一个全局计数器统一分发命令id,事务开始时初始化为0,在执行SELECT
、UPDATE
、DELETE
或者SELECT...FOR UPDATE
等会操作数据的命令时,命令id加1。
一个事务中最多支持2^32-1个命令,当超过这个数量时计时器溢出,数据库报错。
ctid#
ctid字段的类型是tid,由一对数字(number,offset)组成,表示该行数据在表的第number数据块中,通过偏移offset个单位获得。
由于ctid可能会发生变化,所以不能作为数据的长期行标识符来使用,比如:
ubuntu=> select *, ctid from ttable ;
oid | ctid
-----+-------
1 | (0,1)
2 | (0,2)
3 | (0,3)
(3 rows)
ubuntu=> update ttable set oid = 10 where oid =1 ;
UPDATE 1
ubuntu=> select *, ctid from ttable ;
oid | ctid
-----+-------
2 | (0,2)
3 | (0,3)
10 | (0,4)
(3 rows)
多版本并发控制(MVCC)#
在并发操作时,可能会遇到读和写同时进行的情况,如果没有任何限制,可能一条数据刚写入一半,正好其他事务读取了这条数据,导致前一半是新值,后一半是旧值,即破坏了数据的一致性。
简单的做法是添加读写锁,读的时候阻塞写操作,写的时候阻塞读操作,这样可以保证数据的一致性,但是读写不能并发进行,牺牲了效率。
为了既能保证数据一致性,又尽可能提高效率,人们发明了多版本并发控制(Multi-Version Concurrency Control)机制,简称MVCC。原理是在写数据时并不删除原有数据,而是新添一条,这样读取时还能读到原数据,这样既不用加锁,也解决了数据一致性的问题。
MVCC有两种实现方式:
- 在写数据时,将原有数据移动到一个单独的位置,比如回滚段中,可以使用回滚指针找到,其他用户读数据时可以在回滚段中找到原数据,Oracle和MySQL多使用这种方式;
- 写数据时原数据不动,直接插入一条对应的新数据,PostgreSQL使用的是这种方式。
pgsql的MVCC#
正如上面提到,在创建表时,会创建几个隐藏的系统字段,其中xmin,xmax,cmin,cmax就是pgsql用来实现MVCC机制的。
事务在执行时,首先会参考xmin和xmax来确定数据的权限,当删除数据时,并不会直接将数据从数据块中删除,而是修改了对应数据段的值,占用的空间也不会被立即释放。AutoVaccum进程会负责实际物理空间的回收,当更新量达到一定值后,AutoVaccum进程会自动执行回收,或者是手动执行VACCUM命令进行回收。
在pgsql中,事务回滚后产生的脏数据并不会被删除,这主要是出于效率的考虑,因为事务在结束时往往会伴随着I/O操作,如果再删除掉脏数据,就需要额外再进行一次I/O操作。
如果不删除脏数据,如何判断数据是否是有效的呢?实际上,pgsql维护了一个记录事务状态的文件Commit Log,简称CLOG,通过读取数据行上xmin和xmax对应的事务状态,即可判断该行数据是否有效。这个文件保存在pg_clog子目录下。
事务状态 | 值 | 含义 |
---|---|---|
TRANSACTION_STATUS_IN_PROGRESS | 0x00 | 事务正在进行中 |
TRANSACTION_STATUS_COMMITTED | 0x01 | 事务已提交 |
TRANSACTION_STATUS_ABORTED | 0x02 | 事务已回滚 |
TRANSACTION_STATUS_SUB_COMMITTED | 0x03 | 子事务已提交 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)