一碰到 Postgres 的 LSN 十六进制转十进制这种事很多人的第一反应就是打开计算器。上次处理一个压测环境的复制延迟告警监控面板上主库的pg_current_wal_lsn()显示为1C/6B32E8F0备库的pg_last_wal_replay_lsn()是1C/5B3122A0值班同事随手就找在线进制转换工具想把两个都换成十进制再减。我跟他说如果只是想看延迟直接用pg_wal_lsn_diff()就行一行 SQL 的事根本不用人肉换算但如果你要的是监控采集里的 WAL 字节数、要算每秒钟产生了多少 WAL、或者想把某个工具导出的十进制位置写回recovery_target_lsn那就必须把这串十六进制的结构彻底吃透。这篇文章就当一次排障笔记来写不端着讲课的架子。下面要讲的六件事都是这类换算里真正绕不开的点LSN 为什么长成两段十六进制、手工怎么算、用 SQL 怎么写最稳、算完用在哪儿、我在实际操作中踩过的边界和坑以及最后怎么快速验证你没算错。1. LSN为什么长成两段十六进制的样子1.1 本质上就是一个不停递增的64位计数器Postgres 里每个 WAL 记录都有一个唯一的日志序列号 LSN它的底层类型就是无符号 64 位整数。从这套 WAL 逻辑编号体系开始工作起这个数就一直往上加每次写入一个 WAL 记录它就前进相应的字节数。你可以把它理解成一个累计写入的 WAL 字节数只不过这个计数器的起点不只是 0而且它跨越了无数次服务器启停、日志段轮换依旧在同一个逻辑空间里持续递增。pg_lsn类型在输出时把这个 64 位整数拆成两半左边是高 32 位右边是低 32 位各自用十六进制表示前导零省略中间用斜杠分隔。比如1C/6B32E8F0左边的0x1C是高 32 位的值右边的0x6B32E8F0是低 32 位的值。每一位十六进制代表 4 个二进制位所以两边正好各占 8 位十六进制看起来就像一段内存地址。提示LSN 不是内存地址也不是某个真实文件里的绝对偏移。它是一个逻辑空间的字节位置通过换算可以映射到 WAL 文件名和段内偏移。为什么 PostgreSQL 不直接显示成十进制两个原因。第一这种 X/Y 格式跟 WAL 文件名的生成逻辑天然对应DBA 扫一眼就能大概判断这个位置落在哪个日志段附近第二64 位无符号数很快会变成很大的十进制数反而不如十六进制紧凑。所以 PostgreSQL 从设计上就选了这种两个 32 位十六进制段作为人类可读的展示格式。1.2 高位和低位之间隔了整整 2^32 个字节把 64 位拆成两半之后最容易搞混的就是左边的数每增加 1不代表右边滚了一圈 16MB而是代表跨过了 2^32 个字节也就是 4294967296 字节。默认 WAL 段大小是 16MB2^24 字节2^32 字节正好等于 256 个段。所以 LSN 从0/FFFFFF走到0/1000000时只是跨过了 16MB 的边界此刻左边还是 0要一直到低 32 位写满0xFFFFFFFF下一个位置才会进位成1/0。换句话说左边高 32 位每加 1WAL 已经累计往前推进了 4GB。这个结构直接决定了 WAL 文件名的拼法。WAL 文件名是 24 个十六进制字符由三段组成8 位时间线号、8 位逻辑段号的高 32 位、8 位逻辑段号的低 32 位。逻辑段号本身等于LSN 除以段大小取整段内偏移等于LSN 对段大小取余。理解了这一层后面所有换算就都顺了——你真正要操作的对象是一个连续的 64 位字节计数器而不是两个毫无关系的十六进制数。2. 手工换算拆开、乘以2的32次方、再加回去2.1 核心公式decimal X × 4294967296 Y先把 LSN 字符串按斜杠拆成 X 和 Y 两个十六进制数再把各自转成十进制代入公式LSN 的十进制表示 X 的十进制值 × 4294967296 Y 的十进制值。乘的这个 4294967296 就是 2^32也就是高 32 位每跳 1 所代表的字节数。拿1C/6B32E8F0来算一遍。左边0x1C等于十进制的 28右边0x6B32E8F0按位权展开6×16^7 11×16^6 3×16^5 2×16^4 14×16^3 8×16^2 15×16 0 1610612736 184549376 3145728 131072 57344 2048 240 1798498544于是完整十进制 LSN 28 × 4294967296 1798498544 122057582832。如果嫌逐位乘 16 麻烦Windows 计算器的程序员模式、macOS 系统自带的计算器甚至手机上的进制转换工具都能做。不过终端里最利索的方式是直接用 shell 内置功能printf %d\n 0x6B32E8F0立刻得到 1798498544。低 32 位和完整 64 位都能这么转只要 printf 的宿主 shell 支持 64 位整数运算。我整理了一份常见 LSN 样例的换算对照表方便你核对公式LSN十六进制高32位十进制低32位十进制完整十进制LSN段号段内偏移0/FFFFFF016777215167772150167772150/16C266802386493623864936170877201C/6B32E8F028179849854412205758283272753336432FFFFFFFF/FFFFFFFF4294967295429496729518446744073709551615109951162777516777215这个表里最后一行是理论最大值生产库现实中到不了这个量级但它最能说明为什么中间值必须用 numeric 而不是 bigint后面第 5 节会展开讲。2.2 反向十进制还原成 LSN反过来要把十进制数还原成 X/Y 格式就对这个数做除以 4294967296 的整除和取余商就是 X余数就是 Y。比如 122057582832 除以 4294967296商是 28余数是 1798498544把 1798498544 写成十六进制得到6B32E8F0于是还原为1C/6B32E8F0。这个反向过程很多时候比正向更实用。备份工具、运维平台导出的位置信息经常是十进制字节数而 Postgres 的recovery_target_lsn参数只认1C/6B32E8F0这种格式配置 PITR 时就得做这一步还原。手工做的话用整除取余很快要自动化的话直接跳到下一节用 SQL 里的pg_lsn加法一行就够。2.3 顺手把 WAL 文件名和段内偏移也算出来拿到完整十进制 LSN 之后还能继续算 WAL 日志文件名这在实际排障里价值很大。默认段大小 16MB也就是 16777216 字节。逻辑段号 完整十进制 LSN ÷ 16777216 取整段内偏移 完整十进制 LSN ÷ 16777216 取余。对 122057582832 来说122057582832 ÷ 16777216 7275 余 3336432。段号 7275 写成十六进制是0x1C6B所以在时间线为 1 的情况下WAL 文件名是000000010000000000001C6B段内偏移 3336432 字节也就是从段头开始约 3.18MB 的位置。这个映射关系非常值得记住当监控或日志里出现一个 LSN你先用这个办法定位到具体 WAL 文件然后直接去$PGDATA/pg_wal目录下找对应文件检查比对着十六进制发懵高效得多。如果段大小不是 16MB用完整十进制 LSN 除以实际的段大小字节数即可公式在任何段大小下都成立。3. SQL原生解法让pg_lsn自己完成进位3.1 最省事用 pg_lsn 减法一行搞定如果能直接在数据库里操作最省事的方式不是自己拆字符串而是利用pg_lsn类型自带的减法运算符。pg_lsn减去pg_lsn得到 numeric也就是两个位置之间相差的字节数SELECT pg_current_wal_lsn() - 0/0::pg_lsn AS lsn_decimal; SELECT 1C/6B32E8F0::pg_lsn - 0/0::pg_lsn AS lsn_decimal;第二条语句返回122057582832。它的原理就是拿目标 LSN 跟全零 LSN 做差差值天然就是完整的十进制 LSN 大小。反过来要把十进制变回 LSN直接加回去SELECT 0/0::pg_lsn 122057582832 AS lsn_hex; -- 结果1C/6B32E8F0这是我在脚本里最常用的姿势。让 Postgres 自己处理高低位的进位和类型转换出错概率最低。3.2 通用解法hex字符串用bit(32)::bigint强转有些场景不能直接依赖pg_lsn类型比如你从日志、监控系统拿到的是一个纯文本 LSN 字符串想在一条 SQL 里就地完成换算。这时候可以用 Postgresbit类型的输入解析特性字符串以x开头时bit类型会按十六进制解析。配合lpad补足 8 位十六进制再转成 bigint就能拿到单半边 32 位的十进制值SELECT (x || lpad(1C, 8, 0))::bit(32)::bigint AS high_dec, (x || lpad(6B32E8F0, 8, 0))::bit(32)::bigint AS low_dec;第一行返回 28第二行返回 1798498544。组合成完整 LSN 时要注意如果直接让 bigint 乘 4294967296在 X 很大的情况下会溢出。所以乘法前先把它转成 numeric这是稳妥写法WITH p AS ( SELECT lsn, split_part(lsn::text, /, 1) AS high_hex, split_part(lsn::text, /, 2) AS low_hex FROM (SELECT pg_current_wal_lsn() AS lsn) s ) SELECT ((x || lpad(high_hex, 8, 0))::bit(32)::bigint)::numeric * 4294967296::numeric (x || lpad(low_hex, 8, 0))::bit(32)::bigint::numeric AS lsn_decimal FROM p;如果这套逻辑经常要用建议封装成自定义函数放进公共 schemaCREATE OR REPLACE FUNCTION lsn_to_decimal(lsn pg_lsn) RETURNS numeric LANGUAGE sql IMMUTABLE AS $$ SELECT ((x || lpad(split_part(lsn::text, /, 1), 8, 0))::bit(32)::bigint)::numeric * 4294967296::numeric (x || lpad(split_part(lsn::text, /, 2), 8, 0))::bit(32)::bigint::numeric; $$;再配一个反向函数decimal_to_lsnCREATE OR REPLACE FUNCTION decimal_to_lsn(v numeric) RETURNS pg_lsn LANGUAGE sql IMMUTABLE AS $$ SELECT 0/0::pg_lsn v; $$;两个函数配合使用decimal_to_lsn(lsn_to_decimal(1C/6B32E8F0::pg_lsn))可以原样还原。3.3 先别急着换算有些场景根本不需要十进制很多场景下 Postgres 已经帮你把换算做好了。比较两个 LSN 直接可以用比较运算符因为pg_lsn类型内部就是按 64 位整数比较的计算复制延迟用pg_wal_lsn_diff()要知道某个 LSN 在哪个 WAL 文件、文件里偏移多少用pg_walfile_name_offset()。这些函数返回的都是语义化结果比你手动转十进制再自己算要可靠得多。我自己定位问题时的顺序是先看pg_wal_lsn_diff()能不能满足需求再看pg_walfile_name_offset()最后才考虑自己换算。换算只用于那些必须把 LSN 变成纯数值的场景比如监控采集、跨系统传输、二次计算。4. 换算成十进制之后最常落地的三个场景4.1 监控把两次采样差变成WAL字节数数据库监控里最常见的需求是算每秒钟产生了多少 WAL。大多数监控采集器拿到pg_current_wal_lsn()时它是个文本值不能直接求导。把每次采样换算成十进制后落库两次采样值的差就是这段时间写入的 WAL 字节数除以时间间隔就是速率。举个具体例子。假设 10 分钟内采集到两个 LSNt11C/6B32E8F0→ 122057582832t21C/7B42F398→ 122327069592差值是 269486760 字节约 257MB。除以 600 秒得到每秒约 0.43MB 的 WAL 产生速率。这个数字可以直接喂给 Prometheus 画曲线也可以写成告警规则比如WAL 产生速率超过 X MB/s 时报警。我见过不少团队用这个方法做日志风暴告警——WAL 速率突然飙高往往意味着有大批量更新或者被遗忘的循环写任务在跑。采集 SQL 可以直接复用 3.2 节里的 CTE 写法集成到 postgres_exporter 的自定义查询里即可。注意采样落库时用 numeric 类型不要用 float原因在第 5 节讲。4.2 复制延迟备库落后多少字节复制延迟的标准算法本来就应该用pg_wal_lsn_diff()一条 SQL 精确到字节。在备库上执行SELECT pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) AS receive_replay_lag_bytes;但如果你的监控系统里只存了两条文本 LSN或者你想在事后复盘时用历史数据重新计算那就只能先把两个 LSN 都转成十进制再相减。这里有个细节备库上别用pg_current_wal_lsn()它反映的是本地 WAL 状态容易误导备库该用的是pg_last_wal_receive_lsn()接收到的位置和pg_last_wal_replay_lsn()回放到的位置。这两个函数返回的都是pg_lsn可以直接做差。用十进制换算还有一个附加价值字节数可以跟网络传输速率、磁盘 IO 对账。比如备库接收位置落后主库 800MB按当前网络吞吐估算恢复时间这些都需要字节粒度而不是只看一个看起来差不多的十六进制数。4.3 PITR/归档把十进制位置写回 recovery_target_lsnrecovery_target_lsn参数只接受1C/6B32E8F0这种格式。如果你从备份元数据或某个运维平台拿到的是十进制位置就用反向转换还原成 LSN 再配置psql -XAt -c SELECT 0/0::pg_lsn 122057582832;输出1C/6B32E8F0直接填进 recovery.conf 或 postgresql.conf 的recovery_target_lsn。另外pg_controldata输出的Latest checkpoint location、pg_basebackup的起始位置都是这类 LSN用同样思路做归档对账把某两个备份点的 LSN 差除以段大小就能估算两个备份之间需要保留多少个 WAL 段这对存储规划很有用。LSN差字节÷ 段大小 1 中间跨越的WAL段数量这个公式在规划归档保留策略时比任何估算都准。5. 最容易翻车的几个边界点5.1 bigint放不下整个LSN要用numericLSN 是 64 位无符号整数理论最大值FFFFFFFF/FFFFFFFF换算成十进制是 18446744073709551615而 PostgreSQL 的 bigint 最大只有 9223372036854775807连一半都不到。所以任何把整个 LSN 塞进 bigint 再计算的写法都有溢出隐患。尤其是我前面强调过的组合写法X 一旦超过 0x80000000X × 2^32就爆掉 bigint。正确做法是全程用 numeric。目前生产库的 LSN 远没到溢出阈值但写函数和监控脚本时养成 numeric 习惯能把隐患消灭在设计阶段。顺便说一句pg_wal_lsn_diff()和pg_lsn减法的返回值设计成 numeric不是为了装高精度小数正是为了绕过这个容量问题。另一个隐藏坑是 float 精度。如果你在 Python 里用 float 存 LSN 十进制值float64 只有 53 位有效数字无法精确表示 64 位整数采样多了误差会累积。JSON 接口传大整数也要注意很多语言的 JSON 解析器会把它转成 float。跨系统传递 LSN 十进制值时一律用字符串或数值字符串。5.2 版本差异xlog改名为wal函数别用老了PostgreSQL 10 之前WAL 相关函数叫 xlog函数名里全是 xlog10 之后统一改成 wal。如果你还在维护老版本或者在一堆旧脚本里扒代码这个对照表很实用PostgreSQL 9.4 ~ 9.6PostgreSQL 10pg_current_xlog_location()pg_current_wal_lsn()pg_xlogfile_name(lsn)pg_walfile_name(lsn)pg_xlogfile_name_offset(lsn)pg_walfile_name_offset(lsn)pg_xlog_location_diff(a, b)pg_wal_lsn_diff(a, b)另外 PG13 开始多了pg_current_wal_insert_lsn()返回的是插入位置pg_current_wal_lsn()返回的是刷盘位置。算 WAL 产生速率时如果写入很频繁两者会有细微差别。选一个口径并保持一致就行别在同一个报表里混用两个函数。5.3 wal_segment_size 未必是 16MB默认 16MB 在绝大多数环境里成立但 PG11 之后initdb支持用--wal-segsize指定 1MB、4MB、16MB、64MB、256MB。一旦段大小不是 16MB我前面提过的高位换算文件号那套位运算就不成立。但好消息是只要你不是死记位运算而是用完整十进制 LSN 直接除以实际段大小的字节数公式在任何段大小下都成立。判断当前实例的段大小SHOW wal_segment_size;拿到的是类似16MB的字符串写脚本时记得转成字节数再去除。处理历史备份时也要注意不同实例可能用不同段大小初始化从旧备份恢复前先确认目标实例的段大小否则算出的文件号对不上。5.4 lpad和bit强转的细节坑用(x || hex)::bit(32)::bigint这个技巧时我踩过两次坑。第一次是忘了 lpad左侧高 32 位在很多真实 LSN 里只有一两位十六进制比如0/16C2668如果直接对0做::bit(32)会因为位数不够而报错。所以不管哪半边都先用lpad补足 8 个十六进制字符。第二次是输入来源不干净。如果 LSN 字符串来自外部系统可能带0x前缀、带空格、大小写混用甚至因为 JSON 解析被截断。进 SQL 之前最好统一做一次trim、replace(0x,)等清洗。大小写其实没问题bit 类型的十六进制解析大小写都认但前缀和空格一定要处理干净。6. 花一分钟验证换算结果附shell/Python速转6.1 往返验证法最靠谱的验证方式是去又回。算完十进制之后马上用反向加法还原成pg_lsn看是否等于原始值。拿第 3 节的两个自定义函数SELECT lsn_to_decimal(1C/6B32E8F0::pg_lsn) AS dec, decimal_to_lsn(lsn_to_decimal(1C/6B32E8F0::pg_lsn)) AS back, lsn_to_decimal(1C/6B32E8F0::pg_lsn) 122057582832 AS ok;如果ok列是t说明函数逻辑和手工计算全部对上。任何换算逻辑上线前先用几个已知 LSN 做一遍往返验证能拦下绝大多数低级错误。6.2 用pg_walfile_name_offset和磁盘文件对拍另一个更贴近实际的验证是看换算结果跟 Postgres 官方函数是否一致。把同一个 LSN 交给pg_walfile_name_offset()让它返回文件名和偏移再跟你自己算的 7275 号和 3336432 偏移对比SELECT * FROM pg_walfile_name_offset(1C/6B32E8F0::pg_lsn);返回(000000010000000000001C6B, 3336432)。然后去数据目录看一眼这个文件的大小是不是 16777216 字节ls -l $PGDATA/pg_wal/000000010000000000001C6B再往前一步用十六进制查看器或者xxd读一下文件头部确认不是空文件、没被截断xxd -l 64 $PGDATA/pg_wal/000000010000000000001C6BWAL 文件头部有固定的魔数字段HxD 这类编辑器也能直接打开看。这一套对拍做完基本可以放心把换算逻辑写进任何脚本。6.3 终端里秒换算日常排查时我一般直接在终端处理没必要开数据库。三个顺手命令分享一下# 低32位十六进制转十进制bash内置printf printf %d\n 0x6B32E8F0 # 任意完整LSN转十进制Python python3 -c x,y1C/6B32E8F0.split(/); print(int(x,16)*2**32 int(y,16)) # 有psql环境就直接用类型自带的减法 psql -XAt -c SELECT 1C/6B32E8F0::pg_lsn - 0/0::pg_lsn;三个输出的结果应该完全一样122057582832。这也是我最推荐的验证思路——不同工具算同一个数结果一致才是真的正确。最后分享一点个人体会不要在脑子里死记 LSN 和 WAL 文件号之间的位运算更不要写死 16MB。把LSN 是一个 64 位无符号字节计数器这个认知建立起来所有换算都围绕十进制字节数展开遇到任何段大小、任何版本、任何函数名都能快速推导。真到了要算偏移的时候先试试 Postgres 自带的pg_walfile_name_offset()能不能直接满足需求——能就别自己造轮子。
企业数字化 ERP 产品动态
相关推荐
第 0 篇 · 虚拟化hypervisor:Xen简介 Xen 作为一款开源的虚拟化 hypervisor(type1 裸金属虚拟化),其核心是直接运行在硬件上的 hypervisor 内核,同时包含一系列配套组件以实现完整的虚拟化功能。以下是其主程序及核心组件的详细说明。
下图是Xen 整体结构图ÿ… · 2026/9/24 20:13:20
Flutter跨平台实战:螺旋与黄金分割可视化及Harmony适配 1. 项目缘起与整体设计思路1.1 为什么把螺旋和黄金分割搬进跨平台应用做移动端开发这些年,我越来越觉得,技术选型和自然界里的生长规律有某种暗合。这次的项目标题是“螺旋与黄金分割——自然界的韵律密码”,听起来像科普,实际上它… · 2026/9/24 20:13:07
llama.cpp 本地大模型部署指南:从 GGUF 量化到 CPU 推理与 API 服务 如果你最近在折腾本地大模型,llama.cpp 这几个字一定没少刷到。它是一个用 C/C 编写的大语言模型推理引擎,目标很直接:在普通 CPU 上也能跑起 Llama、Mistral、Qwen 这类开源模型,内存占用控制得相当好,性能也打磨了很… · 2026/9/24 20:13:07
ECharts关系图graph实战:从数据设计到性能优化 前两天有个做数据可视化大屏的朋友问我,想把公司几十个业务系统之间的依赖关系画出来,问我用什么方案快。我脱口而出:ECharts的graph——关系图。他愣了一下,说平时柱状图、饼图、折线图用得挺多,关系图还真没碰过。这… · 2026/9/24 21:16:26
弱监督情感分析:基于Stacking的伪标签融合与模型提升方案 简介:基于Stacking框架的弱监督深度学习情感分析研究算法Python完整源码包,面向计算机相关专业学生、算法工程师及对情感分析感兴趣的研究者,可用于课程设计、毕业设计或科研初期算法验证。资源共9个文件,包含6个Python源码、2个E… · 2026/9/24 21:16:26
SpringBoot + Vue3 构建养老院健康管理系统:架构设计与实践复盘 从零搭一个养老院健康管理系统:SpringBoot Vue3 落地实录养老院健康管理系统,本质上就是一个“医疗信息化机构管理”的复合型项目。这两年养老行业数字化需求涨得很快,很多做Java后端的朋友拿到这类需求时,第一反应是“这不就是个… · 2026/9/24 21:16:19
决策树量化投资回测实战:从数据到模型 简介:这是一套基于Python与机器学习的量化投资策略完整项目,适合毕业设计、期末大作业或课程设计场景,也面向想快速上手量化回测的初学者。项目从数据获取、特征构建、模型训练到策略回测均有相应模块支撑,并配有清晰代码注释与说… · 2026/9/24 21:16:19
RabbitMQ死信队列实战:从延迟关单到消息容错与削峰 凌晨两点,我看着监控面板上那个数字从几百一路飙到几十万,整个人是彻底清醒了。订单服务重启了一轮,积压的消息全部回到队列里,又因为消费失败被不断重新投递,整个集群像一台失控的跑步机,消息在原地狂奔&a… · 2026/9/24 21:16:19
仅7MB的Photoshop轻量替代:实测这款免安装图像编辑工具 第一次看到这个标题的时候,我第一反应是:7MB?一张稍微大点的JPG都不止7MB了吧。结果到外网几个软件社区逛了一圈,发现关于这个小体积工具的话题热度确实不低,评论区不少人都在晒自己的使用体验。我索性下载下来实测了几… · 2026/9/24 21:16:19
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44