PG位图索引扫描优化之recheck
Tags: PGPostgreSQLrecheck优化位图堆扫描位图索引扫描索引
简介
PG中的索引扫描可以参考:https://www.xmmup.com/pgzhongdesuoyinsaomiaoleixing.html
普通的索引扫描(Index Scan)一次读一条索引项,而位图索引扫描(Bitmap Index Scan)
一次性将满足条件的索引项全部取出,并在内存中进行排序,然后根据取出的索引项访问表数据。由于位图索引扫描(Bitmap Index Scan)
会输出整个数据块的数据,因此需要recheck
,对输出的索引项进行过滤,此过程就是位图堆扫描(Bitmap Heap Scan)
。BitMap Heap Scan
就是针对有多个索引项满足条件时,通过饱和式的索引页读取结合排序大幅减少随机读取,提升I/O
致率
也就是说位图索引扫描(Bitmap Index Scan)
会获取整个数据块的内容,所以需要进行recheck
操作,如果一个块的数据只有少量是和索引匹配的,那么recheck
的值就会很大,导致非常耗时
在 PostgreSQL
中,recheck
是指在执行计划中使用位图索引扫描(Bitmap Index Scan)时,需要再次检查索引中的行是否满足查询条件。这通常发生在以下情况下:
- 1、查询条件中包含了索引无法覆盖的列,例如查询语句中包含了
SELECT
子句中未包含的列,或者包含了函数调用等无法使用索引的操作。 - 2、查询条件中包含了不等于
(<>)
或者类似于(LIKE)
等无法使用索引的操作符。
在这些情况下,PostgreSQL
会使用索引扫描来查找满足查询条件的行,但是需要在扫描完索引后再次检查每一行是否真正满足查询条件。这个过程就称为 recheck
。
需要注意的是,recheck
操作会增加查询的开销,因为需要对每一行进行再次检查。如果查询条件中包含了无法使用索引的操作符,可以考虑优化查询语句,或者使用其他类型的索引来覆盖查询条件。
- 在
PostgreSQL
中,当使用索引扫描时,如果需要进行recheck
操作,可能会出现 rows removed by index recheck 的提示信息。这个提示信息表示,在recheck
操作中,有一些行被删除了,因为它们不满足查询条件。 - 增加
work_mem
的值可以减小rows removed by index recheck
recheck
在pg
中会经常见到,在索引扫描bitmap heap scan
的时候会有该操作。- 除了
recheck cond
之外可能还会有rows removed by index recheck
操作 - 在某些情况下低版本走
bitmapscan
的时候由于recheck
导致效率低
recheck值很大的原因
- 1、索引选择不当:如果索引选择不当,即索引列的选择性较低,可能导致许多不满足查询条件的行被包含在索引扫描结果中。在索引扫描后,需要进行进一步的过滤来移除不满足查询条件的行,这将增加
Rows Removed by Index Recheck
的数量。需要重建索引,将唯一值高的列作为索引的引导列。 - 2、查询条件与索引列不匹配:如果查询条件与索引列不完全匹配,可能会导致索引重新检查的行数增加。例如,如果查询条件涉及到索引列以外的其他列,那么索引可能无法准确过滤掉不满足条件的行,因此需要进行重新检查。
- 3、统计信息过期或不准确:如果索引的统计信息过期或不准确,优化器可能无法正确估计索引扫描的行数,从而导致较多的行被重新检查。在这种情况下,可以使用
ANALYZE
命令更新索引的统计信息,以确保优化器能够做出更准确的估计。 - 4、数据分布不均匀:如果数据在索引列上分布不均匀,可能会导致索引重新检查的行数增加。当数据分布不均匀时,索引扫描可能会选择更多的行,其中一些行可能不满足查询条件,需要进行重新检查。
- 5、优化方法:
- 确保索引选择正确,尽量选择选择性较高的索引,并根据查询条件进行优化。
- 检查并更新索引的统计信息,以确保优化器能够准确估计索引扫描的行数。
- 考虑重新设计索引或更改查询条件,以减少需要重新检查的行数。
- 对于数据分布不均匀的情况,可以考虑使用表分区或其他技术来平衡数据分布。
下面以一条慢SQL
在pg9.5.3
和pg12.8
验证
PG9.5.3
SQL如下:
1 2 3 4 5 6 7 8 9 10 | SELECT count(*) FROM db_test.t_test1 wt WHERE wt.c_col1 != '2550' AND wt.c_col5 <> '1' AND wt.c_col5 <> '8' AND wt.c_col5 <> '9' AND wt.c_col3 = '2550' AND (wt.c_col4 <> '3' OR wt.c_col5 = '4'); |
1、sql
表里面c_col3
有索引,但是查询并没有走索引
1 2 3 4 5 6 7 | Aggregate (cost=528258.49..528258.50 rows=1 width=0) (actual time=7892.472..7892.473 rows=1 loops=1) -> Seq Scan on t_test1 wt (cost=0.00..526332.68 rows=770327 width=0) (actual time=4.831..7808.734 rows=783982 loops=1) Filter: (((c_col1)::text <> '2550'::text) AND ((c_col5)::text <> '1'::text) AND ((c_col5)::text <> '8'::text) AND ((c_col5)::text <> '9'::text) AND ((c_col3)::text = '2550'::text) AND (((c_col4)::text <> '3'::text) OR ((c_col5)::text = '4'::text))) Rows Removed by Filter: 5216218 Planning time: 0.212 ms Execution time: 7892.623 ms (6 rows) |
2、c_col5
的条件可以改写为not in
形式AND wt.c_col5 not in( '1','8','9')
1 2 3 4 5 6 7 | Aggregate (cost=505754.85..505754.86 rows=1 width=0) (actual time=6282.974..6282.975 rows=1 loops=1) -> Seq Scan on t_test1 wt (cost=0.00..503832.04 rows=769127 width=0) (actual time=0.040..6201.733 rows=783982 loops=1) Filter: (((c_col1)::text <> '2550'::text) AND ((c_col3)::text = '2550'::text) AND ((c_col5)::text <> ALL ('{1,8,9}'::text[])) AND (((c_col4)::text <> '3'::text) OR ((c_col5)::text = '4'::text))) Rows Removed by Filter: 5216218 Planning time: 6.709 ms Execution time: 6283.219 ms (6 rows) |
可以看到将AND wt.c_col5 <> '1' AND wt.c_col5 <> '8' AND wt.c_col5 <> '9'
改成not in
的形式是有提升的,所以大家要注意书写规范
3、由于sql
没有走索引所以常见建一个条件索引
1 2 3 4 5 6 7 8 9 10 11 | CREATE INDEX i_t_test1_stgy_stfy ON t_test1 USING btree (c_col3) WHERE (c_col1 <> '2550' AND c_col5 NOT IN ('1', '8', '9') AND (c_col4 <> '3' OR c_col5 = '4')); Aggregate (cost=396268.94..396268.95 rows=1 width=0) (actual time=7358.981..7358.981 rows=1 loops=1) -> Bitmap Heap Scan on t_test1 wt (cost=14545.84..394326.55 rows=776956 width=0) (actual time=346.334..7261.396 rows=783982 loops=1) Recheck Cond: (((c_col3)::text = '2550'::text) AND ((c_col1)::text <> '2550'::text) AND ((c_col5)::text <> ALL ('{1,8,9}'::text[])) AND (((c_col4)::text <> '3'::text) OR ((c_col5)::text = '4'::text))) Rows Removed by Index Recheck: 4090429 Heap Blocks: exact=27813 lossy=289763 -> Bitmap Index Scan on i_t_test1_stgy_stfy (cost=0.00..14351.60 rows=776956 width=0) (actual time=334.544..334.544 rows=783982 loops=1) Index Cond: ((c_col3)::text = '2550'::text) Planning time: 0.550 ms Execution time: 7359.139 ms |
可以看到虽然走了索引Bitmap Heap Scan,但是变得更慢了
4、创建组合索引
1 2 3 4 5 6 7 8 9 10 | CREATE INDEX idx_wtzx1 ON db_test.t_test1 (c_col3,c_col1, c_col5, c_col4); Aggregate (cost=42069.55..42069.56 rows=1 width=0) (actual time=731.850..731.851 rows=1 loops=1) -> Index Only Scan using idx_wtzx1 on t_test1 wt (cost=0.43..40129.84 rows=775883 width=0) (actual time=0.360..659.121 rows=783982 loops=1) Index Cond: (c_col3 = '2550'::text) Filter: (((c_col1)::text <> '2550'::text) AND ((c_col5)::text <> ALL ('{1,8,9}'::text[])) AND ((((c_col4)::text = '3'::text) AND ((c_col5)::text = '4'::text)) OR ((c_col4)::text <> '3'::text))) Rows Removed by Filter: 58765 Heap Fetches: 0 Planning time: 0.365 ms Execution time: 731.988 ms |