Postgresql中的数据行(tuple)
mhr18 2025-01-03 17:27 29 浏览 0 评论
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. 行上的事务提交信息是由其他读取的事务更新的,并非插入事务更新。
2. 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标记为提交或回滚
3. 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提交状态
总结:
- 执行插入时,往数据块中插入一行数据,将行上的xmin置为当前事务id,xmax置为0,提交事务时仅更新clog相应事务的状态。
- 删除操作时,将原行的xmax置为当前事务id,提交事务时仅更新clog相应事务的状态。
- 更新操作时,先对原行执行delete操作,再做一个insert操作,提交事务时仅更新clog相应事务的状态。
- 行上的xmin、xmax提交、回滚状态是由其他事务查询行时触发更新的。
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)