PostgreSQL底层故障排查:pageinspect工具实战详解

前阵子碰到一个挺闹心的故障:某张业务表的容量持续上涨,磁盘告警都触发了,可业务写入量并没有明显增加。跑了一遍VACUUM,空间也纹丝不动。我用常规的pg_stat_user_tables、pg_stat_progress_vacuum查了一圈,都看不出名堂。最后是靠pageinspect直接抓取数据文件的原始页面,一层层掰开来看,才定位到大量死行堆叠在某几个页面上,触发了VACUUM的延迟清理逻辑。从那之后,pageinspect就成了我排查PostgreSQL底层问题时的固定备选工具。

如果你也是DBA、数据库内核开发者,或者正在为线上PostgreSQL的诡异问题头疼,这篇东西就是为你写的。pageinspect不是那种日常运维天天用的工具,它更像是一台"数据库文件显微镜"——能直接读取表文件、索引文件、可见性映射文件里的原始字节,并解析成可读的结构化数据。它解决的核心问题是:当SQL层面、统计信息层面都找不到答案时,如何从存储层面找到真相。

1. 为什么需要pageinspect:数据库文件页的"显微镜"

先讲清楚pageinspect的定位。PostgreSQL的数据库文件不是文本文件,而是按固定大小(默认8KB)切分的页面块,每个页面有页头、行数据、空闲空间、特殊空间等区域。正常情况下你不需要关心这些,SQL语句会把底层细节全部封装掉。但当问题出在"正常路径"之外——比如崩溃恢复异常、索引损坏、死行残留、页内碎片混乱——SQL层就无能为力了。

pageinspect干的正是这件事:它绕过常规的读写接口,直接按文件页的二进制布局读取内容,并以行记录的形式把页头字段、行指针、事务ID、数据内容这些细节展示出来。打个比方,如果把PostgreSQL比作一栋楼,普通运维手段能看的是房间号、住户登记表,pageinspect则是直接拆开墙体看里面的钢筋和水泥。

1.1 与常规诊断工具的分工差异

  • pg_stat_user_tables、pg_stat_all_tables只能给你"汇总数字",比如n_dead_tup又涨了多少,但不会告诉你死行具体分布在哪一页。
  • VACUUM能清理死行,但如果碰到旧快照阻塞、页内碎片、或某些页被跳过,你很难知道清理不彻底的直接原因。
  • pg_stat_progress_vacuum能看到VACUUM进行到哪一步,但看不到它为什么在这个页面停住。
  • pageinspect能直接咔嚓一刀切开页面,告诉你某一页里有多少个有效行、多少个死行、这些行的事务状态是什么、行指针指向哪里。

这也是我常用的组合拳:先用统计视图发现问题,再用pageinspect定位具体页面,最后根据页面细节决定是VACUUM、VACUUM FULL、REINDEX,还是直接物理重建。

1.2 什么时候你会真正需要它

不是每个问题都要上pageinspect。我个人遇到的需求大致分几类:

  1. 表文件占用异常增长,但VACUUM之后空间不回收。
  2. 索引扫描结果和顺序扫描不一致,怀疑索引损坏。
  3. 崩溃恢复后某些页数据异常,需要确认损坏范围。
  4. 想研究PostgreSQL内部机制,比如事务可见性、MVCC实现细节。
  5. 排查wal日志、checkpoint相关的底层行为。

前两类是生产环境的真实痛点,后两类更偏开发和原理研究。无论哪种,pageinspect都能给出SQL层拿不到的证据。

2. 安装与版本匹配:五秒钟装好,但细节容易翻车

pageinspect的安装本身特别简单,一条命令的事:

CREATE EXTENSION pageinspect;

前提是PostgreSQL的contrib包已经装了。用编译安装的源码包,在contrib/pageinspect目录下执行make && make install;用apt/yum装的一般是postgresql-contrib这个包。这些就不展开了,生产库上唯一要记住的是——在执行CREATE EXTENSION之前确认好版本。

2.1 版本检查这件事为什么重要

pageinspect的函数大多直接解析页面结构,而页面结构在不同大版本里是有变化的。以最常见的get_raw_page(text, integer, text)为例,旧版本里fork参数是整数,新版本改成了文本;heap_page_items返回的字段版本间也有增删。

我曾在一套旧版本实例上直接使用了新版本命令,结果函数压根不存在,报错信息还特别含糊。所以装扩展前先跑一下:

SELECT extversion FROM pg_extension WHERE extname = 'pageinspect';

