查看原文
其他

第18期吐槽:都走索引了为什么还要回表访问?原来是索引里缺少了“灵魂”

digoal PostgreSQL码农集散地 2024-07-08

文中参考文档点击阅读原文打开, 同时推荐2个学习环境: 

1、懒人Docker镜像, 已打包200+插件:《最好的PostgreSQL学习镜像

2、有web浏览器就能用的云起实验室: 《免费体验PolarDB开源数据库

3、PolarDB开源数据库内核、最佳实践等学习图谱:  https://www.aliyun.com/database/openpolardb/activity 

关注公众号, 持续发布PostgreSQL、PolarDB、DuckDB等相关文章. 


第18期吐槽:PG 索引中没有存储tuple版本信息

1、产品的问题点

  • PG 索引中没有存储tuple版本信息, 即使仅查询索引包含的字段内容也可能需要回表进行判断.

2、问题点背后涉及的技术原理

  • PG 的索引没有TUPLE版本信息, 所以必须回表才能判断索引指向的tuple是否为dead tuple、是否对当前事务可见. 回表访问就意味着离散IO, 而且还占用更多的shared buffer资源.

    • 为了解决这个问题, PG引入VM file, 记录每个data block的状态, 当datablock为clean状态时, 则不需要回表, 但是也要访问VM才能判断datablock为clean状态, 所以离散IO次数是没有节省下来的, 节省的是IO覆盖范围, 毕竟VM里面2bit就表示1个data block(默认8KB).

3、这个问题将影响哪些行业以及业务场景

  • 对IO敏感的场景, 例如:

    • 多对象的时序、时空场景. 对某个对象, 范围扫描大量记录, 需要回表, 产生大量离散IO. 例如: 查询某个车辆的轨迹数据, 将需要访问上万条离散记录. 查询某个用户在某个时间段的行为, 例如用户消费记录、话费、短视频浏览历史记录、视频历史观看记录、音频历史记录等等.

4、会导致什么问题?

  • 即使采用 include index , 叶子节点包含需要查询的所有数据, 如果需要回表判断, 离散IO依旧是问题(从索引到table或VM文件都是离散IO).

  • 额外对table数据文件的回访, 导致浪费shared buffer.

  • 在删除大量数据后 或者 更新大量数据后, 如果索引的垃圾版本未及时进行垃圾回收, 将导致命中这部分索引的Query性能急剧下降.

5、业务上应该如何避免这个坑

  • 尽量不要使用更新, 并且使用include index, 使得大部分数据块内的记录都是对所有会话可见, 所以不需要回表查询, 减少IO.

  • 尽量不要使用更新, 并且使用多值类型存储多条点的聚合数据, 例如array或json, 使得单一对象的多条记录聚合为1条, 密集存储在少量block里面, 可以降低IO的数量.

  • 按对象ID分区, 使用BRIN索引代替BTREE索引, 减少时序类数据的范围搜索IO.

6、业务上避免这个坑牺牲了什么, 会引入什么新的问题

  • 管理复杂度、程序设计的复杂度均有增加

7、数据库未来产品迭代如何修复这个坑

  • 内核层解决, 降低回表次数, 除了vm file, 是否可以考虑引入索引支持版本更新? 因为本身写入、删除、更新时就需要更新对应的索引tuple, 索引支持记录多版本信息并没有增加更多的IO操作, 只是需要增加存储空间.

  • 或者使用undo这样的存储引擎, 因为是in-place update, 不存在索引多版本的问题.


本期彩蛋-招商中...


文章中的参考文档请点击阅读原文获得. 


欢迎关注我的github (https://github.com/digoal/blog) , 学习数据库不迷路.  

近期正在写公开课材料, 未来将通过视频号推出, 欢迎关注视频号:


继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存