针对多个表优化缓慢的 postgresql 查询

Posted

技术标签:

【中文标题】针对多个表优化缓慢的 postgresql 查询【英文标题】:optimizing a slow postgresql query against multiple tables 【发布时间】:2013-09-21 05:00:36 【问题描述】:

我们的一个 PostgreSQL 查询开始变慢(约 15 秒),因此我们考虑迁移到图形数据库。早期测试显示速度明显更快,太棒了。

问题出在这里——我们仍然需要在 Postgres 中存储数据的备份以满足非分析需求。 Graph 数据库仅用于分析,我们希望它保留为辅助数据存储。因为我们的业务逻辑在这次迁移过程中发生了相当大的变化,两个现有的表变成了 4 个 - 而 Postgres 中当前的“备份”选择需要 1 到 6 分钟才能运行。

我尝试了几种方法来优化它,最好的方法似乎是将它变成两个查询。如果有人可以在这里提出明显的错误,我很想听听建议。我尝试在查询计划器中切换左/右/内连接,但差别不大。加入顺序确实会影响差异;我想我只是没有正确理解这一点。

我会详细介绍。

目标:检索发送给给定人员的最后 10 个附件

数据库结构:

CREATE TABLE message ( 
    id SERIAL PRIMARY KEY NOT NULL , 
    body_raw TEXT 
    );
CREATE TABLE attachments ( 
    id SERIAL PRIMARY KEY NOT NULL , 
    body_raw TEXT 
    );
CREATE TABLE message_2_attachments ( 
    message_id INT NOT NULL REFERENCES message(id) , 
    attachment_id INT NOT NULL REFERENCES attachments(id) 
    );

CREATE TABLE mailings ( 
    id SERIAL PRIMARY KEY NOT NULL , 
    event_timestamp TIMESTAMP not null , 
    recipient_id INT NOT NULL  , 
    message_id INT NOT NULL REFERENCES message(id) 
    );

旁注:从邮件中抽象出邮件的原因是邮件通常有多个收件人/和/单个邮件可以发送给多个收件人

这个查询在一个相对较小的数据集上大约需要 5 分钟(查询规划器时间是每个项目上方的评论):

-- 159374.75
EXPLAIN ANALYZE SELECT attachments.*
FROM attachments
JOIN message_2_attachments ON attachments.id = message_2_attachments.attachment_id
JOIN message ON message_2_attachments.message_id = message.id
JOIN mailings ON mailings.message_id = message.id
WHERE mailings.recipient_id = 1
ORDER BY mailings.event_timestamp desc limit 10 ;

将其拆分为 2 个查询只需要 1/8 的时间:

-- 19123.22
EXPLAIN ANALYZE SELECT message_2_attachments.attachment_id
FROM mailings
JOIN message ON mailings.message_id = message.id
JOIN message_2_attachments ON message.id = message_2_attachments.message_id
JOIN attachments ON message_2_attachments.attachment_id = attachments.id
WHERE mailings.recipient_id = 1
ORDER BY mailings.event_timestamp desc limit 10 ;

-- 1.089
EXPLAIN ANALYZE SELECT * FROM attachments WHERE id IN ( results of above query )

我已经尝试过多次重写查询——不同的连接顺序、不同类型的连接等等。我似乎无法在单个查询中实现几乎所有地方的效率,因为它可以在两个。

更新 Github 有更好的格式,所以解释的完整输出在这里 - https://gist.github.com/jvanasco/bc1dd38ca06e52c9a090

【问题讨论】:

你能把 EXPLAIN 的输出也贴出来吗? 谢谢。我将它添加到 github gist 中。 【参考方案1】:

在此处插入您解释的输出:http://explain.depesz.com/s/hqPT

如您所见,:

Hash Join  (cost=96588.85..158413.71 rows=44473 width=3201) (actual time=22590.630..30761.213 rows=44292 loops=1)
               Hash Cond: (message_2_attachment.attachment_id = attachment.id)

花费大量时间。我会尝试将索引添加到外键以及:

CREATE INDEX idx_message_2_attachments_attachment_id ON "message_2_attachments" USING btree (attachment_id);
CREATE INDEX idx_message_2_attachments_message_id ON "message_2_attachments" USING btree (message_id);`
CREATE INDEX idx_mailings_message_id ON "mailings" USING btree (message_id);

【讨论】:

谢谢。我认为索引不会有太大影响,因为表结构本身是如此标准化。添加这些索引并没有太大的区别,但这产生了巨大的差异 "CREATE INDEX "idx_mailings_message_speedy" ON "mailings" USING btree (event_timestamp);" 现在的解释是什么样子的?我喜欢看到“之后”。 我稍后会拉它,但现在 2:30 的查询是 100 毫秒。 event_timestamp 索引将其降至 200 毫秒;您的 3 个索引将其降至 100 毫秒。如果没有 event_timestamp 索引,我认为大约是 20 秒。【参考方案2】:

联结表缺少主键。此外,建议在此 PK 上添加 reversed 索引:

CREATE TABLE message_2_attachments (
    message_id INT NOT NULL REFERENCES message(id) ,
    attachment_id INT NOT NULL REFERENCES attachments(id)
        , PRIMARY KEY (message_id,attachment_id) -- <<== here
    );

CREATE UNIQUE INDEX ON message_2_attachments(attachment_id,message_id); -- <<== here

对于邮件表,情况还不是很清楚。 看起来 event_timestamp, recipient_id, message_id 的某种组合可以用作候选键。 id 字段仅用作代理项。

【讨论】:

以上是关于针对多个表优化缓慢的 postgresql 查询的主要内容,如果未能解决你的问题,请参考以下文章

MySQL - 添加多个派生表时查询慢 - 优化

优化来自多个表的连接查询

PostgreSQL 查询性能和可能的优化

MySQL索引使用方法和性能优化

MySQL索引使用方法和性能优化

MySQL索引使用方法和性能优化