然后对照官方发布说明里的变更点。这里给个大概的版本对照经验(说明:这是基于常见发行版的实际观察,不同小版本之间可能存在细微差异):

PostgreSQL版本pageinspect版本主要变化
9.x1.5及以前heap_page_items字段较少,无vm_page_items
10.x ~ 12.x1.6 ~ 1.7增加page_checksum等功能,函数签名调整
13.x ~ 14.x1.8 ~ 1.9heap_page_item_attrs的返回字段更完整
15.x及以上1.10+支持更多新页面格式,btree函数调整较大

这个表不是官方文档的抄录,是我在不同环境里实际操作下来的大致记忆。真到某个具体环境,最可靠的办法还是"先试一个函数,再看报错"。

2.2 权限模型边界

pageinspect的权限问题容易被忽略。默认情况下,get_raw_page(text, integer, text)这类函数只允许超级用户执行。普通业务账号即使有表的SELECT权限,也可能在执行时收到类似"permission denied"的报错。原因是它要读取文件系统层面数据,风险等级偏高。

如果确实需要给某个运维账号放开,建议单独建角色,只授权特定函数,并且严格控制在使用范围。我见过有人在生产库直接给所有业务账号授权,后来安全扫描直接报警,又灰头土脸逐个回收。游戏规则是:pageinspect是诊断工具,不是常规巡检工具,权限越窄越好。

2.3 官方函数的整体地图

打开contrib/pageinspect的sql文件,你会看到一组函数。我整理了一份最常用的清单,后面几节会结合实战逐个细说:

函数名作用典型场景
get_raw_page(text, integer, text)获取指定文件的原始页字节所有检查的起点
page_header(bytea)解析页头信息判断页是否损坏
heap_page_items(bytea)解析堆表页内的行项死行分析、事务状态排查
heap_page_item_attrs(bytea, text, integer)在行项基础上解析字段属性查看行数据具体内容
bt_page_stats(text, integer)获取B-tree索引页统计索引页空闲、溢出判断
bt_page_items(text, integer)罗列B-tree索引页条目索引结构检查
fsm_page_contents(bytea)解析空闲空间映射页空间分配问题
vm_page_contents(bytea)解析可见性映射页了解index-only scan线索
page_checksum(bytea, integer)计算页校验和校验损坏页

先记住它们的关系:大部分高层函数(heap_page_items、bt_page_stats等)都接受低层函数get_raw_page的返回结果作为输入。也就是说,get_raw_page相当于一把撬锁工具,后面的函数才负责把锁撬开之后的东西分类摆好。

3. 从页头开始拆解:page_header与heap_page_items的解析逻辑

pageinspect最有价值的用法,是先定位一个页,然后逐层分析。我在测试环境里构造过一张简单的表来做示范,你可以照着自己试试。

CREATE TABLE demo_page(id integer primary key, name text); INSERT INTO demo_page SELECT g, 'name_' || g FROM generate_series(1, 1000) AS g; DELETE FROM demo_page WHERE id % 100 = 0;

先拿一个页的原始数据:

SELECT get_raw_page('demo_page', 0, 'main') AS raw_page;

默认的main fork存储主堆数据。如果你指定0号块,就能拿到第一页的8KB原始字节。这串字节直接看是乱码,所以要用page_header解析页头:

SELECT * FROM page_header(get_raw_page('demo_page', 0, 'main'));

3.1 页头字段到底在说什么

PostgreSQL的页头结构固定为24字节,page_header会返回这些字段:

  • pd_lsn:最近一次修改该页的WAL日志位置,它告诉你这一页最后一次被改动是在哪个日志序列号上。
  • pd_flags:位掩码标志位,常见的是PD_HAS_FREE_LINES等。
  • pd_lower/pd_upper:这两个字段非常关键。页面空间布局是"两头往中间挤":页头从低地址(页首)往下放,行数据从高地址(页尾)往上放。pd_lower指向空闲区起点,pd_upper指向空闲区终点。如果你看到pd_lower接近pd_upper,说明页面内存被占得差不多;反之则说明页内碎片化严重。
  • pd_special:特殊空间的起始位置,索引页用它存储内部结构信息。
  • pd_pagesize_version:页面大小和格式版本,正常是8192字节配页面版本号。
  • pd_prune_xid:最近一次页裁剪的XID,是VACUUM判断能否裁剪页内死行的依据之一。

