PostgreSQL 和 MySQL 索引行为的三点差异
最近把一个老服务从 MySQL 8 迁到 PostgreSQL 16,迁移前我以为索引就是"建了就快",迁移后才发现两边的行为差异比想象中大。挑三点最影响设计的说说。
第一,聚簇索引的有无。MySQL InnoDB 的表本身就是聚簇索引组织的一棵 B+ 树,主键页面上直接挂着行数据,所以"主键顺序插入"非常快,而用 UUID 当主键会引发大量页面分裂。PostgreSQL 的表是堆存储,索引和数据是两套结构,主键索引里存的是行指针(ctid)。这带来一个反直觉的结论:PG 里用 UUID 主键的代价没有 MySQL 那么夸张,但代价转移到了索引体积上。
第二,回表的成本模型不同。MySQL 的二级索引叶子节点存的是主键值,回表是"拿主键回聚簇树再查一次"。PG 的二级索引存 ctid,回表是"按物理位置直接读堆"。所以在 MySQL 里覆盖索引收益巨大,PG 里因为堆读本身不慢,优化器反而不那么执着于 Index Only Scan——但它的 Visibility Map 一旦没更新,Index Only Scan 会退化成反复回堆检查可见性,这是 PG 特有的坑。
第三,页面分裂的碎屑。InnoDB 每次分裂会保留页空间做顺序插入优化(innodb_fill_factor),PG 的页则填得比较满,随机写多了之后碎片清理全靠 autovacuum。迁移后我们遇到过一次索引膨胀到原数据三倍的情况,REINDEX CONCURRENTLY 才救回来。
总结成一句:MySQL 的索引设计围绕"聚簇"展开,PG 的围绕"堆+指针"展开。把 InnoDB 的直觉直接带进 PG,早晚要还。