暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Postgresql中的数据行tuple

原创  江湖小虾米  2024-01-21
542

pg中每一行除了自定义的字段外,还包含一个行头部,头部包含如下字段

  • tableoid 表的OID,可以与pg_class的oid列进行连接来获得表的名称
  • xmin 插入该行版本的事务身份(事务ID)
  • xmax 删除事务的身份(事务ID)
  • cmin 插入事务中的命令标识符(从0开始)
  • cmax 删除事务中的命令标识符,或者为0
  • infomask 提供一组定义版本属性的信息位。
  • ctid 行版本在其表中的物理位置,由块号和相对位置的元组组成(block_id,row_num)。注意尽管ctid可以被用来非常快速地定位行版本,但是一个行的ctid会在被更新或者被VACUUM FULL移动时改变。因此,ctid不能作为一个长期行标识符。 应使用主键来标识逻辑行
  • null bitmap 空值位图,标记行的空字段
    可以通过sql语句查询隐藏的字段
mydb=# select tableoid,xmin,xmax,cmin,xmax,ctid,* from t; tableoid | xmin | xmax | cmin | xmax | ctid | id | s ----------+------+------+------+------+-------+----+------- 24603 | 784 | 0 | 0 | 0 | (0,1) | 1 | alice

tuple的DML操作内部实现
创建一个示例表和pageinspect扩展,pageinspect可以分析页内部结构。

#创建表 CREATE TABLE t( id integer GENERATED ALWAYS AS IDENTITY, s text ); #创建索引 CREATE INDEX ON t(s); #创建扩展 mydb=# create extension pageinspect; #创建获取行元数据的函数 CREATE FUNCTION heap_page(relname text, pageno integer) RETURNS TABLE(ctid tid, state text, xmin text, xmax text) AS $$ SELECT (pageno,lp)::text::tid AS ctid, CASE lp_flags WHEN 0 THEN 'unused' WHEN 1 THEN 'normal' WHEN 2 THEN 'redirect to '||lp_off WHEN 3 THEN 'dead' END AS state, t_xmin || CASE WHEN (t_infomask & 256) > 0 THEN ' c' WHEN (t_infomask & 512) > 0 THEN ' a' ELSE '' END AS xmin, t_xmax || CASE WHEN (t_infomask & 1024) > 0 THEN ' c' WHEN (t_infomask & 2048) > 0 THEN ' a' ELSE '' END AS xmax FROM heap_page_items(get_raw_page(relname,pageno)) ORDER BY lp; $$ LANGUAGE sql; #创建获取索引元数据的函数 CREATE FUNCTION index_page(relname text, pageno integer) RETURNS TABLE(itemoffset smallint, htid tid) AS $$ SELECT itemoffset, htid -- ctid before v.13 FROM bt_page_items(relname,pageno); $$ LANGUAGE sql;
  1. Insert操作