我在实际排查时最关注的是pd_lower和pd_upper的差值。有一次一张表的页面占用越来越大,但行的平均长度没变,用page_header看了一轮,发现大量页面的空闲空间被"拆碎"成了很多小片,每页的pd_upper与pd_lower之间都残留大量不可合并的空隙——这实际上就是页内碎片化,后面DROP掉大量行之后,VACUUM并没有把碎片重新整理回页面末尾。

3.2 heap_page_items:死行和可见性的真相

把页头看明白,再往下走一步就是行项。行项也叫ItemIdData,每一个行指针4字节,指向页内某个实际行数据的位置。heap_page_items能把这一页里的所有行指针都解析出来:

SELECT lp, lp_off, lp_flags, lp_len, t_xmin, t_xmax, t_ctid, t_infomask FROM heap_page_items(get_raw_page('demo_page', 0, 'main'));

结果里你会看到很多行,每行代表一个行项。重点看:

  • lp:行指针编号,1开始。
  • lp_flags:I 表示行指针有效,N 表示无效,U 表示未使用的行指针。
  • t_xmin/t_xmax:插入这条行版本的事务ID和删除/锁定它的事务ID。这是MVCC的核心证据。
  • t_ctid:当前行版本的物理位置,如果指向自己说明这是最新版本;如果指向别的行,说明有更新链。
  • t_infomask:位掩码,记录行版本状态,例如t_xmin是否已提交、t_xmax是否已提交等。

最常见的排查动作是结合t_xmin和t_xmax判断"这一行对某个快照是否可见"。举个场景:表里某个事务删了一大批行,但删除事务还没提交,那么这些行对别人依然可见,VACUUM也不该动它们。你如果直接用heap_page_items看,会发现t_xmax填的是那个删除事务的ID,而t_infomask的某个高位是0——表示删除事务未提交。这种情况下空间不回收是完全正常的行为,不用瞎折腾。

3.3 一个完整的判断案例:死行到底死没死

回到前面我构造的demo表,删除掉id % 100 = 0的行后,跑一次不带有完整清理参数的VACUUM,然后再看:

SELECT count(*) FILTER (WHERE t_xmin > 0 AND t_xmax > 0) AS dead_rows FROM heap_page_items(get_raw_page('demo_page', 0, 'main'));

如果统计出来的dead_rows比预想多,就要排查是不是有非常老的只读事务持有了旧快照。这类场景用pageinspect查,比逮着pg_stat_activity反复刷要直接得多——你能直接看到死行的t_xmax对应的事务ID,然后去pg_stat_activity里按backend_xid签名匹配长时间运行的查询。这在多长事务与VACUUM互相拉扯的案例里,是定位根因的关键路径。

4. 实战排查:表膨胀、VACUUM失效与孤儿数据的典型场景

直接进入生产实战最常遇到的几类问题。每个场景我都尽量把"为什么会这样""用什么函数怎么查""查出来的结果怎么解读"讲透。

4.1 场景一:表文件膨胀但VACUUM后容量不降

这是我最常被问到的。一个表已经跑了VACUUM,但pg_relation_size还是有几十GB,甚至比实际数据量大好几倍。很多人第一反应是"VACUUM不完整",其实更精确的说法是"VACUUM回收了行,但没有把页面末尾的空间返还给操作系统"。

PostgreSQL里,VACUUM会清理死行、合并页内空闲空间,但默认不会把空闲空间截断。只有VACUUM FULL才会重写表文件并收缩。如果你确认清了死行但仍没有降到合理大小,用pageinspect看所有页面的pd_upper与pd_lower:

SELECT blkno, pd_lower, pd_upper, (8192 - pd_lower) AS free_bytes FROM ( SELECT blkno, (page_header(get_raw_page('demo_page', blkno, 'main'))).pd_lower AS pd_lower, (page_header(get_raw_page('demo_page', blkno, 'main'))).pd_upper AS pd_upper FROM generate_series(0, (pg_relation_size('demo_page') / 8192 - 1)) AS blkno ) t WHERE pd_upper - pd_lower < 32 ORDER BY free_bytes DESC LIMIT 20;

这串SQL会找出所有空闲区接近耗尽、几乎无法再插入新行的页。如果这些页很多,说明表确实处于"快满"状态——但里面的数据可能是大量死行占着位置,VACUUM还没来得及处理。这时候再叠加heap_page_items查看具体页的死行占比,就能把"膨胀根因"和"死行位置"一起坐实。

4.2 场景二:确认VACUUM到底有没有清理目标页

