我遇到的问题是,索引的查询拒绝使用索引,因为它没有足够的选择性(让我们说1.3亿行中的60个符合条件)因此决定使用seqscan。
我面临的问题是seqscan在这种情况下真的不是最好的选择,由于某种原因它获得了非常好的分数,但事实是seqscan只有在之前被查询时才能快速运行它可以加载缓冲区/缓存中的所有内容。
与seqscan相比,索引扫描可能会稍慢一些,如果它们都在缓冲区上,但这很少发生,当两个查询都很冷时,索引扫描仍然更快(ms 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'
更新:
表格def(电子邮件和email_domain上的索引,标准和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'
答案 0 :(得分:2)
您可以使用平均技巧来强制进行索引扫描:
SELECT *
FROM identities_identity
WHERE email_domain IN ('live.com', NULL)
ORDER BY email_domain
LIMIT 100;
如果PostgreSQL必须排序,使用索引总是会更便宜。
如果你有WHERE email_domain = 'live.com'
,PostgreSQL很聪明,知道它不需要排序,这就是为什么我添加了第二个无用的项目来欺骗它。
答案 1 :(得分:1)
嗯,解决方案是物理地重新排序数据,因此对这些特殊情况的顺序扫描不会失败。
基本上,在使数据均匀分布的列上运行CLUSTER identities_identity USING index_name;
(例如电子邮件域之前的值)。
顺序扫描现在运行得很好,即使使用冷缓冲也是如此。
但是,对于我发布的具体案例,@ Laurenz Albe的回答非常好,如果无法进行聚类,这是一个很好的技巧。