最近看线上的数据库的慢日志,发现有些一条非常慢的sql。
SELECT
*
FROM
"batch"
WHERE
compensation_end_time >= '2024-07-31 21:02:58+08'
AND "subsidy_id" = '100498835946114078'
AND "type" = '1'
AND status NOT IN ('4','6','5');
但是已经存在这个索引了
create index idx_batch_subsidy_status
on batch (subsidy_id, status);
所以执行explain显示
Gather (cost=1000.43..334292.14 rows=1455 width=533)
Workers Planned: 2
-> Parallel Index Scan using batch_subsidy_id_status_idx on batch (cost=0.4
3..333146.64 rows=606 width=533)
Index Cond: (subsidy_id = '100498835946114078'::bigint)
Filter: ((compensation_end_time >= '2024-07-31 21:02:58+08'::timestamp
with time zone) AND (type = '1'::smallint) AND (status <> ALL ('{4,6,5}'::smalli
nt[])))
但是因为索引过滤掉的数据不多,后面留给通过filter过滤的太多,导致缓慢造成的。
第一时间想到的是, 加一个下面的索引, 然后把上面已经存在的索引删除掉,因为下面这个明显包含上面那个了。
CREATE INDEX CONCURRENTLY idx_batch_subsidy_status_type_compensation
ON batch (subsidy_id, status, type, compensation_end_time);
测试发现,加上新的以后,查询依然不走新索引,走的依然是旧的索引。非常奇怪,最终分析发现,是表某些subsidy_id关联的数据存在比例,已经不符合之前的预期了。正常业务预期status比type更具有选择性一些,甚至不用关心type,但是因为某些业务使用习惯,产生了很多条数据,比如有百万条。但是type =1的一条都没有,索引建立的时候status在前面,符合条件的本来就很多,通过subsidy_id+status 查询出来符合条件的本来就很多。type索引的选择性不强,索引没有去走索引,而是通过过滤。考虑到这个以后,调整新的索引位置。
CREATE INDEX batch_subsidy_id_idx ON public.batch (subsidy_id,"type",status,compensation_end_time);
第一步通过type就直接过滤掉不存在的数据,后面也不用去过滤了。此时sql执行就正确利用索引了,原来旧的subsidy_id + status的索引继续保留。