加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

xmin, xmax 之postgres

发布时间:2020-12-13 17:32:51 所属栏目:百科 来源:网络整理
导读:来自:http://zoulx1982.blog.163.com/blog/static/45487219201142944214318/ Postgresql数据库使用的是多版本并发控制 原理:在元组的头部记录着2个特殊的标记值,xmin和xmax;xmin表示插入该元组的事务号,xmax表示删除该元组的事务号 执行select查询时,

来自:http://zoulx1982.blog.163.com/blog/static/45487219201142944214318/
Postgresql数据库使用的是多版本并发控制

原理:在元组的头部记录着2个特殊的标记值,xmin和xmax;xmin表示插入该元组的事务号,xmax表示删除该元组的事务号

执行select查询时,会首先获取一个数据库快照,该快照也包括xmin和xmax,这里的xmin表示当前已完成的事务的最小id,xmax表示正在执行的事务的最大id,任何小于xmin的事务被认为已完成,任何大于xmax的事务被认为尚未结束;位于xmin与xmax之间的事务可能已经完成,也可能尚未完成,视情况而定。

扫描元组时,通过对比元组头的xmin、xmax与快照的xmin、xmax,就可以知道这条元组对当前事务是可见还是不可见了。

下面用Tupxmin、Tupxmax表示元组头的事务号;Snapxmin、Snapxmax表示快照中的事务号

如果 Tupxmin 小于 Snapxmin,说明插入该元组的事务已提交,那么再看Tupmax是不是也小于Snapxmin,如果Tupmax也小于Snapxmin,说明删除该元组的事务已提交,那么这条元组不可见;如果Tupmax大于Snapxmin,说明删除该元组的事务还未提交,那么这条元组可见。

判断事务号是否在快照中的方法:


1)如果事务号小于快照中的xmin,那么事务不在快照中;

2)如果事务号大于快照中的xmax,那么事务在快照中;

3)如果事务号在快照的xmin和xmax之间,那么扫描快照中的活动事务,如果扫描到了,那么事务在快照中,否则事务不在快照中。


http://m.oschina.net/blog/63668
和oracle数据库一样,postgresql也有自身的一套隐藏的系统列。下面介绍如下:
1.oid
oid是object identifier的简写,其相关的参数设置default_with_oids设置一般默认是false,或者创建表时指定with (oids=false),其值长度32bit,实际的数据库系统应用中并不能完全保证其唯一性;
2.tableoid
是表对象的一个唯一标识符,可以和pg_class中的oid联合起来查看
3.xmin
是插入的事务标识符,是用来标识不同事务下的一个版本控制。每一次更新该行都会改变这个值。可以和mvcc版本结合起来看
4.xmax
是删除更新的事务标识符,如果该值不为0,则说明该行数据当前还未提交或回滚。比如设置begin事务时可以明显看到该值的变化
5.cmin
插入事务的命令标识符,从0开始
6.cmax
删除事务的命令标识符,或者为0
7.ctid
是每行数据在表中的一个物理位置标识符,和oracle的rowid类似,但有一点不同,当表被vacuum full或该行值被update时该值可能会改变。所以定义表值的唯一性最好还是自己创建一个序列值的主键列来标识比较合适。不过使用该值去查询时速度还是非常快的。


下面举例说明:
postgres=# create table test(id int,name varchar);
CREATE TABLE
postgres=# insert into test select generate_series(1,3),repeat('kenyon',2);
INSERT 0 3
postgres=# select cmin,cmax,xmin,xmax,ctid from test;
cmin | cmax | xmin | xmax | ctid
------+------+------+------+-------
0 | 0 | 1852 | 0 | (0,1)
0 | 0 | 1852 | 0 | (0,2)
0 | 0 | 1852 | 0 | (0,3)
(3 rows)
我们可以看到xmin值是一样的,表示是同一个事务
postgres=# begin;
BEGIN
postgres=# insert into test values (4,'a');
INSERT 0 1
postgres=# insert into test values (5,'aa');
INSERT 0 1
postgres=# insert into test values (6,'aa');
INSERT 0 1
postgres=# insert into test values (7,'aad');
INSERT 0 1
postgres=# commit;
COMMIT
postgres=# select cmin,ctid,* from test;
cmin | cmax | xmin | xmax | ctid | id | name
------+------+------+------+-------+----+--------------
0 | 0 | 1852 | 0 | (0,1) | 1 | kenyonkenyon
0 | 0 | 1852 | 0 | (0,2) | 2 | kenyonkenyon
0 | 0 | 1852 | 0 | (0,3) | 3 | kenyonkenyon
0 | 0 | 1853 | 0 | (0,4) | 4 | a
1 | 1 | 1853 | 0 | (0,5) | 5 | aa
2 | 2 | 1853 | 0 | (0,6) | 6 | aa
3 | 3 | 1853 | 0 | (0,7) | 7 | aad
这里我们可以看到cmin和cmax值有了变化
postgres=# begin;
BEGIN
postgres=# update test set name = 'keke' where id = 7;
UPDATE 1
postgres=# update test set name = 'kekeke' where id = 6;
UPDATE 1
postgres=# update test set name = 'kenyon_test' where id = 5;
UPDATE 1
在另外一个会话中我们去查看xmax值
postgres=# select cmin,4) | 4 | a
2 | 2 | 1853 | 1854 | (0,5) | 5 | aa
1 | 1 | 1853 | 1854 | (0,6) | 6 | aa
0 | 0 | 1853 | 1854 | (0,7) | 7 | aad
(7 rows)
原会话中我们执行commit并查看
postgres=# commit;
COMMIT
postgres=# select cmin,* from test;
cmin | cmax | xmin | xmax | ctid | id | name
------+------+------+------+--------+----+--------------
0 | 0 | 1852 | 0 | (0,4) | 4 | a
0 | 0 | 1854 | 0 | (0,8) | 7 | keke
1 | 1 | 1854 | 0 | (0,9) | 6 | kekeke
2 | 2 | 1854 | 0 | (0,10) | 5 | kenyon_test
(7 rows)
这时我们可以看到ctid也有了变化,在原来的基础上(0,7)往上累积,另外xmax因为事务被commit 的缘故也被置为0了。 再做下delete和insert的一个简单操作
postgres=# delete from test where id = 1;
DELETE 1
postgres=# insert into test values (8,'jackson');
INSERT 0 1
postgres=# select cmin,10) | 5 | kenyon_test
0 | 0 | 1856 | 0 | (0,11) | 8 | jackson
这时我们可以看到其实delete的事务ID(xmin)值是1855,insert的(xmin)值是1856,ctid往上累计


