描述
Linux 上的 PostgreSQL 9.6,tags_tmp
表大小 ~ 30 GB(1000 万行),tags
是一个text[]
并且只有 6 个值。
tags_tmp(id int, tags text[], maker_date timestamp, value text)
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
我需要使用 filter ontags
和order by
on检索数据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
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 ;
解释结果:
Limit (cost=233469.63..233469.65 rows=5 width=116) (actual time=801.482..801.483 rows=5 loops=1)
Output: id, tags, maker_date, value
-> Sort (cost=233469.63..234714.22 rows=497833 width=116) (actual time=801.481..801.481 rows=5 loops=1)
Output: id, tags, maker_date, value
Sort Key: tags_tmp.maker_date DESC
Sort Method: top-N heapsort Memory: 25kB
-> Bitmap Heap Scan on public.tags_tmp (cost=6486.58..225200.81 rows=497833 width=116) (actual time=212.982..696.650 rows=366392 loops=1)
Output: id, tags, maker_date, value
Recheck Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
Heap Blocks: exact=120034
-> Bitmap Index Scan on idx1 (cost=0.00..6362.12 rows=497882 width=0) (actual time=171.742..171.742 rows=722612 loops=1)
Index Cond: (tags_tmp.tags && '{funny,inspiration}'::text[])
Planning time: 0.185 ms
Execution time: 802.128 ms
更多信息
我测试了只对一个标签使用部分索引,当然,它更快。但是我有很多标签,例如: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']
and之间进行了测试'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% 行的 tablewhere '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 index
or converttext[] to int[]
以及 converttext[] to int
等,它将在多标签上使用位图索引。最后,经过测试,我决定用老方案,OR
改成很多UNION
子句,每个子句UNION
都会限制数据的数量。当然,我将为partial index
每个标签值创建,并且可以与上面的 (1) 结合使用。就 而言OFFSET
,它将使用一个或多个条件 inWHERE
子句。
例子
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 ;
一般注意事项
索引优化总是取决于完整的画面。表大小、行大小、基数、值频率、典型查询的选择性、Postgres 版本、典型访问模式等。
您的情况特别困难,原因有两个:
WHERE
和中使用的不同列ORDER BY
。使用 GIN 或 GiST 索引对数组进行过滤是最有效的,但是这两种索引类型都不会产生排序输出。手册:
您可以
(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
列很大并且 TOASTed,这将使我的改进更加有效,因为主关系被缩减到它的一半或更少:由于对齐填充,列的顺序是相关的。细节:
更重要的是,这个:
tags int
. 为什么?数组有相当大的 24 字节开销(类似于一行),加上实际项目。
因此
text[]
,像您演示的 1-6 个项目 ('funny', 'inspiration', ...)在 avg 上占用约 56 个字节。并且 6 个不同的值只能由 6 位信息表示(假设数组的排序顺序无关紧要)。我们可以压缩更多,但我选择了方便的integer
类型(占用4 个字节),它为多达 31 个不同的标签提供空间。这为以后添加而不更改表模式留下了空间。详细理由:您的标签映射到位图中的位,
'a'
是最低有效位(右侧):所以标签数组
'{a,d,f}'
映射到41
. 你可以使用任意字符串代替'a'-'f',没关系。为了封装逻辑,我建议使用两个辅助功能,易于扩展:
标签->整数:
整数 -> 标签:
这里的基础:
为方便起见,您可以添加一个视图以将标签显示为文本数组,就像您拥有它一样:
现在您的基本查询可以是:
使用二元 AND 运算符
&
。有更多的运算符来操作列。get_bit()
并且set_bit()
从二进制字符串操作符也很方便。上面的查询应该已经更快了,仅对于更小尺寸和更便宜的运营商来说,但还没有什么革命性的。为了让它更快,我们需要索引,上面还不能使用索引。
每个标签都有一个部分索引:
此查询与上述等效,但可以利用索引:
Postgres 可以非常有效地在一个步骤中组合多个位图索引扫描
BitmapOr
- 如此SQL Fiddle所示。您可以添加另一个索引条件以将索引限制为
maker_date
> 某个恒定时间戳(并在查询中重复逐字条件)以减少它们的大小(大量)。相关示例:更复杂:
其他相关答案:
在 PostgreSQL 中使用 GIN 索引时如何加快 ORDER BY 排序?
空间索引能否帮助“范围-按-限制”查询
或者只有 6
boolean
列...简单的 6 个布尔列可能是更好的选择。两种解决方案都有一些优点和缺点......
您可以定义 flags
NOT NULL
,具体取决于您的完整用例。考虑:
部分索引很简单:
等等。
您的特殊情况的替代方案
再想一想,由于您所有的几个标签都很常见,并且将多个标签与 OR 组合起来的选择性更小,因此仅在
maker_date DESC
. Postgres 可以遍历索引并过滤标签上的合格行。这将与单独的布尔列而不是数组或编码整数结合使用,因为 Postgres 对单独的列有更有用的列统计信息。接着:
寻呼
您需要一个明确的结果集排序顺序,以使分页工作。我没有在这个答案中打扰,它已经太长了。通常,您会向
ORDER BY
. 如何使分页有效地工作:您的测试用例的各种问题:
id
现在是int8
。您在最初的问题中声明它int
没有很大的不同,但是为什么要开始混淆呢?它对行大小和对齐填充很重要。请记住在问题中声明您的实际、准确和完整的表定义。测试数据中的数据分布是不现实的。您只有 6 个不同的标签组合,并且 *所有行都有 tag
'1'
。我假设,您的实时表格中有所有 63 种可能的组合,标签分布与您在问题中添加的一样。您的测试表包括新旧标签列,这抵消了我所追求的对存储大小的影响。现在行大小更大。您的行大小为 124 - 164 字节,而我的测试中只有68字节(包括填充和项目标识符)。超过两倍的尺寸会有所不同。
你写size = 4163 MB。什么尺寸?
你有
order by random()
测试数据。你的高效餐桌真的那么随意吗?通常,您会根据时间戳对数据进行粗略排序。你的实际情况是什么?要查看将选择哪个计划,
EXPLAIN
请在实际运行查询之前仅查看查询计划进行测试。使用大桌子节省大量时间。但始终提供EXPLAIN (ANALYZE, BUFFERS)
此处的输出。在您的回答中(与问题相反),cost=
缺少估计值。这使得很难猜出问题所在。但是这些问题都不能解释为什么即使使用
enable_seqscan = off
;也会看到顺序扫描。使用 Postgres 9.5 对我的笔记本电脑进行快速测试。第 9.6 页也应如此。就像我已经在SQL Fiddle中演示的一样。
您确定您正确创建了所有索引吗?