mydb=# begin; BEGIN mydb=*# INSERT INTO t(s) VALUES ('FOO'); INSERT 0 1 mydb=*# SELECT pg_current_xact_id(); pg_current_xact_id -------------------- 774 (1 row) mydb=*# SELECT '(0,'||lp||')' AS ctid, CASE lp_flags WHEN 0 THEN 'unused' WHEN 1 THEN 'normal' WHEN 2 THEN 'redirect to '||lp_off WHEN 3 THEN 'dead' END AS state, t_xmin as xmin, t_xmax as xmax, (t_infomask & 256) > 0 AS xmin_committed, (t_infomask & 512) > 0 AS xmin_aborted, (t_infomask & 1024) > 0 AS xmax_committed, (t_infomask & 2048) > 0 AS xmax_aborted FROM heap_page_items(get_raw_page('t',0)) ; ctid | state | xmin | xmax | xmin_committed | xmin_aborted | xmax_committed | xmax_aborted -------+--------+------+------+----------------+--------------+----------------+-------------- (0,1) | normal | 774 | 0 | f | f | f | t (1 row) #提交事务,查询一下行数据,再此查看行上的状态 # 此时xmin_committed变成true,代表此插入事务已提交 mydb=# SELECT '(0,'||lp||')' AS ctid, CASE lp_flags WHEN 0 THEN 'unused' WHEN 1 THEN 'normal' WHEN 2 THEN 'redirect to '||lp_off WHEN 3 THEN 'dead' END AS state, t_xmin as xmin, t_xmax as xmax, (t_infomask & 256) > 0 AS xmin_committed, (t_infomask & 512) > 0 AS xmin_aborted, (t_infomask & 1024) > 0 AS xmax_committed, (t_infomask & 2048) > 0 AS xmax_aborted FROM heap_page_items(get_raw_page('t',0)) ; ctid | state | xmin | xmax | xmin_committed | xmin_aborted | xmax_committed | xmax_aborted -------+--------+------+------+----------------+--------------+----------------+-------------- (0,1) | normal | 788 | 0 | t | f | f | t (1 row) # 1. 插入操作时添加一个指针号1到页中,1号指针指向页中的第一个记录。xmin字段设置为当前的事务ID,由于事务还没有提交,xmin_aborted和xmax_committed字段还没有设置。xmax字段为0,因为行还未删除。 # 2. 一旦事务成功完成,必须要记录事务的提交信息。pg使用提交日志clog保持事务提交信息。clog位于PGDATA/pg_xact目录。clog用2个位来标记每个事务:提交和回滚。事务提交后,clog的标志位被设置为commited。 # 3. 当其他事务要访问此数据时,必须要判断xmin事务是否完成。通过查询clog中的事务标志位判断事务是否提交,如果未提交,此行数据将不可见,如果提交,会将提交信息写到tuple头中。 # 4. 行上的事务提交信息是由其他读取的事务更新的,并非插入事务更新。
  1. Delete操作:
mydb=# BEGIN; BEGIN mydb=*# DELETE FROM t; DELETE 1 mydb=*# SELECT * FROM heap_page('t',0); ctid | state | xmin | xmax -------+--------+-------+------ (0,1) | normal | 788 c | 791 (1 row) mydb=*# commit; COMMIT mydb=# select * from t; id | s ----+--- (0 rows) mydb=# SELECT * FROM heap_page('t',0); ctid | state | xmin | xmax -------+--------+-------+------- (0,1) | normal | 788 c | 791 c (1 row) # 1. 删除操作是在原行的xmax字段写入当前的事务id,由于事务还未提交,因此不知道xmax_commited的状态 # 2. 提交或回滚后在clog中将相应的事务标记为提交或回滚 # 3. 当有事务查询此行时,会更具clog中的事务状态将xmax_commited标记为提交或回滚
  1. Update操作:
    update可以看成时delete和insert的组合,先删除原行,再插入一个新行。
mydb=# SELECT * FROM heap_page('t',0); ctid | state | xmin | xmax -------+--------+------+------ (0,1) | normal | 793 | 0 a (1 row) mydb=# begin; BEGIN mydb=*# UPDATE t SET s = 'Bobo'; UPDATE 1 mydb=*# SELECT pg_current_xact_id(); pg_current_xact_id -------------------- 794 (1 row) mydb=*# SELECT * FROM heap_page('t',0); ctid | state | xmin | xmax -------+--------+-------+------ (0,1) | normal | 793 c | 794 (0,2) | normal | 794 | 0 a (2 rows) mydb=*# commit; COMMIT mydb=# select * from t; id | s ----+------ 2 | Bobo (1 row) mydb=# SELECT * FROM heap_page('t',0); ctid | state | xmin | xmax -------+--------+-------+------- (0,1) | normal | 793 c | 794 c (0,2) | normal | 794 c | 0 a (2 rows) # 1. 先将原行的xmax标记为当前事务id # 2. 插入新行,将新行的xmin标记为当前事务id # 3. 提交事务,在clog中标记事务的状态 # 4. 其他事务查询此行时,更新旧行上的xmax提交状态,更新新行上的xmin提交状态

总结:

  1. 执行插入时,往数据块中插入一行数据,将行上的xmin置为当前事务id,xmax置为0,提交事务时仅更新clog相应事务的状态。
  2. 删除操作时,将原行的xmax置为当前事务id,提交事务时仅更新clog相应事务的状态。
  3. 更新操作时,先对原行执行delete操作,再做一个insert操作,提交事务时仅更新clog相应事务的状态。
  4. 行上的xmin、xmax提交回滚状态是由其他事务查询行时触发更新的。
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论