有一次某客户环境跑完VACUUM之后,我顺手抽查了若干页,发现某些页面的死行还是原封不动躺在那。原因不是VACUUM偷懒,而是页面头部有PD_ALL_VISIBLE标志问题、或者是页内存在准备中的事务(事务ID处于in-doubt状态)导致清理被跳过。

页内死行的判定条件是t_xmin或者t_xmax对应的事务"对所有快照都不可见"。而要判断这个,最直觉的方式就是对比当前事务快照ID和页面里的事务ID。pageinspect给人一种"硬核证据"的感觉,它把每个行版本的事务信息摊开,你可以精确到"这一页第3个行指针的死行是某个特定事务留下的"。

对照结果如果发现是prepared transaction导致的,那就要去处理两阶段提交残留事务。这也是pageinspect的妙处:它把问题从"VACUUM怎么这么弱"转成"某个具体机制阻止了清理",方向一换,解法立刻清晰。

4.3 场景三:堆表与索引不一致

索引损坏最典型的表现是:走索引扫描和走顺序扫描返回的行数不一样。这种问题通常数据文件本身没坏,而是索引条目和堆表行对应关系错位。用pageinspect读B-tree索引页,再用堆表页交叉比对,可以快速确认损坏范围。

具体做法通常是:

  1. 用bt_page_stats看索引页的整体状态。
  2. 用bt_page_items列出索引页内的键值和指向堆表行位置的指针。
  3. 用heap_page_items检查堆表页里对应的行是否存在。

这是一个"以索引为中心"的排查路径。如果索引条目指向的行在对应页面根本不存在,或者键值与堆表行的实际数据对不上,那就基本实锤索引损坏。后续动作就是CONCURRENTLY REINDEX,避免长时间锁表。

5. 深入B-tree索引:bt_page_stats与bt_page_items的配合使用

索引页和堆表页的布局不完全一样。索引页没有行指针的概念,取而代之的是索引元组数组,页底有一个特殊的"高键/低键"结构用于B-tree的层次遍历。pageinspect为B-tree专门设计了两个函数。

5.1 bt_page_stats的字段怎么读

SELECT * FROM bt_page_stats('demo_page_idx', 1);

返回的字段中比较重要的是:

  • live_items/dead_items:页内有效项和死项数量。死项过多意味着该索引页有大量更新残留。
  • avg_item_size:平均项大小,如果异常变大,可能存入了超大键或者闹鬼了。
  • free_size:页内空闲空间。索引页空闲空间低,说明页快满,可能导致索引分裂。
  • btpo_prev/btpo_next:B-tree兄弟页指针。这两个字段能帮你发现页链断裂——如果相邻页的prev/next对不上,索引结构一定有问题。
  • btpo_flags:页类型标志。比如root页、leaf页、dead页都能从这里看出来。

有一次我排查一个持续增长的索引,发现它的leaf页free_size普遍很低,但索引列基数并不大。顺着btpo_prev/btpo_next走了一批页,发现很多页存在大量重复键,才意识到是应用侧重复写入的问题——这又回到应用逻辑排查了,但pageinspect提供了最早的线索。

5.2 bt_page_items的条目怎么看

bt_page_items会返回索引页内每个条目的详细信息:

SELECT itemoffset, ctid, itemlen, nulls, vars, data FROM bt_page_items('demo_page_idx', 1);
  • ctid指向堆表里的行位置。
  • data是索引键的原始字节。对于整型键可以直接看出来,文本键可能是压缩过的。
  • itemlen是条目长度。

实际排查中,我经常用bt_page_items按ctid分组,统计某个堆表页被多少个索引条目引用。如果引用数量和堆表页里的实际行数不一致,那就是索引与堆表之间的一致性出了问题。这种层面的一致性校验,PostgreSQL平时没有对外暴露直接接口,pageinspect基本是唯一选择。

5.3 用bt_page_stats判断索引空洞

索引的膨胀不像堆表那么直观,但同样存在。大量更新会产生死索引条目,VACUUM之后会释放空间,但如果更新量太大、VACUUM跟不上,你就会发现索引文件明显比实际需要的容量大很多。

判断是否该REINDEX,可以扫描所有索引页:

SELECT blkno AS page, live_items, dead_items, free_size, avg_item_size FROM bt_page_stats('demo_page_idx', 1);

如果某些页面的dead_items比例超过10%,且长期保持,即使跑了VACUUM也不下降,我会建议直接REINDEX CONCURRENTLY。因为索引页的空闲空间如果被死项碎片化,插入新项时很可能触发页分裂,导致索引体积越滚越大,形成恶性循环。这种情况用常规VACUUM已经救不回来,必须重建索引才能让空间布局恢复健康。