最后看一下tableoid和pg_class中oid的关系,(oid不说了,系统中一般设置为false)
postgres=# select tableoid,cmin,* from test;
tableoid | cmin | cmax | xmin | xmax | ctid | id | name
----------+------+------+------+------+--------+----+--------------
24601 | 0 | 0 | 1852 | 0 | (0,2) | 2 | kenyonkenyon
24601 | 0 | 0 | 1852 | 0 | (0,3) | 3 | kenyonkenyon
24601 | 0 | 0 | 1853 | 0 | (0,4) | 4 | a
24601 | 0 | 0 | 1854 | 0 | (0,8) | 7 | keke
24601 | 1 | 1 | 1854 | 0 | (0,9) | 6 | kekeke
24601 | 2 | 2 | 1854 | 0 | (0,10) | 5 | kenyon_test
24601 | 0 | 0 | 1856 | 0 | (0,11) | 8 | jackson
(7 rows)


postgres=# select oid,relname,relfilenode,relkind from pg_class where oid = 24601;
oid | relname | relfilenode | relkind
-------+---------+-------------+---------
24601 | test | 24601 | r




http://www.tuicool.com/articles/RbMJFv

xmax

The identity (transaction ID) of the deleting transaction,or zero for an undeleted row version. It is possible for this column to be nonzero in a visible row version. That usually indicates that the deleting transaction hasn't committed yet,or that an attempted deletion was rolled back.


http://www.postgresql.org/docs/9.1/static/ddl-system-columns.html


作一个实验:
目前我刚刚结束了一条数据,我数据库关闭前,最后一条transaction的id是 1874。

(查看当前事务ID:select txid_current();)

我开一个终端A,此时终端A的,当前transactionId为 1875。

[pgsql@localhost bin]$ ./psql
psql (9.1.2)
Type "help" for help.


pgsql=# begin;
BEGIN


pgsql=# select xmin,* from tab01;
xmin | xmax | id | cd
------+------+----+----
1866 | 0 | 3 | 3
1867 | 0 | 4 | 4
1868 | 0 | 5 | 5
1870 | 0 | 6 | 6
1872 | 0 | 7 | 7
1873 | 0 | 8 | 8
1874 | 0 | 9 | 9
(7 rows)


pgsql=#
我再开一个终端B,此时,终端B的transactionId为:1876。


[pgsql@localhost bin]$ ./psql
psql (9.1.2)
Type "help" for help.


pgsql=# begin;
BEGIN
pgsql=# select xmin,* from tab01;
xmin | xmax | id | cd
------+------+----+----
1866 | 0 | 3 | 3
1867 | 0 | 4 | 4
1868 | 0 | 5 | 5
1870 | 0 | 6 | 6
1872 | 0 | 7 | 7
1873 | 0 | 8 | 8
1874 | 0 | 9 | 9
(7 rows)


pgsql=#
回到终端A,执行 delete 操作:


pgsql=# delete from tab01 where id=9;
DELETE 1
pgsql=#
此时,在终端A中,已经看不到删除后的数据:


pgsql=# select xmin,* from tab01;
xmin | xmax | id | cd
------+------+----+----
1866 | 0 | 3 | 3
1867 | 0 | 4 | 4
1868 | 0 | 5 | 5
1870 | 0 | 6 | 6
1872 | 0 | 7 | 7
1873 | 0 | 8 | 8
(6 rows)


