PostgreSQL如何实现MVCC (基于xmin、xmax、cmin、cmax)
声明:本文是《PostgreSQL实战》读书笔记,参考了http://www.jasongj.com/sql/mvcc/ 部分,可以参考该书事务与并发控制章节 和 http://www.jasongj.com/sql/mvcc/
PostgreSQL如何实现MVCC (基于xmin、xmax、cmin、cmax)
一、基于多版本的并发控制
在PostgreSQL中,会为每一个事务分配一个递增的、类型为int32的整型数作为唯一的一个ID,称为xid。可通过txid_current()
函数获取当前事务的ID。PostgreSQL中,对于每一行数据(称为一个tuple),包含有4个隐藏字段,分别是xmin、xmax、cmin、cmax。这四个字段是隐藏的,但可直接访问。创建一个快照时,将收集当前正在执行的事务id 和 已经提交的最大事务id, 根据快照信息,PostgreSQL可以确定事务的操作是否对执行语句是可见的 。
cmin
和 cmax
分别是插入和删除该元组的命令在事务中的命令标识。( xmin
: 在创建(insert)记录(tuple)时,记录此值为插入tuple的事务ID; xmax
: 默认值为0.在删除tuple时,记录此值)
测试表准备:
create table tb_mvcc(id int PRIMARY KEY,ival int
);
insert into tb_mvcc values(1,1);
启动psql
[root@instance-o5o8g5v0 ~]# su postgres
bash-4.2$ psql technology postgres
could not change directory to "/root"
psql (9.2.24, server 10.8)
WARNING: psql version 9.2, server version 10.0.Some psql features might not work.
Type "help" for help.
technology=#
可以通过sql直接查询四个值
technology=# SELECT xmin,xmax,cmin,cmax,id,ival FROM tb_mvcc WHERE id = 1;xmin | xmax | cmin | cmax | id | ival
------+------+------+------+----+------630 | 0 | 0 | 0 | 1 | 1
(1 row)
二、 通过xmin决定事务的可见性
当插入一行数据时,PostgreSQL会将插入这行数据的事务的xid存储在xmin中。通过xmin值判断事务中插入的行记录对其他事务的可见性有两种情况
(一)由回滚的事务或未提交的事务创建的行对于任何其他事务都是不可见的。
开启一个新的事务,如下所示:
technology=# begin;
BEGIN
technology=# SELECT txid_current();txid_current
--------------631
(1 row)technology=# INSERT INTO tb_mvcc(id,ival) VALUES(2,2);
INSERT 0 1
technology=# SELECT xmin,xmax,cmin,xmax,id,ival FROM tb_mvcc WHERE id = 2;xmin | xmax | cmin | xmax | id | ival
------+------+------+------+----+------631 | 0 | 0 | 0 | 2 | 2
(1 row)
SELECT txid_current()
;查询当事务的xid
是631
。可以看到这条新数据的隐藏列xmin
值为631
。
开启另外一个事务,如下所示:
technology=# BEGIN;
BEGIN
technology=# SELECT txid_current();txid_current
--------------632
(1 row)technology=# SELECT xmin,xmax,cmin,xmax,id,ival FROM tb_mvcc WHERE id = 2;xmin | xmax | cmin | xmax | id | ival
------+------+------+------+----+------
(0 rows)technology=# END;
COMMIT
可以看见由于第一个事务并未提交,所以第一个事务对第二个事务是不可见的。
(二)无论提交成功或回滚的事务,xid 都会递增,对于repeatable read 和 serializable 隔离级别的事务,如果它的xid 小于另外一个事务的xid 。也就是xmin小于另外一个事务的xmin,那么另外一个事务对这个事务是不可见的。而read committed 则不会
注意在 read committed(对已提交): PostgreSQL的默认隔离级别,它满足了一个事务只能看见已经提交事务对关联数据所做的改变的隔离需求。 该隔离级别可能出现 不可重复读 和 幻读。 演示一下:
不可重复读 : 当一个事务第一次读取数据之后,被读取的数据被另一个已经提交的事务进行了修改,事务再次读取这些数据发现数据已经被另一个事务修改,两次查询的结果不一致,这种读现象称为不可重复读。
- 设置一个
read committed
对已提交隔离级别,
technology=# begin transaction isolation level read committed;
BEGIN
technology=# SELECT ival FROM tb_mvcc WHERE id = 1;id | ival
----+------1 | 1
(1 row)
- 另外一个事务对id=1 的进行修改,并
commit
technology=# BEGIN;
BEGIN
technology=# update tb_mvcc set ival = 11 where id = 1;
UPDATE 1
technology=# commit;
- 在从第一个事务进行读取时,发现数据已经被修改,即在同一个事务中两次读取结果不一致。发现ival 被修改成了11
technology=# SELECT id,ival FROM tb_mvcc WHERE id = 1;
id | ival
----+------1 | 11
(1 row)
完整信息如下所示:
technology=# begin transaction isolation level read committed;
BEGIN
technology=# SELECT xmin,xmax,cmin,xmax,id,ival FROM tb_mvcc WHERE id = 1;xmin | xmax | cmin | xmax | id | ival
------+------+------+------+----+------630 | 0 | 0 | 0 | 1 | 1
(1 row)technology=# SELECT xmin,xmax,cmin,xmax,id,ival FROM tb_mvcc WHERE id = 1;xmin | xmax | cmin | xmax | id | ival
------+------+------+------+----+------635 | 0 | 0 | 0 | 1 | 11
(1 row)technology=# END;
COMMIT
另外
read committed
还可能出现幻读。
验证一下,repeatable read
和 serializable
隔离级别的事务:
technology=# begin transaction isolation level repeatable read;
BEGIN
technology=# SELECT txid_current();txid_current
--------------636
(1 row)
上面语句开启repeatable read重复读隔离级别的一个事务,这个事务的xid是636。再开启另外一个事务,如下所示:
technology=# BEGIN;
BEGIN
technology=# SELECT txid_current();txid_current
--------------637
(1 row)technology=# INSERT INTO tb_mvcc(id,ival) VALUES(4,4);
INSERT 0 1
technology=# SELECT xmin,xmax,cmin,xmax,id,ival FROM tb_mvcc WHERE id = 4;xmin | xmax | cmin | xmax | id | ival
------+------+------+------+----+------637 | 0 | 0 | 0 | 4 | 4
(1 row)technology=# COMMIT;
COMMIT
第二个事务的xid 是637。并在第二个事务中插入一条数据,并成功commit。 然后再回到第一个事务中查询第二个数据提交的数据。如下所示:
technology=# SELECT xmin,xmax,cmin,xmax,id,ival FROM tb_mvcc WHERE id = 4;xmin | xmax | cmin | xmax | id | ival
------+------+------+------+----+------
(0 rows)
由于第一个事务的xid小于第二个事务的xid。所以插入的数据在第一个事务中不可见。正好跟read committed 事务隔离级别形成对比
PostgreSQL 的事务隔离级别与读现象的关系
隔离级别 | 脏读 | 不可重复读 | 幻读 | 序列化异常 |
---|---|---|---|---|
Read Uncommitted | 不可能 | 可能 | 可能 | 可能 |
Read Committed | 不可能 | 可能 | 可能 | 可能 |
Repeatable Read | 不可能 | 不可能 | 不可能 | 可能 |
Serializable | 不可能 | 不可能 | 不可能 | 不可能 |
postgresql内部将 Read uncommitted与Read Committed 设计成一样。也就是postgresql数据库中不会出现脏读。(可能会出现不可重复读和幻读)。而postgresql的Repeatable Read 实现不允许幻读。 这种隔离级别与其他数据库定义隔离级别稍有不同。
三、通过xmax 决定事务的可见性
- 如果没有设置xmax值,该行对其他事务总是可见的。
- 如果它被设置为回滚事务的xid,该行对其他事务可见
- 如果它被设置成为一个正在运行,灭有commit 和 rollback的事务xid。该行对其他事务是可见的。
- 如果它被设置为一个已经提交的事务的xid。该行对在这个已提交的事务之后发起的所有事务都是不可见的。
四、通过pageinspect 观察 MVCC
可以使用pageinspect 这个外部扩展来观察数据库页面的内容。pageinspect 提供了一些函数可以得到数据库的文件系统中页面的详细内容,使用之前先在数据库中创建扩展:
technology=# create extension pageinspect;
CREATE EXTENSION
technology=# \dx+ pageinspectObjects in extension "pageinspect"Object Description
-------------------------------------------------------------------function brin_metapage_info(bytea)
……
(26 rows)
创建如下视图,为了更清楚的观察PostgreSQL的MVCC是如何控制并发时得多版本的。
DROP VIEW IF EXISTS v_pageinspect;
CREATE VIEW v_pageinspect AS (SELECT '(0,' || lp || ')' AS ctid,CASE lp_flagsWHEN 0 THEN 'unsed'WHEN 1 THEN 'normal'WHEN 2 THEN 'redirect to ' || lp_offWHEN 3 THEN 'dead'END,t_xmin::text::int8 AS xmin,t_xmax::text::int8 AS xmax,t_ctid
FROM heap_page_items(get_raw_page('tb_mvcc',0)))
ORDER BY lp;
对表tb_mcc 清空数据操作: TRUNCATE TABLE tb_mvcc;
注意关闭所有的事务,否则会删除失败。别用delete,不然v_pageinspect 不能清除。
不考虑并发的情况:当insert 数据时,事务将insert 的数据的xmin值设置为当前事务的xid,xmax设置为0。
technology=# begin;
BEGIN
technology=# SELECT txid_current();txid_current
--------------648
(1 row)technology=# INSERT INTO tb_mvcc(id,ival) values(1,1);
INSERT 0 1
technology=# SELECT * FROM v_pageinspect;ctid | case | xmin | xmax | t_ctid
-------+--------+------+------+--------(0,1) | normal | 648 | 0 | (0,1)
(1 row)technology=# END;
COMMIT
在另外一个事务中,delete数据时,将xmax 的值设置为当前事务的xid。 如所示:
technology=# BEGIN;
BEGIN
technology=# SELECT txid_current();txid_current
--------------649
(1 row)technology=# DELETE FROM tb_mvcc WHERE id = 1;
DELETE 1
technology=# SELECT * FROM v_pageinspect;ctid | case | xmin | xmax | t_ctid
-------+--------+------+------+--------(0,1) | normal | 648 | 649 | (0,1)
(1 row)
当UPDATE 数据时,对于每个更新的行,首先DELTE原先行,再执行INSERT。如下所示:
INSERT INTO tb_mvcc(id,ival) values(2,2); -- 预先插入数据
technology=# BEGIN;
BEGIN
technology=# SELECT txid_current();txid_current
--------------661
(1 row)
-- 当前事务xid 661
technology=# SELECT * FROM tb_mvcc;id | ival
----+------2 | 2
(1 row)technology=# SELECT * FROM v_pageinspect;ctid | case | xmin | xmax | t_ctid
-------+--------+------+------+--------(0,1) | normal | 660 | 0 | (0,1)
(1 row)technology=# UPDATE tb_mvcc SET ival = 20 WHERE id = 2;
UPDATE 1
technology=# SELECT * FROM v_pageinspect;ctid | case | xmin | xmax | t_ctid
-------+--------+------+------+--------(0,1) | normal | 660 | 661 | (0,2)(0,2) | normal | 661 | 0 | (0,2)
(2 rows)
通过pageinspect 查看page的内部,可以看见update 实际上是先delete 先前的数据(可以看前一个例子),再insert 一行新的数据。在数据库中就存在两个版本,一个是被update 之前的那条数据,另外一个是update之后被重新插入的那条数据。
五、其他
5.1 MVCC保证原子性
原子性(Atomicity)指得是一个事务是一个不可分割的工作单位,事务中包括的所有操作要么都做,要么都不做。
对于插入操作,PostgreSQL会将当前事务ID存于xmin中。对于删除操作,其事务ID会存于xmax中。对于更新操作,PostgreSQL会将当前事务ID存于旧数据的xmax中,并存于新数据的xin中。换句话说,事务对增、删和改所操作的数据上都留有其事务ID,可以很方便的提交该批操作或者完全撤销操作,从而实现了事务的原子性。
5.2 PostgreSQL中的MVCC优势
- 使用MVCC,读操作不会阻塞写,写操作也不会阻塞读,提高了并发访问下的性能
- 事务的回滚可立即完成,无论事务进行了多少操作
- 数据可以进行大量更新,不像MySQL和Innodb引擎和Oracle那样需要保证回滚段不会被耗尽
5.3 PostgreSQL中的MVCC缺点
事务ID个数有限制
事务ID由32位数保存,而事务ID递增,当事务ID用完时,会出现wraparound问题。
PostgreSQL通过VACUUM机制来解决该问题。对于事务ID,PostgreSQL有三个事务ID有特殊意义:
- 0代表invalid事务号
- 1代表bootstrap事务号
- 2代表frozon事务。frozon transaction id比任何事务都要老
可用的有效最小事务ID为3。VACUUM时将所有已提交的事务ID均设置为2,即frozon。之后所有的事务都比frozon事务新,因此VACUUM之前的所有已提交的数据都对之后的事务可见。PostgreSQL通过这种方式实现了事务ID的循环利用。
5.4 大量过期数据占用磁盘并降低查询性能
由于上文提到的,PostgreSQL更新数据并非真正更改记录值,而是通过将旧数据标记为删除,再插入新的数据来实现。对于更新或删除频繁的表,会累积大量过期数据,占用大量磁盘,并且由于需要扫描更多数据,使得查询性能降低。
PostgreSQL解决该问题的方式也是VACUUM机制
。从释放磁盘的角度,VACUUM分为两种
VACUUM: 该操作并不要求获得排它锁,因此它可以和其它的读写表操作并行进行。同时它只是简单的将dead tuple对应的磁盘空间标记为可用状态,新的数据可以重用这部分磁盘空间。但是这部分磁盘并不会被真正释放,也即不会被交还给操作系统,因此不能被系统中其它程序所使用,并且可能会产生磁盘碎片。
VACUUM FULL :需要获得排它锁,它通过“标记-复制”的方式将所有有效数据(非dead tuple)复制到新的磁盘文件中,并将原数据文件全部删除,并将未使用的磁盘空间还给操作系统,因此系统中其它进程可使用该空间,并且不会因此产生磁盘碎片。
参考
- 《PostgreSQL实战》
- http://www.jasongj.com/sql/mvcc/
- https://blog.csdn.net/qq_31156277/article/details/84310746
PostgreSQL如何实现MVCC (基于xmin、xmax、cmin、cmax)相关推荐
- PostgreSQL中的MVCC机制
MVCC,Multi-Version Concurrency Control,多版本并发控制. 一句话讲,MVCC就是用同一份数据临时保留多版本的方式,实现并发控制.它可以避免读写事务之间的互相阻塞, ...
- KingbaseES 中的xmin,xmax等系统字段说明
在KingbaseES中,当我们创建一个数据表时,数据库会隐式增加几个系统字段.这些字段由系统进行维护,用户一般不会感知它们的存在. 例如,以下语句创建了一个简单的表: create table te ...
- PostgreSQL 从cmin/cmax到combo cid
cmin和cmax介绍 cmin和cmax是PostgreSQL中表的系统字段之一,用来判断同一个事务内的其他命令导致的行版本变更是否可见.即在事务中每个命令都应该能看到其之前执行的命令的变更. 很多 ...
- ArcGIS计算图斑的四邻坐标(XMin,XMax,YMin,YMax)
1.背景: 在国土,调查等行业业务里面经常有需要计算某个图斑的四邻坐标,即xmax,xmin,ymin,ymax;也就是常说的MBR(最小外包矩形),本教程演示如何计算一个shapefile文件上的图 ...
- darknetYolov3图片的分类计数、置信度以及输出xmin,xmax,ymin,ymax的结果
之前一直没找到c语言的darknet的计数代码,就自己改了.就是一个计数器再显示在识别框上,输出xmin,xmax,ymin,ymax的坐标是因为比赛需要,然而大佬们都不用yolo了根本拼不过...很 ...
- [转]ArcGIS计算图斑的四邻坐标(XMin,XMax,YMin,YMax)
1.背景: 在国土,调查等行业业务里面经常有需要计算某个图斑的四邻坐标,即xmax,xmin,ymin,ymax;也就是常说的MBR(最小外包矩形),本教程演示如何计算一个shapefile文件上的图 ...
- PostgreSQL 物流调度算法探索 - 基于PostGIS/pgrouting/机器学习
PostgreSQL 物流调度算法探索 - 基于PostGIS/pgrouting/机器学习 digoal 2016-07-20 17:57:13 浏览389 评论0 发表于: 阿里云数据库Apsar ...
- PostgreSQL从cmin/cmax到combo cid
作者:吴聪 cmin和cmax介绍 cmin和cmax是PostgreSQL中表的系统字段之一,用来判断同一个事务内的其他命令导致的行版本变更是否可见.即在事务中每个命令都应该能看到其之前执行的命令的 ...
- postgresql主从备份_基于PG12.2实现主从异步流复制及主从切换教程(下)
概述 今天主要介绍如何搭建PG主从流复制及主从切换,仅供参考. PS:上篇的地址在文末链接. PostgreSQL数据库主从异步流复制搭建 环境说明: 1.安装PG数据库(主从库进行) 用脚本进行,略 ...
最新文章
- Directx11教程(42) 纹理映射(12)-简单的bump mapping
- java形参的传递机制
- Tomcat配置虚拟路径,使上传文件与服务器及工程文件分离开
- Java实现两个递增有序链表合并成一个递增有序链表和两个非递减有序链表合成一个非递增有序链表
- Pytorch中RNN入门思想及实现
- 深度学习推荐系统中各类流行的Embedding方法
- OpenCV如何提取人眼区域的眼球位置
- 防火电缆分类、标准、阻燃等级划分详细说明
- Submissions in Production是什么意思?
- 【原创】在winform程序中实现在IE浏览器中打开一个新的页面,全屏化并屏蔽IE窗口的工具栏和地址栏
- java编程:对两个分数进行简单的算术运算
- 华为物联网(IOT)开发者平台
- 罗斯福国家森林树木类型识别
- iPad 2第一次开机与激活指南
- ddk for win7
- quot转双引号 php,js把 quot 转义双引号
- php 单笔转账到支付宝账户,php实现单笔转账到支付宝功能
- 《ARM学习手札》----B、BL、BX、BLX 和 BXJ
- 以太网MII接口类型大全 MII、RMII、SMII、SSMII、SSSMII、GMII、RGMII、SGMII、TBI、RTBI、XGMII、XAUI、XL、RXAUI
- “请在微信客户端打开链接”解决方案
热门文章
- 这行代码好怪,再看一眼!聊聊在 Gitea 玩 Code Review
- web网页设计实例作业 ——电影泰坦尼克号(4页) HTML+CSS+JavaScript 学生HTML个人网页作业作品下载 个人网页设计制作 大学生个人网站作业模板 简单个人网页制作
- composer的初级使用
- 微信公众号推文各标签含义
- Pandas入门超详细教程,看了超简单
- 在WIN10下通过网口给华为海思Hi3516DV300刷机(鸿蒙系统)
- android 滑动取值_Android中滑屏实现
- 万洲金业:白银继续走高,预期缓慢冲高
- 求滑动窗口中的最大值和最小值
- GO语言-第二节-顺序编程