6. 可见性映射与空闲空间:两个容易被忽视的底层信息源

除了堆表和索引,PostgreSQL还有两类文件页不太被日常工具触及:可见性映射(VM)和空闲空间映射(FSM)。pageinspect能直接读它们,这在某些疑难杂症里非常有用。

6.1 vm_page_contents:index-only scan的幕后影响者

可见性映射页记录的是"哪些堆页面的所有行对所有事务都可见"。如果VM被破坏,最直接的影响是index-only scan失效——优化器会认为页面上存在不可见行,宁愿回表检查也不走索引覆盖扫描。

SELECT * FROM vm_page_contents(get_raw_page('demo_page', 0, 'vm'));

输出里能看到页面上哪些块被标记为all-visible、哪些块被标记为all-frozen。all-frozen是PostgreSQL在某个版本后引入的机制,用于防止事务ID回卷时反复扫描同一批页面。

如果VM标记和实际页面状态不一致——比如标记all-visible但实际存在未提交事务——会造成非常隐蔽的查询结果错误。这种事极少发生,真要发生多半是底层损坏或某些特殊操作残留。用vm_page_contents和heap_page_items交叉检查,能确认到底是"页面标记错了"还是"行状态异常"。

6.2 fsm_page_contents:空间分配不均的谜底

FSM是PostgreSQL决定"新行插入哪一页"的依据。每个表都有一个FSM文件,以树形结构记录每个页面的空闲空间大小。如果FSM和实际页面空闲空间不一致,可能会产生"表还有大量空闲空间,但新行总是插到某个特定页导致页分裂"的怪象。

SELECT * FROM fsm_page_contents(get_raw_page('demo_page', 0, 'fsm'));

输出里能看到FSM页各个槽位记录的空间区段(0到255之间的一个档位)。当你怀疑表页空间分布不均匀时,用get_raw_page扫描几个main fork页,再对比FSM记录,能快速判断是哪一层出了问题。

这一层用得少,但一旦用上就是救命稻草。我记得某次排查批量插入性能问题,表总体空闲空间充足,但插入动作频繁触发页分裂。最后查FSM发现上层页的空闲空间信息没有及时更新,导致插入路径总是瞄准某些"假满"页面。虽然最终修复还是靠VACUUM和重建FSM,但pageinspect是当时唯一能直接证明问题的工具。

7. 踩坑记录:版本差异、页面缓存、权限还有那些文档没写的事

工具好用,坑也不少。尤其在生产环境里操作,一个不小心不只是查不到数据,还可能把问题放大。

7.1 事务快照带来的"不一致假象"

pageinspect直接读文件页,不受MVCC快照控制。这意味着你看到的内容可能包含"当前事务还没提交的中间状态"。比如在大规模UPDATE进行中,你读取页面,可能看到有些行t_xmax已经设置但事务还没提交,也可能看到某些页面的内容在读取过程中被并发修改。

这不是bug,而是工具本身的设计——它根本不走事务可见性逻辑。所以排查时尽量在业务低峰期执行,或者在明确的时间点快速取样。如果连续两次读取同一页得到的结果不一致,多半是有并发写入正在发生,不要被中间状态误导。

7.2 页面缓存导致重复读结果一样,但不是真相

PostgreSQL有共享缓冲区(shared buffers)。get_raw_page读出来的数据实际上是从共享缓冲区或文件里获取的,一次读取之后,后续同一页的读取可能命中缓存。所以当你修完某个问题后复查页面,如果发现结果没变,先别急着断定"修复无效",考虑下是不是缓存里还留着旧页。

想要真正读到磁盘文件内容,比较粗暴的方法是重启实例(不推荐生产这样做),或者读到不同fork、不同块的页做对比。实践中我更建议这样处理:先明确"我要查的是共享缓冲区里的状态,还是磁盘文件里的状态?"如果是磁盘文件损坏排查,重启后复查会更干净;如果只是解运行时的性能问题,缓存里的状态恰恰更有参考价值。

7.3 函数入参与返回字段的版本差异

pageinspect不同版本的函数行为差异很大,我见过的翻车现场还真不少。

比如get_raw_page(text, integer, text),在旧版本是get_raw_page(text, integer),新版本才加了fork参数,没带fork参数时默认读main fork。如果我在新版本实例上习惯性写了旧版语法,会直接报"function get_raw_page(text, integer, text) does not exist"吗?不会,因为新版保留了兼容重载,但反过来,如果在旧版本上写带fork参数的版本,就会报错。

