案例:postgre数据库里面的transaction表采用了范围分区,按照月进行划分,同时有一个默认分区,存储当前月的数据。
select * from transaction where id in ()
上面根据这个主键id用in查询特别慢,当id的数量大于4时候就卡死了,小于4的时候非常快,in里面的id是否相同结果都一样。因为数据表是按照时间分区的,按照主键查询必定要跨越所有的分区查询数据,然后进行数据合并,但是每个分区内的数据主键是有索引的,所以理论上不会很慢才对。
通过explian发现当id个数小于4的时候发现每个分区有两种执行计划,都走了索引所以总体还是比较快的。
-
Bitmap Heap Scan
:这种是走索引了,但是对每个id查询出的结果建立位图,最后按位图将结果合并。 -
Index
就是走主键索引。
但是当id超过4个,通过执行计划可以看到有2个分区执行计划变成了Seq Scan ,也就是顺序扫描,是这里导致查询缓慢的。
这个不走索引大概是postgre觉得这种场景下走索引不划算,虽然id在每个分区是主键,但是分区表里面主键必须包含分区键字段,也就是分区表是id+created_at
联合构成的,所以这里查询应该是和那种不分区的表只用id做主键的有区别,这里走的其实是一个联合索引。
网上找了很久没有解决办法,同时奇怪的是只在sandbox环境有问题,线上却没有问题,sandbox数据量比线上要少一些。
解决方案如下
-
目前这些id业务中大部分都是处于default的分区的,所以先对这些id进行分组,然后手动的对每个分区表查询对应的id,这个最可控,也是最终采用的办法。
-
尝试对id单独建立一个唯一索引,走这个单独的唯一索引肯定比之前的联合索引高,但是索引也是有代价的,数据本来就比较多了,单独建立一个这个不划算。
-
执行这个查询前调用
SET enable_seqscan=true
,这个禁止走串行扫描,可以解问题,但影响面太大。
网友评论