如何在PostgreSQL中存储和查询同一文档的版本?

时间:2015-07-20 07:29:24

标签: sql postgresql database-design indexing postgresql-performance

我在PostgreSQL 9.4中存储文档的版本。每次用户创建新版本时,它都会插入一行,以便我可以跟踪所有更改。每行与前一行共享一个reference_id列。有些行获得批准,有些仍然作为草稿。每行还有viewable_at时间。

id | reference_id | approved | viewable_at         | created_on | content
1  | 1            | true     | 2015-07-15 00:00:00 | 2015-07-13 | Hello
2  | 1            | true     | 2015-07-15 11:00:00 | 2015-07-14 | Guten Tag
3  | 1            | false    | 2015-07-15 17:00:00 | 2015-07-15 | Grüß Gott

最常见的查询是获取由reference_id分组的行,approvedtrueviewable_at小于当前时间。 (在这种情况下,行ID 2将包含在结果中)

到目前为止,这是我提出的最佳查询,不需要我添加其他列:

SELECT DISTINCT ON (reference_id) reference_id, id, approved, viewable_at, content 
FROM documents 
WHERE approved = true AND viewable_at <= '2015-07-15 13:00:00' 
ORDER BY reference_id, created_at DESC`

我有一个关于reference_id的索引和一个关于approved和viewable_at的多列索引。

只有15,000行,它仍然在我的本地机器上平均几百毫秒(140 - 200)。我怀疑不同的呼叫或排序可能会减慢它的速度。

存储此信息的最有效方法是什么,以便SELECT查询最高效?

EXPLAIN(BUFFERS,ANALYZE)的结果:

                                                              QUERY PLAN                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
Unique  (cost=6668.86..6730.36 rows=144 width=541) (actual time=89.862..99.613 rows=145 loops=1)
  Buffers: shared hit=2651, temp read=938 written=938
  ->  Sort  (cost=6668.86..6699.61 rows=12300 width=541) (actual time=89.861..97.796 rows=13184 loops=1)
        Sort Key: reference_id, created_at
        Sort Method: external merge  Disk: 7488kB
        Buffers: shared hit=2651, temp read=938 written=938
        ->  Seq Scan on documents  (cost=0.00..2847.80 rows=12300 width=541) (actual time=0.049..40.579 rows=13184 loops=1)
              Filter: (approved AND (viewable_at < '2015-07-20 06:46:55.222798'::timestamp without time zone))
              Rows Removed by Filter: 2560
              Buffers: shared hit=2651
Planning time: 0.218 ms
Execution time: 178.583 ms
(12 rows)

文件使用说明:

文档是手动编辑的,我们还没有每X秒或任何东西自动保存文档,因此音量会相当低。此时,每个reference_id有平均7个版本平均只有2个已批准版本。 (〜30%)

在最小和最大方面,绝大多数文档将有1或2个版本,并且似乎不太可能有任何文档超过30或40。有一个垃圾收集过程来清除未批准的版本一周,所以版本总数应该保持很低。

对于检索和实际使用,我可以在查询中使用限制/偏移,但在我的测试中没有产生巨大的差异。理想情况下,这是一个填充视图或其他内容的基本查询,以便我可以在这些结果之上进行其他查询,但我不完全确定这会对结果性能产生什么影响并且对建议持开放态度。我的印象是,如果我能够尽可能简单/快速地获取此存储/查询,那么从这一点开始的所有其他查询都可以得到改进,但可能是我错了,并且每个查询都需要更多的独立思考。

2 个答案:

答案 0 :(得分:2)

查看您的解释输出,看起来您正在获取documents表中的大部分内容,因此它可以合理地执行顺序扫描。你的行数估计是合理的,这里似乎没有任何统计数据。

它正在磁盘上进行外部合并排序,因此您可能会通过增加会话中的work_mem来看到性能的显着提升,例如

SET work_mem = '12MB'

(reference_id ASC, created_at DESC) WHERE (approved)上的索引可能有用,因为它允许以所需的顺序获取结果。

您还可以尝试将viewable_at添加到索引中。我认为它可能必须是最后一栏,但我不确定。或者甚至通过附加viewable_at, id, content并从结果集中省略不必要的approved列,使其成为覆盖索引。这可能允许仅索引扫描,但涉及DISTINCT ON但我不确定。

答案 1 :(得分:1)

@Craig already covers大多数选项可以更快地进行此查询。会话的更多work_mem可能是最有效的项目。

自:

  

有一个垃圾收集过程来清理未批准的版本   超过一周

排除未经批准的版本的部分索引不会太多。 如果 您使用了索引,但仍然会排除那些不相关的行。
由于您似乎每reference_id 极少 版本:

  

绝大多数文件都有1或2个版本

您已经拥有DISTINCT ON的最佳查询技术:

随着版本数量的增加,其他技术会越来越优越:

您的查询中唯一的略微非传统的元素是谓词位于viewable_at,但您接着使用最新的created_at行,这就是您的索引的原因实际上是:

(reference_id, viewable_at ASC, created_at DESC) WHERE (approved)

假设所有列都为defined NOT NULLviewable_atcreated_at之间的交替排序顺序非常重要。然后,虽然每reference_id行的行数很少,但我并不希望任何索引都有很多用处。无论如何都必须读取整个表格,顺序扫描的速度一样快。增加的维护成本甚至可能超过其利益。

然而,因为:

  

理想情况下,这是一个填充视图或其他内容的基本查询   我可以在这些结果之上进行其他查询

我还有一个建议:从您的查询中创建 MATERIALIZED VIEW ,为您提供给定时间点项目的快照。如果磁盘空间不是问题而快照可能会重复使用,您甚至可能会收集其中的几个:

CREATE MATERIALIZED VIEW doc_20150715_1300 AS
SELECT DISTINCT ON (reference_id)
       reference_id, id, approved, viewable_at, content 
FROM   documents 
WHERE  approved  -- simpler expression for boolean column
AND    viewable_at <= '2015-07-15 13:00:00' 
ORDER  BY reference_id, created_at DESC;

或者,如果所有其他查询在同一会话中发生,请使用临时表(在会话结束时自动死亡):

CREATE TEMP TABLE doc_20150715_1300 AS ...;

ANALYZE doc_20150715_1300;

确保在临时表上运行ANALYZE(如果在创建查询后立即运行,也在MV上运行):

无论哪种方式,可能付费在支持后续查询的快照上创建一个或多个索引。取决于数据和查询。

注意,当前版本1.20.0的pgAdmin不显示MV的索引。那个already been fixed正在等待下一个版本发布。