Lua*_*ynh 8 postgresql performance order-by index-tuning postgresql-performance
Linux 上的 PostgreSQL 9.6,tags_tmp表大小~ 30 GB(1000 万行),tags是一个text[]并且只有 6 个值。
tags_tmp(id int, tags text[], maker_date timestamp, value text)
Run Code Online (Sandbox Code Playgroud)
tags_tmp(id int, tags text[], maker_date timestamp, value text)
Run Code Online (Sandbox Code Playgroud)
我需要使用 filter ontags和order byon检索数据maker_date desc。我可以在两tags & maker_date desc列上创建索引吗?
如果没有,你能提出其他想法吗?
select id, tags, maker_date, value
from tags_tmp
where tags && array['a','b']
order by maker_date desc
limit 5 offset 0
Run Code Online (Sandbox Code Playgroud)
SQL 代码:
create index idx1 on tags_tmp using gin (tags);
create index idx2 on tags_tmp using btree(maker_date desc);
explain (analyse on, costs on, verbose)
select id, tags, maker_date, value
from tags_tmp
where tags && array['funny','inspiration']
order by maker_date desc
limit 5 offset 0 ;
Run Code Online (Sandbox Code Playgroud)
解释结果:
id tags maker_date value
1 {a,b,c} 2016-11-09 This is test
2 {a} 2016-11-08 This is test
3 {b,c} 2016-11-07 This is test
4 {c} 2016-11-06 This is test
5 {d} 2016-11-05 This is test
Run Code Online (Sandbox Code Playgroud)
我测试了只对一个标签使用部分索引,当然,它更快。但是我有很多标签,例如:create index idx_tmp on tags_tmp using btree (maker_date desc) where (tags && array['tag1') or tags && array['tag2'] or ... or tags && array['tag6']. 我在tags && array['tag1']和之间进行了测试'tag1' = any(tags),性能相同。
text[]只有 6 个值 = a, b, c, d, e, f。例如:tags={a,b,c}, tags={a}, tags={a,c}, tags={a,b,c,d,e,f}, tags={b,f}等等。但它不能具有价值g->z, A-Z等。
create table tags_tmp(id int primary key not null, tags text[] not null, maker_date timestamp not null, value text)
就distinct数组值而言,tags包含a表的 20% 行where 'a' = any(tags), b=20% where 'b' = any(tags), c=20% where 'c' = any(tags), d=20% where 'd' = any(tags), e=10% where 'e' = any(tags),f=10% where 'f' = any(tags)。
此外,(tags, maker_date)也不是唯一的。
该表不是只读的。
它是sort on timestamp,但我的示例显示了日期,对此感到抱歉。
现状:tags = 'a' or tags = 'b' or tags = 'c'等等
(1) with GIN indexor converttext[] to int[]以及 convert text[] to intand more,它将在多个标签上使用位图索引。最后,经过测试,我决定使用旧解决方案,将其更改OR为许多UNION子句,每个子句UNION都会限制数据数量。当然,我会partial index为每个标签值创建,也可以与上面的 (1) 结合使用。就 而言OFFSET,它将在WHERE子句中使用一个或多个条件。
EXPLAIN (ANALYSE ON, costs ON, VERBOSE)
SELECT rs.*
FROM (
(SELECT tags,
id,
maker_date
FROM tags_tmp
WHERE 'a' = any(tags)
AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
ORDER BY maker_date DESC LIMIT 5)
UNION
(SELECT tags,
id,
maker_date
FROM tags_tmp
WHERE 'b' = any(tags)
AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
ORDER BY maker_date DESC LIMIT 5)
UNION
(SELECT tags,
id,
maker_date
FROM tags_tmp
WHERE 'c' = any(tags)
AND maker_date <= '2016-03-28 05:43:57.779528'::TIMESTAMP
ORDER BY maker_date DESC LIMIT 5)) rs
ORDER BY rs.maker_date DESC LIMIT 5 ;
Run Code Online (Sandbox Code Playgroud)
索引优化始终取决于完整图片。表大小、行大小、基数、取值频率、典型查询的选择性、Postgres 版本、典型访问模式等。
您的情况特别困难,原因有两个:
WHERE和 中使用的不同列ORDER BY。使用 GIN 或 GiST 索引对数组进行过滤是最有效的,但两种索引类型都不会产生排序输出。手册:
在 PostgreSQL 当前支持的索引类型中,只有 B-tree 可以产生排序输出——其他索引类型以未指定的、依赖于实现的顺序返回匹配的行。
您可以在(tags, maker_date)甚至更多列上创建多列GIN 索引(索引列的顺序与 GIN 索引无关)。但是您需要btree_gin安装附加模块。指示:
它不会帮助ORDER BY您解决问题的组成部分。
再澄清一点:OFFSET m LIMIT n通常几乎与LIMIT m+n.
您澄清说:只有 6 个不同的标签可能。这很关键。
你的表很大,你的表定义留下了改进的空间。尺寸对于大桌子很重要。您的数字(30 GB,1000 万行)也表明了一个很大的平均值。行大小约为 3 KB。要么你有比你显示的更多的列或表格膨胀并且需要VACUUM FULL运行(或类似的),或者你的value列很大并且已经过烤,这将使我的改进更加有效,因为主关系被减少到它的一半或更少:
CREATE TABLE tags_tmp (
id int PRIMARY KEY -- assuming PK
, tags int NOT NULL -- also assuming NOT NULL
, value text
, maker_date timestamp NOT NULL -- NOT NULL!
);
Run Code Online (Sandbox Code Playgroud)
由于对齐填充,列的顺序是相关的。细节:
更重要的是,这个:tags int。为什么?
数组有 24 字节(类似于一行)的相当大的开销,加上实际项目。
因此text[],像您演示的 1-6 个项目 ('funny', 'inspiration', ...)在 avg 上占用~ 56 个字节。并且 6 个不同的值只能由 6 位信息表示(假设数组的排序顺序无关紧要)。我们可以压缩更多,但我选择了方便的integer类型(占用4 个字节),它为最多 31 个不同的标签提供空间。这为以后的添加留出了空间,而无需更改表架构。详细理由:
您的标签映射到位图中的位,'a'即最低有效位(右侧):
tag: a | b | c | d | e | f
position: 0 | 1 | 2 | 3 | 4 | 5
int value: 1 | 2 | 4 | 8 | 16 | 32
Run Code Online (Sandbox Code Playgroud)
所以标签数组'{a,d,f}'映射到41. 您可以使用任意字符串代替 'a'-'f',没关系。
为了封装逻辑,我建议使用两个辅助功能,易于扩展:
标签 -> 整数:
CREATE OR REPLACE FUNCTION f_tags2int(text[])
RETURNS int AS
$func$
SELECT bit_or(CASE x
WHEN 'a' THEN 1
WHEN 'b' THEN 2
WHEN 'c' THEN 4
WHEN 'd' THEN 8
WHEN 'e' THEN 16
WHEN 'f' THEN 32
-- more?
END)
FROM unnest ($1) x
$func$ LANGUAGE SQL IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)
整数 -> 标签:
CREATE OR REPLACE FUNCTION f_int2tags(int)
RETURNS text[] AS
$func$
SELECT array_remove(ARRAY [CASE WHEN $1 & 1 > 0 THEN 'a' END
, CASE WHEN $1 & 2 > 0 THEN 'b' END
, CASE WHEN $1 & 4 > 0 THEN 'c' END
, CASE WHEN $1 & 8 > 0 THEN 'd' END
, CASE WHEN $1 & 16 > 0 THEN 'e' END
, CASE WHEN $1 & 32 > 0 THEN 'f' END], NULL)
-- more?
$func$ LANGUAGE SQL IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)
这里的基础知识:
为方便起见,您可以添加一个视图以将标签显示为文本数组,就像您拥有的那样:
CREATE VIEW tags_tmp_pretty AS
SELECT id, tags
, f_int2tags(tags) AS tags_pretty
, maker_date, value
FROM tags_tmp;
Run Code Online (Sandbox Code Playgroud)
现在您的基本查询可以是:
SELECT id, tags, maker_date, value
FROM tags_tmp
WHERE tags & f_tags2int('{a,b}') > 0 -- any of the tags matched
ORDER by maker_date DESC
LIMIT 5;
Run Code Online (Sandbox Code Playgroud)
使用二元 AND 运算符&。有更多的运算符来操作列。get_bit()而且set_bit()从二进制字符串操作也很方便。
上面的查询应该已经更快了,仅对于更小规模和更便宜的运营商来说,但还没有什么革命性的。为了使它更快,我们需要索引,而上述内容还不能使用索引。
每个标签有一个部分索引:
CREATE INDEX foo_tag_a ON tags_tmp(maker_date DESC) WHERE tags & 1 > 0;
CREATE INDEX foo_tag_b ON tags_tmp(maker_date DESC) WHERE tags & 2 > 0;
...
CREATE INDEX foo_tag_f ON tags_tmp(maker_date DESC) WHERE tags & 32 > 0;
Run Code Online (Sandbox Code Playgroud)
此查询等效于上述查询,但可以使用索引:
SELECT *
FROM tags_tmp_pretty
WHERE (tags & f_tags2int('{a}') > 0 -- same as tags & 1
OR tags & f_tags2int('{e}') > 0) -- same as tags & 32
ORDER BY maker_date DESC
LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
Postgres 可以在一个BitmapOr步骤中非常有效地组合多个位图索引扫描- 如这个SQL Fiddle 所示。
您可以添加另一个索引条件以将索引限制为maker_date> 某个恒定时间戳(并在查询中重复逐字条件)以减少它们的大小(大量)。相关示例:
更高级:
其他相关回答:
boolean列...简单的 6 个布尔列可能是更好的选择。两种解决方案都有一些优点和缺点......
CREATE TABLE tags_tmp (
id int PRIMARY KEY -- assuming PK
, tag_a bool
, tag_b bool
...
, tag_f bool
, value text
, maker_date timestamp NOT NULL -- NOT NULL!
);
Run Code Online (Sandbox Code Playgroud)
您可以定义 flags NOT NULL,具体取决于您的完整用例。
考虑:
部分索引简单地:
CREATE INDEX foo_tag_a ON tags_tmp(maker_date DESC) WHERE tag_a;
CREATE INDEX foo_tag_b ON tags_tmp(maker_date DESC) WHERE tag_b;
Run Code Online (Sandbox Code Playgroud)
等等。
再想一想,由于您所有的几个标签都如此常见,并且将多个标签与 OR 组合起来的选择性甚至更低,因此仅在maker_date DESC. Postgres 可以遍历索引并过滤标签上的合格行。这将与单独的布尔列结合使用,而不是与数组或编码整数结合使用,因为 Postgres 为单独的列提供了更有用的列统计信息。
CREATE INDEX tags_tmp_date ON tags_tmp(maker_date DESC);
Run Code Online (Sandbox Code Playgroud)
进而:
SELECT *
FROM tags_tmp_pretty
WHERE tag_a
OR tag_b
ORDER BY maker_date DESC
LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
您需要一个明确的结果集排序顺序,以使分页工作。我没有打扰这个答案,已经太久了。通常,您会向ORDER BY. 如何使分页有效地工作:
| 归档时间: |
|
| 查看次数: |
6302 次 |
| 最近记录: |