【发布时间】:2018-03-29 17:55:32
【问题描述】:
我遇到了一个问题,即已编入索引的查询拒绝使用索引,因为它的选择性不够(假设 1.3 亿行中有 60 行满足条件),因此决定使用 seqscan。
我面临的问题是 seqscan 在这种情况下确实不是最佳选择,由于某种原因它得到了非常好的分数,但事实是 seqscan 只有在之前被查询过并且它运行得很快可以从缓冲区/缓存中加载所有内容。
与 seqscan 相比,如果它们都在缓冲区上,索引扫描可能会稍微慢一些,但这种情况很少发生,当两个查询都是冷的时,索引扫描仍然更快(毫秒 vs 秒)。
请注意,索引扫描更胜一筹,因为我使用了限制子句,因此它应该能够非常快速地拾取那几行。
我已将统计值设置为 1000(默认为 100)并清空以防万一,但情况相同。
TLDR:Seq 扫描与低选择性索引上的索引扫描,seqscan 是首选,但规划器是错误的,seqscan 只有在缓存时更好,否则会更糟。
查询和计划,注意索引一是从缓冲区加载的,而 seqscan 没有完全加载。
explain (analyze, buffers)
select *
from identities_identity
where email_domain = 'live.com'
limit 100
'Limit (cost=0.00..63.50 rows=100 width=573) (actual time=75215.573..75215.640 rows=100 loops=1)'
' Buffers: shared hit=75113 read=588870'
' -> Seq Scan on identities_identity (cost=0.00..2980008.00 rows=4692733 width=573) (actual time=75215.571..75215.604 rows=100 loops=1)'
' Filter: ((email_domain)::text = 'live.com'::text)'
' Rows Removed by Filter: 54464136'
' Buffers: shared hit=75113 read=588870'
'Planning time: 0.097 ms'
'Execution time: 75215.675 ms'
'Limit (cost=0.57..187.26 rows=100 width=573) (actual time=0.027..0.090 rows=100 loops=1)'
' Buffers: shared hit=6'
' -> Index Scan using identities_identity_email_domain_9056bd28 on identities_identity (cost=0.57..8760978.66 rows=4692733 width=573) (actual time=0.026..0.057 rows=100 loops=1)'
' Index Cond: ((email_domain)::text = 'live.com'::text)'
' Buffers: shared hit=6'
'Planning time: 0.078 ms'
'Execution time: 0.124 ms'
更新:
表定义(电子邮件和电子邮件域的索引,标准和 varchar_pattern_ops 之一)
CREATE TABLE public.identities_identity
(
id bigint NOT NULL DEFAULT nextval('identities_identity_id_seq'::regclass),
email character varying(1000) COLLATE pg_catalog."default",
email_domain character varying(1000) COLLATE pg_catalog."default",
leak_id bigint NOT NULL,
CONSTRAINT identities_identity_pkey PRIMARY KEY (id),
CONSTRAINT identities_identity_leak_id_87e1ae4e_fk_identities_leak_id FOREIGN KEY (leak_id)
REFERENCES public.identities_leak (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
DEFERRABLE INITIALLY DEFERRED
)
表统计信息(真空分析后
attname, avg_width, n_distinct, correlation
'id',8,'-1','0.999988'
'email',23,'-0.636853','-0.020479'
'email_domain',10,'3876','0.696452'
'leak_id',8,'1','1'
【问题讨论】:
-
有趣的是,即使使用 random_page_cost=0.05(一个无意义的值),它仍然会为一些最大的查询(如“gmail”)选择 seqscan,尽管它会切换到对一些较小的索引(如'live.com'。但是,带有冷缓冲区的索引仍然需要几毫秒,而 seqscan 甚至可能需要几分钟,这里确实很糟糕。
-
我想知道这个问题与数据可能分布不均匀有关,因为它是按域插入的(即首先是所有 gmail.com,然后是 live.com,等等..)
-
你试过
set enable_seqscan = false;吗? postgresql.org/docs/current/static/runtime-config-query.html -
这个问题显然与数据分布有关——PostgreSQL 认为它会在顺序扫描中快速获取前 100 个匹配行,但事实并非如此。该表包含多少行?
-
DaveGray 是的,这就是我获取索引扫描计划的方式,否则它将始终使用 seq 扫描。 @LaurenzAlbe 这就是我的想法,postgres 不应该更聪明吗?我尝试将统计数据设置为 10000 进行 vacum 分析,但结果是相同的。有哪些选项可以解决此问题?数据插入和自动 ID 顺序几乎都将大约 5000 万行组合在一起,我应该尝试在其他索引上进行 CLUSTER 吗?否则如何随机分布数据?
标签: postgresql performance indexing sql-execution-plan seq