PostgreSQL自定义Bitmap索引扩展大范围扫描时崩溃(TIDBitmap损坏)
PostgreSQL自定义Bitmap索引扩展大范围扫描时崩溃(TIDBitmap损坏)
看起来你遇到了一个特别诡异的问题——同样的查询条件,SELECT *能正常返回结果,但SELECT count(*)直接把PostgreSQL进程搞挂了,而且错误出在TIDBitmap的销毁阶段,这确实让人挠头。我来帮你梳理下可能的原因和排查方向:
先明确环境与核心问题
环境信息
- PostgreSQL版本:17.4
- 系统/架构:Ubuntu 22.04 x86_64
- 扩展:自定义Bitmap索引扩展(源码编译安装)
核心现象
- 执行
SELECT * FROM people WHERE age BETWEEN 25 AND 27:正常返回240行结果,扩展打印的TID日志也无异常; - 执行相同条件的
SELECT count(*):PostgreSQL进程意外终止,连接直接断开,错误栈指向pagetable_destroy→tbm_free→ExecEndBitmapHeapScan,说明TIDBitmap在销毁时内存结构已损坏。
为什么SELECT *和count(*)表现不一样?
这是因为PostgreSQL对两个查询的执行处理逻辑有细微差异,刚好触发了扩展中的内存隐患:
SELECT *需要遍历TIDBitmap取出每行数据,可能在遍历过程中没踩到损坏的内存区域;SELECT count(*)不需要返回实际行,Executor的清理阶段会更早销毁TIDBitmap,此时已被非法修改的TBM内存结构触发了崩溃;- 简单说:内存损坏已经发生,只是
SELECT *没触发错误,count(*)刚好撞在了这个“雷”上。
排查方向与解决方案
1. 优先排查TIDBitmap的内存越界/非法写入
你说已经验证过插入的TID是正确的,但要注意几个细节:
- 检查TID的合法性:PostgreSQL堆表的TID中,block号从0开始,offset从1开始,你扩展输出的
ItemPos block=1 offset=12是否是正确的转换结果?有没有出现block=0、offset=0的非法TID? - 检查内存越界:比如在批量处理TID时,是否循环次数超出数组长度,写入了不属于TBM的内存区域;
- 禁止直接修改TBM内部结构:PostgreSQL的TIDBitmap是封装的黑箱,必须用官方提供的API(
tbm_add_tid、tbm_union等)操作,不要直接修改pagetable等内部字段。
2. 检查TBM的引用计数与生命周期
PostgreSQL的TIDBitmap用引用计数管理内存,一旦处理不当就会出现重复释放/野指针:
- 确认扩展是否手动调用过
tbm_free:如果扩展已经释放了TBM,但Executor在清理阶段会再次释放,就会导致双重释放,直接触发崩溃; - 检查
getbitmap函数的返回逻辑:确保返回的TBM指针正确设置了引用计数(比如用tbm_ref增加引用),避免出现野指针。
3. 用调试工具定位具体错误
这种内存损坏问题,光靠日志很难定位,必须用调试工具:
(1)用gdb+core dump抓崩溃现场
- 先开启core dump:
ulimit -c unlimited; - 重新执行
count(*)查询触发崩溃,系统会生成core文件; - 加载调试文件:
gdb /path/to/your/postgres/build/bin/postgres core.xxxx; - 执行
bt full查看完整栈帧,p tb查看损坏的pagetable_hash结构,就能看到具体的非法内存地址或条目。
(2)编译带断言的PostgreSQL
编译PostgreSQL时加上--enable-cassert --enable-debug,一旦有内存越界或非法操作,会直接触发断言失败,打印更详细的错误信息,而不是直接静默崩溃。
4. 对比两个查询的执行计划
即使条件相同,执行计划也可能有差异,先确认路径是否一致:
EXPLAIN ANALYZE SELECT * FROM people WHERE age BETWEEN 25 AND 27; EXPLAIN ANALYZE SELECT count(*) FROM people WHERE age BETWEEN 25 AND 27;
检查两个查询是否都走了自定义Bitmap索引扫描,count(*)是否使用了Bitmap Index Scan + Aggregate的组合,而SELECT *是Bitmap Index Scan + Bitmap Heap Scan,不同的执行路径可能导致TBM的生命周期不同。
最后总结
这个问题的本质是内存损坏——你的扩展在处理TIDBitmap时不小心破坏了其内存结构,count(*)的清理逻辑刚好触发了这个错误。重点排查内存越界、引用计数错误、TID合法性这几个点,用gdb和带断言的PostgreSQL能帮你快速定位到具体的错误代码行。