又比如heap_page_items,旧版返回的t_infomask和t_infomask2已经够用,但新版会增加一些字段帮助你判断行版本状态。写排查脚本时,我会先跑一下SELECT * FROM heap_page_items(NULL)看看实际返回的列,再写正式语句,避免照搬旧笔记白跑一趟。

还有一个坑是不同平台页大小的差异。默认8KB没问题,但如果你初始化PG时用了不同的块大小(比如16KB),pageinspect的某些字段值会显得"不正常"。做对比检查前先确认current_setting('block_size'),这一步别省。

7.4 权限授权的分寸把握

前面提到权限,这里再补充一点:即使你是超级用户,也不要直接在业务高峰期跑全表页扫描。比如用generate_series列出全部分区、对每个分区都调用get_raw_page和page_header,这种操作会瞬间产生海量磁盘读取,把数据库IO打满。

如果想巡检大表的页面健康度,建议分页采样,一次只抽查几个疑似页面,不要一股脑全扫。毕竟pageinspect的目标是"定位与取证",不是"批量巡检"。哪怕只是读取,也要有章法,否则排查问题的过程中又制造了新问题。

7.5 VACUUM之后立刻查页面,结果可能"过早乐观"

VACUUM本质上是延迟清理机制。它把死行标记为可重用,但不代表页面立刻变得整齐——页内空间依然存在碎片。你跑完VACUUM立刻用heap_page_items看,可能还是会看到不少lp_flags为N或者死行,这不代表VACUUM没用,而说明页内这些槽位尚未被新行复用。

想确认碎片是否真的被整理,需要看pd_lower和pd_upper的位置变化,而不是只看行指针有没有清零。这也是我多次提到的点:读pageinspect的结果时,脑子里要有"页面布局"这个坐标轴,否则容易被字段值带偏。

8. 我在实际使用中的一点建议

pageinspect不该是你遇到问题第一个上手的工具,但也千万别让它只躺在contrib目录里吃灰。常规检查顺序我会这样安排:先看统计信息、看等待事件、看锁冲突;解决不了,再看WAL日志和错误日志;都无果,才轮到pageinspect出手。它的价值不在于日常高频使用,而在于让你在走投无路时有一条能直接看到存储层真相的路。

如果你还没用过,建议先在测试实例上建个小表,插入几万行、删掉一批、更新一批,然后用page_header和heap_page_items反复观察页面的变化。把页头字段变化、死行数量变化、pd_lower/pd_upper的移动方向都记下来,下次生产环境遇事你就有底了。真正到了线上排查的时候,这种"底层直觉"比任何脚本都管用。

相关新闻

Playwright MCP:AI驱动的浏览器自动化与跨平台测试

Playwright MCP:AI驱动的浏览器自动化与跨平台测试

这两年做Web自动化测试&#xff0c;我从早期的录制回放工具一路换到代码级框架&#xff0c;老实说&#xff0c;真正让我觉得"工具终于跟上人了"的节点&#xff0c;是Playwright MCP这套组合出现之后。以前写一条跨平台测试用例&#xff0c;要先关心浏览器驱动、等待时…

2026/10/22 9:59:19 阅读更多 →
Text-to-Prototype与D2C实战:从一句话到可交互原型的工程化工作流

Text-to-Prototype与D2C实战:从一句话到可交互原型的工程化工作流

一句话丢给 AI&#xff0c;它真能吐出一套能点、能跳转、能填表单的 App 原型&#xff1f;这个问题在过去一年里被问了无数遍&#xff0c;我自己也拿它当正经课题折腾了小半年。Text-to-Prototype&#xff0c;也就是文本生成原型&#xff0c;再到 D2C&#xff08;Design to Cod…

2026/10/21 7:54:10 阅读更多 →
2026年Agent智能体趋势观察:从玩具到员工的关键跃迁

2026年Agent智能体趋势观察:从玩具到员工的关键跃迁

想要准确判断一份行业报告的含金量&#xff0c;其实看两样东西就够&#xff1a;它可以不用被证伪的底层逻辑&#xff0c;以及它敢不敢给出明确的到达时间点&#xff0c;而不是和稀泥。最近我深度研读了一份来自某头部科技公司研究团队的调研报告预览稿&#xff0c;报告的名字很…

2026/10/22 11:40:54 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →