用pageinspect浅探下PostgreSQL的MVCC
Whoami:5年+金融、政府、医疗领域工作经验的DBA
Certificate:OCP、PCP
Skill:Oracle、Mysql、PostgreSQL
Platform:CSDN、墨天伦、公众号(呆呆的私房菜)
业务范围:数据库安装部署、日常维护、主备切换、故障处理、性能优化、技术培训等。
需要的伙伴或者商业合作请移步 公众号【呆呆的私房菜】获取联系方式。
01 MVCC概述
到底什么是MVCC呢?
- MVCC即多版本并发控制, 是一种用于处理数据库中并发操作的机制;
- 在MVCC中,每个数据库事务在读取数据时会看到一个特定的版本,这使得事务之间可以同时进行读写操作,并且不会相互干扰。每个事务可以操作自己的数据版本,从而实现了更高的并发性和更好的性能;
- MVCC核心思想是对每个修改此操作,不是直接在原始数据上进行修改,而是创建一个新的数据版本,并将修改操作应用于新版本,这样其它事务仍可以访问旧版本数据,而不会受到正在进行的修改的影响。只有在事务提交时,新版本的数据才会替代旧版本,从而实现数据的一致性。
- MVCC在数据库中起作用的原理是通过使用版本号、时间戳或类似的标识来管理数据版本,并通过一定的规则来决定哪些版本对于每个事务是可见的。
02 MVCC实现方法
第一种: 写新数据时,元数据移到一个单独的位置,如回滚段中,其他用户读数据时,从回滚段中把元数据读出来。
第二种: 写新数据时,原数据不删除,而是把新数据插入进来。
03 PostgreSQL MVCC
PostgreSQL数据库是通过把原数据保留在数据文件中,新插入一条数据来实现多版本并发功能的。
我们可以通过pageinspect插件来窥视PostgreSQL的多版本控制工作过程。
shell
## 为实现MVCC,PostgreSQL数据库的每张表上都存在4个系统字段:
## 1. xmin:表示记录的第一个可见事务的 XID;
## 2. xmax:表示记录的最后一个修改它的事务的 XID;
## 3. cmin:表示命令ID的最小值;
## 4. cmax:表示命令ID的最大值。
postgres=# create extension pageinspect;
CREATE EXTENSION
postgres=# create table a1 (id int);
CREATE TABLE
postgres=# insert into a1 values (1),(2);
INSERT 0 2
## 不收集统计信息前,看不到当前元组使用的数据页和元组数量
postgres=# select relname, relpages, reltuples from pg_class where relname = 'a1';
relname | relpages | reltuples
---------+----------+-----------
a1 | 0 | -1
(1 row)
## 收集统计信息
postgres=# analyze a1;
ANALYZE
## 表a1占用了1个数据页,一共有2个元组
testdb=# select relname, relpages, reltuples from pg_class where relname = 'a1';
relname | relpages | reltuples
---------+----------+-----------
a1 | 1 | 2
(1 row)
## heap_page_items函数用于获取数据库堆表页上条目的信息
## lp: 指向下一条记录的指针。如果这是页上的最后一条记录,这个值通常是指向页末尾的偏移量。
## lp_off: 记录在页上的偏移量。
## lp_flags: 记录的一些状态标志,比如是否是脏页(dirty)。
## lp_len: 记录的长度。
## t_xmin: 插入这条记录的事务的最小事务 ID(XID)。如果这个值是 InvalidTransactionId(0),表示这条记录是可见的。
## t_xmax: 最后一次删除或更新这条记录的事务的事务 ID。如果这个值是 0,表示这条记录没有被当前事务删除或更新。
## t_field3: 这个字段通常用于存储额外的事务信息,但具体内容取决于 PostgreSQL 的版本。
## t_ctid: 当前记录的复合索引,包含块号和行号。
## t_infomask2: 一个掩码,用于存储记录的额外信息,比如是否是只读副本。
## t_infomask: 另一个掩码,用于存储记录的元数据信息,比如是否是压缩的或是否包含 OID。
## t_hoff: 指向记录头信息的偏移量。
## t_bits: 一个位图,用于存储记录的可见性和其他信息。
## t_oid: 如果记录包含 OID 列,则这个字段存储该值。
## t_data: 记录的实际数据,通常以字节序列的形式存储。
## 输出解读:
## 1. 两条记录的t_xmin,说明是由同个事务插入的
## 2. t_xmax没有记录代表没有事务更新或删除这条记录
## 3. t_ctid显示了记录的当前位置
## postgres=# select * from heap_page_items(get_raw_page('a1',0));
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
----+--------+----------+--------+------------+--------+----------+--------+-------------+------------+--------+--------+-------+------------
1 | 8160 | 1 | 28 | 2146483941 | 0 | 0 | (0,1) | 1 | 2304 | 24 | | | \x01000000
2 | 8128 | 1 | 28 | 2146483941 | 0 | 0 | (0,2) | 1 | 2304 | 24 | | | \x02000000
(2 rows)
## 更新一条数据
postgres=# update a1 set id = 22 where id = 2;
UPDATE 1
## 输出解读:
## 1. 新增了一条xid为2146483943的记录,并且记录了更新后的数据
## 2. 被更新的记录并没有被删除,而是被标记为旧版本,数据仍保存在原数据文件中。
postgres=# select * from heap_page_items(get_raw_page('a1',0));
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
----+--------+----------+--------+------------+------------+----------+--------+-------------+------------+--------+--------+-------+------------
1 | 8160 | 1 | 28 | 2146483941 | 0 | 0 | (0,1) | 1 | 2304 | 24 | | | \x01000000
2 | 8128 | 1 | 28 | 2146483941 | 2146483943 | 0 | (0,3) | 16385 | 256 | 24 | | | \x02000000
3 | 8096 | 1 | 28 | 2146483943 | 0 | 0 | (0,3) | 32769 | 10240 | 24 | | | \x16000000
(3 rows)
动动手,做做试验吧!通过上边小小的实验我们就可以看到PostgreSQL的多版本并发控制的实现原理啦!