Cha*_*ark 10 postgresql performance index postgresql-9.4 query-performance
我有一个包含大约 1000 万行的表和一个日期字段的索引。当我尝试提取索引字段的唯一值时,即使结果集只有 26 个项目,Postgres 也会运行顺序扫描。为什么优化器会选择这个计划?我能做些什么来避免它?
从其他答案我怀疑这与查询和索引一样多。
explain select "labelDate" from pages group by "labelDate";
QUERY PLAN
-----------------------------------------------------------------------
HashAggregate (cost=524616.78..524617.04 rows=26 width=4)
Group Key: "labelDate"
-> Seq Scan on pages (cost=0.00..499082.42 rows=10213742 width=4)
(3 rows)
Run Code Online (Sandbox Code Playgroud)
表结构:
http=# \d pages
Table "public.pages"
Column | Type | Modifiers
-----------------+------------------------+----------------------------------
pageid | integer | not null default nextval('...
createDate | integer | not null
archive | character varying(16) | not null
label | character varying(32) | not null
wptid | character varying(64) | not null
wptrun | integer | not null
url | text |
urlShort | character varying(255) |
startedDateTime | integer |
renderStart | integer |
onContentLoaded | integer |
onLoad | integer |
PageSpeed | integer |
rank | integer |
reqTotal | integer | not null
reqHTML | integer | not null
reqJS | integer | not null
reqCSS | integer | not null
reqImg | integer | not null
reqFlash | integer | not null
reqJSON | integer | not null
reqOther | integer | not null
bytesTotal | integer | not null
bytesHTML | integer | not null
bytesJS | integer | not null
bytesCSS | integer | not null
bytesHTML | integer | not null
bytesJS | integer | not null
bytesCSS | integer | not null
bytesImg | integer | not null
bytesFlash | integer | not null
bytesJSON | integer | not null
bytesOther | integer | not null
numDomains | integer | not null
labelDate | date |
TTFB | integer |
reqGIF | smallint | not null
reqJPG | smallint | not null
reqPNG | smallint | not null
reqFont | smallint | not null
bytesGIF | integer | not null
bytesJPG | integer | not null
bytesPNG | integer | not null
bytesFont | integer | not null
maxageMore | smallint | not null
maxage365 | smallint | not null
maxage30 | smallint | not null
maxage1 | smallint | not null
maxage0 | smallint | not null
maxageNull | smallint | not null
numDomElements | integer | not null
numCompressed | smallint | not null
numHTTPS | smallint | not null
numGlibs | smallint | not null
numErrors | smallint | not null
numRedirects | smallint | not null
maxDomainReqs | smallint | not null
bytesHTMLDoc | integer | not null
maxage365 | smallint | not null
maxage30 | smallint | not null
maxage1 | smallint | not null
maxage0 | smallint | not null
maxageNull | smallint | not null
numDomElements | integer | not null
numCompressed | smallint | not null
numHTTPS | smallint | not null
numGlibs | smallint | not null
numErrors | smallint | not null
numRedirects | smallint | not null
maxDomainReqs | smallint | not null
bytesHTMLDoc | integer | not null
fullyLoaded | integer |
cdn | character varying(64) |
SpeedIndex | integer |
visualComplete | integer |
gzipTotal | integer | not null
gzipSavings | integer | not null
siteid | numeric |
Indexes:
"pages_pkey" PRIMARY KEY, btree (pageid)
"pages_date_url" UNIQUE CONSTRAINT, btree ("urlShort", "labelDate")
"idx_pages_cdn" btree (cdn)
"idx_pages_labeldate" btree ("labelDate") CLUSTER
"idx_pages_urlshort" btree ("urlShort")
Triggers:
pages_label_date BEFORE INSERT OR UPDATE ON pages
FOR EACH ROW EXECUTE PROCEDURE fix_label_date()
Run Code Online (Sandbox Code Playgroud)
这是一个关于 Postgres 优化的已知问题。如果不同的值很少 - 就像你的情况 - 而你是 8.4+ 版本,这里描述了一个使用递归查询的非常快速的解决方法:Loose Indexscan。
您的查询可以重写(LATERAL
需要 9.3+ 版本):
WITH RECURSIVE pa AS
( ( SELECT labelDate FROM pages ORDER BY labelDate LIMIT 1 )
UNION ALL
SELECT n.labelDate
FROM pa AS p
, LATERAL
( SELECT labelDate
FROM pages
WHERE labelDate > p.labelDate
ORDER BY labelDate
LIMIT 1
) AS n
)
SELECT labelDate
FROM pa ;
Run Code Online (Sandbox Code Playgroud)
Erwin Brandstetter 在这个答案中有一个详尽的解释和查询的几个变体(在一个相关但不同的问题上):优化 GROUP BY 查询以检索每个用户的最新记录
最佳查询很大程度上取决于数据分布。
你每个日期有很多行,这已经建立。由于您的案例在结果中仅燃烧到 26 个值,因此一旦使用索引,以下所有解决方案都会非常快。
(对于更多不同的值,案例会变得更有趣。)
有没有必要让pageid
所有(如你评论)。
您所需要的只是一个简单的 btree 索引"labelDate"
。
由于列中有多个 NULL 值,部分索引有更多帮助(并且更小):
CREATE INDEX pages_labeldate_nonull_idx ON big ("labelDate")
WHERE "labelDate" IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
你后来澄清:
0% NULL 但只有在导入时修复之后。
部分索引可能仍然有助于排除具有 NULL 值的行的中间状态。将避免对索引进行不必要的更新(导致膨胀)。
如果您的日期出现在连续范围内且没有太多间隔,我们可以利用数据类型的性质date
来发挥我们的优势。两个给定值之间只有有限的、可数的值。如果差距很小,这将是最快的:
SELECT d."labelDate"
FROM (
SELECT generate_series(min("labelDate")::timestamp
, max("labelDate")::timestamp
, interval '1 day')::date AS "labelDate"
FROM pages
) d
WHERE EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");
Run Code Online (Sandbox Code Playgroud)
为什么要投timestamp
的generate_series()
?看:
可以从索引中廉价地选择最小值和最大值。如果您知道可能的最小和/或最大日期,它会便宜一点。例子:
SELECT d."labelDate"
FROM (SELECT date '2011-01-01' + g AS "labelDate"
FROM generate_series(0, now()::date - date '2011-01-01' - 1) g) d
WHERE EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");
Run Code Online (Sandbox Code Playgroud)
或者,对于不可变区间:
SELECT d."labelDate"
FROM (SELECT date '2011-01-01' + g AS "labelDate"
FROM generate_series(0, 363) g) d
WHERE EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");
Run Code Online (Sandbox Code Playgroud)
这对于任何日期分布都表现得很好(只要我们每个日期有很多行)。基本上@ypercube 已经提供了什么。但是有一些优点,我们需要确保我们最喜欢的索引可以在任何地方使用。
WITH RECURSIVE p AS (
( -- parentheses required for LIMIT
SELECT "labelDate"
FROM pages
WHERE "labelDate" IS NOT NULL
ORDER BY "labelDate"
LIMIT 1
)
UNION ALL
SELECT (SELECT "labelDate"
FROM pages
WHERE "labelDate" > p."labelDate"
ORDER BY "labelDate"
LIMIT 1)
FROM p
WHERE "labelDate" IS NOT NULL
)
SELECT "labelDate"
FROM p
WHERE "labelDate" IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
第一个 CTEp
实际上与
SELECT min("labelDate") FROM pages
Run Code Online (Sandbox Code Playgroud)
但是详细形式确保使用我们的部分索引。另外,根据我的经验(和我的测试),这种形式通常要快一些。
仅对于单个列,rCTE 递归项中的相关子查询应该快一点。这需要排除导致“labelDate”为 NULL 的行。看:
不带引号的、合法的、小写的标识符让您的生活更轻松。
对表定义中的列进行排序以节省一些磁盘空间:
归档时间: |
|
查看次数: |
16701 次 |
最近记录: |