0
点赞
收藏
分享

微信扫一扫

PG核心技术篇--表中系统字段

系统字段概述

每个表都有多个系统字段,这些字段是由系统隐式定义的。这些系统字段在psql中使用“\d”命令返回的结果中并不显示,所以需要记住实际表中还存在这些隐含字段。因为表中已隐含这些名字的字段,所以用户定义的名称不能与这些字段的名称相同,这一限制与名字是否为关键字没有关系,即使字段名称用双引号括起来也不行。

这些系统字段如下。

oid:行对象标识符(对象ID)。该字段只有在创建表时使用了“with oids”或配置参数“default_with_oids”的值为真时出现。该字段的类型是oid(类型名和字段名相同)

·tableoid:包含本行的表的oid。对父表(该表存在有继承关系的子表)进行查询时,使用此字段就可以知道某一行来自父表还是子表,以及是来自哪个子表。tableoid可以和pg_class的oid字段连接起来获取表名字。

·xmin:插入该行版本的事务ID。

·xmax:删除此行时的事务ID,第一次插入时,此字段为0。如果查询出来此字段不为0,则可能是删除这行的事务还未提交,或者是删除此行的事务回滚了。

·cmin:事务内部的插入类操作的命令ID,此标识是从0开始的。

·cmax:事务内部的删除类操作的命令ID,如果不是删除命令,此字段为0。

·ctid:一个行版本在它所处的表内的物理位置。

OID

PostgreSQL在内部使用对象标识符(OID)作为各种系统表的主键。系统不会给用户创建的表增加一个oid系统字段,但用户可以在建表时使用“with oids”选项为表增加oid字段。目前oid类型用一个4字节的无符号整数实现,它不能提供大数据范围内的唯一性保证,甚至在单个的大表中也不行。因此,PostgreSQL官方不鼓励在用户创建的表中使用oid字段,建议oid字段只是用于系统表。

表(包括toast表)、索引、视图的对象标识符就是系统表“pg_class”的oid字段的值。

postgres=# select oid,relname from pg_class where relname like 't%';
oid | relname
-------+--------------------------
16384 | test
16524 | test01_pkey
16518 | table_name
16521 | test01
16661 | test02
16416 | tables
16531 | test002
16526 | test001
16529 | test001_pkey
16536 | test003
16556 | test005
16562 | test005_pkey
13463 | table_constraints
13467 | table_privileges
13474 | tables
13478 | transforms
13482 | triggered_update_columns
13486 | triggers

除oid这种通用的对象标识符类型外,其他的类型都提供一种把字符串转换成oid类型的操作符,这可以大大简化查询对象信息时的SQL语句,示例如下。

想要知道对象标识符为“16556”的表是哪一张,可以使用如下SQL命令查询

postgres=# select 16556::regclass;
regclass
----------
test005
(1 row)

要查询系统表,看表“t”有哪些字段,一般的SQL命令是需要先查询pg_attribute表再关联pg_class表的,示例如下

postgres=# SELECT attrelid,attname,atttypid,attlen, attnum,attnotnull FROM pg_attribute
postgres-# WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'student');
attrelid | attname | atttypid | attlen | attnum | attnotnull
----------+--------------+----------+--------+--------+------------
16641 | tableoid | 26 | 4 | -6 | t
16641 | cmax | 29 | 4 | -5 | t
16641 | xmax | 28 | 4 | -4 | t
16641 | cmin | 29 | 4 | -3 | t
16641 | xmin | 28 | 4 | -2 | t
16641 | ctid | 27 | 6 | -1 | t
16641 | student_no | 23 | 4 | 1 | t
16641 | student_name | 1043 | -1 | 2 | f
16641 | age | 23 | 4 | 3 | f
(9 rows)

postgres=# select oid,relname from pg_class where relname like 'student';
oid | relname
-------+---------
16641 | student
(1 row)

使用regclass类型的自动转换运算符就可以不关联查询pg_class了

postgres=# SELECT attrelid,attname,atttypid,attlen, attnum,attnotnull FROM pg_attribute
postgres-# WHERE attrelid = 'student'::regclass;
attrelid | attname | atttypid | attlen | attnum | attnotnull
----------+--------------+----------+--------+--------+------------
16641 | tableoid | 26 | 4 | -6 | t
16641 | cmax | 29 | 4 | -5 | t
16641 | xmax | 28 | 4 | -4 | t
16641 | cmin | 29 | 4 | -3 | t
16641 | xmin | 28 | 4 | -2 | t
16641 | ctid | 27 | 6 | -1 | t
16641 | student_no | 23 | 4 | 1 | t
16641 | student_name | 1043 | -1 | 2 | f
16641 | age | 23 | 4 | 3 | f
(9 rows)

下例中的SQL命令是查询操作符的左右操作数的类型,使用“::regtype”后也不再需要关联查询pg_type系统表

select oprname,oprleft::regtype, oprright::regtype,oprresult::regtype,
oprcode from pg_operator limit 3;

CTID

ctid表示数据行在它所处的表内的物理位置。ctid字段的类型是tid。尽管ctid可以非常快速地定位数据行,但每次VACUUM FULL之后,数据行在块内的物理位置会移动,即ctid会发生变化,所以ctid是不能作为长期的行标识符的,应该使用主键来标识逻辑行。

select ctid ,student_no from student limit 10;
ctid | student_no
-------+------------
(0,5) | 1
(0,6) | 2

从上例中可以看出,ctid由两个数字组成,第一个数字表示数据行所在的物理块的物理块号,第二个数字表示数据行在物理块中的行号。


postgres=# select ctid, student_no from student where ctid='(0,6)';
ctid | student_no
-------+------------
(0,6) | 2

利用ctid可以删除表中的重复记录

DELETE FROM t a
WHERE a.ctid <> (SELECT min(b.ctid)
FROM t b
WHERE a.id = b.id);

DELETE FROM t 
WHERE ctid = ANY(ARRAY(SELECT ctid
FROM (SELECT row_number() OVER (PARTITION BY id), ctid
FROM t) x
WHERE x.row_number > 1));

xmin、xmax

xmin、xmax、cmin、cmax这4个字段在多版本实现中用于控制数据行是否对用户可见。PostgreSQL将修改前后的数据存储在相同的结构中,分为以下几种情况:

(1)新插入一行时,将新插入行的xmin填写为当前的事务ID,xmax填“0”。

(2)修改这一行时,实际上新插入一行,原数据行上的xmin不变,xmax改为当前的事务ID,新数据行上的xmin填为当前的事务ID,xmax填“0”。

(3)删除一行时,把被删除行上的xmax填写当前的事务ID。

从上面的叙述中就可以知道,xmin就是标记插入数据行的事务ID,而xmax就是标记删除数据行的事务ID。

没有修改数据行的操作,因为修改数据行,实际上就是把原数据行上的xmax标记上自己的事务ID(相当于打上删除标记),然后再新插入一条记录。

cmin、cmax

PostgreSQL使用与解决事务内可见性问题类似的方法引入了命令ID的概念。行上记录了操作这行的命令ID,当其他命令读取这行数据时,如果当前的命令ID大于等于数据行上的命令ID,说明这行数据是可见的;如果当前的命令ID小于数据行上的命令ID,则这条数据不可见。

命令ID的分配规则如下:

(1)每个命令使用事务内一个全局命令标识计数器的当前值作为当前命令标识。

(2)事务开始时,命令标识计数器被置为初值“0”。

(3)执行更新性的命令时,如INSERT、UPDATE、DELETE、SELECT...FOR UPDATE,在SQL命令执行后命令标识计数器加1。

(4)当命令标识计数器经过不断累加又回到初值“0”时,报错“cannot have more than 2^32-1 commands in a transaction”,即一个事务中命令的个数最多为232-1个。

举报

相关推荐

0 条评论