提问人:some_guy 提问时间:8/11/2023 最后编辑:some_guy 更新时间:8/12/2023 访问量:82
Postgres 忽略了我在数组(字符串)列上的 GIN 索引
Postgres is ignoring my GIN index on an array (strings) column
问:
我在 Students 表上有一个数组列(字符串)。此列可能(通常)包含数百个元素。
对于我的一个查询,我使用运算符将另一个数组(也可能有数百个元素)与该数组进行比较,以查看是否有任何重叠。它看起来像这样:&&
SELECT students.*
FROM students
WHERE (students.read_allow_sids && ARRAY['long', 'list', 'of', 'strings'])
AND NOT (students.read_deny_sids && ARRAY['long', 'list', 'of', 'strings'])
在我的测试数据库中,students 表大约有 10k 条记录。但是,我的查询大约需要 4.5 秒才能完成顺序扫描(太长了!
为了提高性能,我在数组中添加了一个 GIN 索引(在 Rails 迁移中):
add_index :students, :read_allow_sids, using: 'gin'
add_index :students, :read_deny_sids, using: 'gin'
我注意到使用 EXPLAIN 时,我的新索引被忽略了,转而支持顺序扫描。我暂时禁用了它来检查索引的性能。瞧,一旦查询开始使用索引,它就从 4.5 秒 -> 200 毫秒(大约快 20 倍!SET enable_seqscan TO OFF;
编辑:这是查询的 EXPLAIN(ANALYZE, VERBOSE, BUFFERS) 的输出(开启与关闭 seq 扫描)
开启后:
Seq Scan on public.students (cost=0.00..970.57 rows=786 width=522) (actual time=3.429..4319.502 rows=1698 loops=1)
Output: id, classroom_id, created_at, updated_at, user_id, read_allow_sids, read_deny_sids, write_allow_sids, write_deny_sids
Filter: ((students.read_allow_sids && '{long, list, of, strings}'::character varying[]) AND (NOT (students.read_deny_sids && '{long, list, of, strings}'::character varying[])))
Rows Removed by Filter: 9554
Buffers: shared hit=803
Planning Time: 20.932 ms
Execution Time: 4320.698 ms
关闭时:
Bitmap Heap Scan on public.students (cost=1734.59..2634.39 rows=786 width=522) (actual time=127.219..137.151 rows=1698 loops=1)
Output: id, classroom_id, created_at, updated_at, user_id, read_allow_sids, read_deny_sids, write_allow_sids, write_deny_sids
Recheck Cond: (students.read_allow_sids && '{long, list, of, strings}'::character varying[])
Filter: (NOT (students.read_deny_sids && '{long, list, of, strings}'::character varying[]))
Heap Blocks: exact=549
Buffers: shared hit=1390
-> Bitmap Index Scan on index_students_on_read_allow_sids (cost=0.00..1734.40 rows=6453 width=0) (actual time=126.842..126.843 rows=1698 loops=1)
Index Cond: (students.read_allow_sids && '{long, list, of, strings}'::character varying[])
Buffers: shared hit=841
Planning Time: 21.460 ms
Execution Time: 137.890 ms
我希望 postgres 查询规划器在规划查询时选择最佳选择(即 GIN 索引),但事实并非如此。查询一开始非常简单......目前尚不清楚我将如何更改它以使查询计划器更喜欢索引。我已经熟悉 Postgres 没有“提示”的事实。我也已经尝试在 students 表上运行 ANALYZE 来更新其统计信息,但查询计划器仍然选择 seq 扫描。
所以最大的问题是:
- 如果索引速度如此之快,为什么查询规划器更喜欢 seq 扫描?
- 更重要的是,我们如何才能让查询规划器使用在生产环境中可行的方法更喜欢索引(即正确计划)?
答:
索引扫描的成本要高得多,为 2634.39,而顺序扫描的成本为 970.57。当这不能反映实际情况时,您可以降低random_page_costs和/或cpu_index_tuple_cost以支持索引扫描。
SET random_page_costs TO 1.1; -- close to seq_page_cost
SET cpu_tuple_cost TO 0.1; -- to increate the sequential scan costs
SET cpu_index_tuple_cost TO 0.001; -- to lower the costs for an index scan
然后再次运行。现在,您应该会看到查询成本的不同结果,并且可能看到不同/更好的查询计划。explain(analyze, verbose, buffers, settings)
当您找到适用于此查询和其他查询的设置时,您可以将 设置为 postgresql.conf 或作为数据库的属性。
ALTER SYSTEM SET random_page_cost TO 1.1;
-- or:
ALTER DATABASE your_database SET random_page_cost TO 1.1;
完成后:
SELECT pg_reload_conf(); -- as superuser
您也可以为每个数据库角色设置此项。
评论
random_page_cost
0.5
seq_page_cost
1.0
random
seq
cpu_index_tuple_cost
评论
EXPLAIN(ANALYZE, VERBOSE, BUFFERS)
random_page_costs