pgsql=#
此时,由于终端A尚未提交,所以,可以在终端B中看到如下的情形:


pgsql=# select xmin,* from tab01;
xmin | xmax | id | cd
------+------+----+----
1866 | 0 | 3 | 3
1867 | 0 | 4 | 4
1868 | 0 | 5 | 5
1870 | 0 | 6 | 6
1872 | 0 | 7 | 7
1873 | 0 | 8 | 8
1874 | 1875 | 9 | 9
(7 rows)


pgsql=#
也就是说,id为9的那条记录,其xmax为1875,表明其为 transactionid为 1875的事务所删除。


回到终端A,进行提交:


pgsql=# commit;
COMMIT
pgsql=# select xmin,* from tab01;
xmin | xmax | id | cd
------+------+----+----
1866 | 0 | 3 | 3
1867 | 0 | 4 | 4
1868 | 0 | 5 | 5
1870 | 0 | 6 | 6
1872 | 0 | 7 | 7
1873 | 0 | 8 | 8
(6 rows)


pgsql=#
再回到终端B,查看:


pgsql=# select xmin,* from tab01;
xmin | xmax | id | cd
------+------+----+----
1866 | 0 | 3 | 3
1867 | 0 | 4 | 4
1868 | 0 | 5 | 5
1870 | 0 | 6 | 6
1872 | 0 | 7 | 7
1873 | 0 | 8 | 8
(6 rows)




http://www.pgsqldb.org/pgsqldoc-8.1c/ddl-system-columns.html

每个表都有几个系统字段,这些字段是由系统隐含定义的。 因此,这些名字不能用于用户定义的字段名。 (请注意这些限制与这个名字是否关键字无关;把名字用引号括起来并不能让你逃离这些限制。) 你实际上不需要注意这些字段,只要知道它们存在就可以了。

oid

行的对象标识符(对象 ID)。这个字段只有在创建表的时候使用了WITH OIDS,或者是设置了配置参数default_with_oids时出现。 这个字段的类型是oid(和字段同名); 参阅Section 8.12获取有关这种类型的更多信息。

tableoid

包含本行的表的 OID。这个字段对那些从继承层次中选取的查询特别有用(参阅Section 5.8), 因为如果没有它的话,我们就很难说明一行来自哪个独立的表。tableoid可以和pg_classoid字段连接起来获取表名字。

xmin

插入该行版本的事务的标识(事务 ID)。(注意:在这个环境里, 一个行版本是一行的一个状态;一行的每次更新都为同一个逻辑行创建一个新的行版本。)

cmin

在插入事务内部的命令标识(从零开始)。

xmax

删除事务的标识(事务ID),如果不是被删除的行版本,那么是零。 在一个可见行版本里,这个字段有可能是非零。这通常意味着删除事务还没有提交, 或者是一个删除的企图被回滚掉了。

cmax

在删除事务内部的命令标识符,或者是零。

ctid

一个行版本在它所处的表内的物理位置。请注意,尽管ctid可以用于非常快速地定位行版本,但每次VACUUM FULL之后, 一个行的ctid都会被更新或者移动。 因此ctid是不能作为长期的行标识符的。 应该使用OID,或者更好是用户定义的序列号,来标识一个逻辑行。

OID 是 32 位的量,是在同一个集群内通用的计数器上赋值的。 对于一个大型或者长时间使用的数据库,这个计数器是有可能重叠的。 因此,假设 OID 是唯一的是非常错误的,除非你自己采取了措施来保证它们是唯一的。 如果你需要标识表中的行,我们强烈建议使用序列号生成器。 不过,也可以使用 OID,只要采取几个注意事项即可:

  • 在使用 OID 标识行的每个表的 OID 字段创建一个唯一约束。 在唯一约束(或者唯一索引)存在的时候,系统会注意不去生成一个和现有行相同的 OID。 (当然,只有在表中的数据行少于 232(40 亿)行的时候才是可能的, 而实际上表中的行最好远比这个小,要不性能就会受影响了。)

  • 绝对不要假设 OID 是跨表唯一的;如果你需要全数据库范围内的标识,请使用tableoid和行的 OID 的组合。

  • 需要 OID 的表应该带着WITH OIDS创建。 对于PostgreSQL8.1,WITHOUT OIDS是缺省。

事务标识符也是 32 位的量。在长时间运转的数据库里,它也可能会重叠。 只要我们采取一些合适的维护步骤,这并不是很要命的问题; 参阅Chapter 22获取细节。不过, 在长时间运行的环境里(超过十亿次事务)依赖事务 ID 的唯一性并非明智的做法。

命令标识符也是 32 位的量。这样就在一个事务里有232(四十亿)条SQL命令的硬限制。 在现实里这个限制应该不是什么问题 — 注意这个限制是SQL命令的条数, 而不是处理的行版本的条数。




参考:

http://eric.themoritzfamily.com/understanding-psqls-mvcc.html

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读