PostgreSQL:使用主键作为排序键的 ORDER BY 非常慢
Posted
技术标签:
【中文标题】PostgreSQL:使用主键作为排序键的 ORDER BY 非常慢【英文标题】:PostgreSQL: Terribly slow ORDER BY with primary key as the ordering key 【发布时间】:2013-11-15 04:52:28 【问题描述】:我有一个这样的模型
具有以下表格大小:
+------------------+-------------+
| Table | Records |
+------------------+-------------+
| JOB | 8k |
| DOCUMENT | 150k |
| TRANSLATION_UNIT | 14,5m |
| TRANSLATION | 18,3m |
+------------------+-------------+
现在是下面的查询
select translation.id
from "TRANSLATION" translation
inner join "TRANSLATION_UNIT" unit
on translation.fk_id_translation_unit = unit.id
inner join "DOCUMENT" document
on unit.fk_id_document = document.id
where document.fk_id_job = 11698
order by translation.id asc
limit 50 offset 0
大约需要 90 秒 才能完成。当我删除 ORDER BY 和 LIMIT 子句时,需要 19.5 秒。 ANALYZE 已在执行查询之前对所有表运行。
对于这个特定的查询,这些是满足条件的记录数:
+------------------+-------------+
| Table | Records |
+------------------+-------------+
| JOB | 1 |
| DOCUMENT | 1200 |
| TRANSLATION_UNIT | 210,000 |
| TRANSLATION | 210,000 |
+------------------+-------------+
查询计划:
不带ORDER BY和LIMIT的修改查询计划为here。
数据库参数:
PostgreSQL 9.2
shared_buffers = 2048MB
effective_cache_size = 4096MB
work_mem = 32MB
Total memory: 32GB
CPU: Intel Xeon X3470 @ 2.93 GHz, 8MB cache
谁能看出这个查询有什么问题?
UPDATE:Query plan 用于不带 ORDER BY 的同一查询(但仍带有 LIMIT 子句)。
【问题讨论】:
优化器如何为 Postgre 工作?例如,您可以从您的选择中进行选择,然后在没有优化器的情况下订购它吗? 只是一个幸运的猜测。您可以尝试在连接中移动 where 子句吗?在这种情况下,只需将单词where
替换为 and
。
@foibs:这不会有任何区别。 Postgres 优化器足够聪明,可以检测到两个版本是否相同。
@twoflower:您能否也发布执行计划没有 order by
(那个很快)?理想情况下将其上传到explain.depesz.com,这样它们的可读性会更高。
@foibs 结果一模一样。
【参考方案1】:
如果有人有同样的问题。它发生在我身上,我通过将索引更改为有序索引来解决它。索引按列 ID(PK 列)和顺序方向扩展。
像这样:
create index index_name on SCHEMA.TABLE (id asc, (sent_time IS NULL), some_id_ref, type);
【讨论】:
【参考方案2】:这对评论来说有点太长了。当您删除 order by
子句时,您正在比较苹果和橙子。没有order by
,查询的处理部分只需要拿出50行。
使用order by
,需要先生成所有行,然后才能对其进行排序并选择前几行。如果删除order by
和 limit
子句,查询需要多长时间?
translation.id
是主键这一事实并没有什么不同,因为处理需要经过多个连接(过滤结果)。
编辑:
我想知道这将如何与 CTE 一起工作以首先创建表,然后另一个来排序和获取结果:
with CTE as (
select translation.id
from "TRANSLATION" translation
inner join "TRANSLATION_UNIT" unit
on translation.fk_id_translation_unit = unit.id
inner join "DOCUMENT" document
on unit.fk_id_document = document.id
where document.fk_id_job = 11698
)
select *
from CTE
order by translation.id asc
limit 50 offset 0;
【讨论】:
你说得对,戈登,这两个查询是无与伦比的。我只是在没有ORDER BY
和LIMIT
的情况下运行查询,它需要 19.5 秒。查询计划为here
@twoflower 。 . .你有大量的数据。我对 Postgres 的优化参数不太熟悉,但如果您可以增加缓冲区大小以使用更多内存,您可能会看到性能提升。
是的,卷相当大,这就是为什么我并不感到惊讶,它需要 19.5 秒来获取 所有 记录。然而,让我感到奇怪的是,仅仅订购这个数据集(cca 212,000 条记录)就又增加了 70 秒。
我现在尝试了您建议的 CTE 方法,它运行了 20-30 秒,这比原始查询快得多。非常感谢。但是,在一个小 5 倍(即 40,000 条记录)的数据集上需要 120 毫秒(即快 240 倍),这是否正常?
@twoflower 。 . .数据库性能取决于很多方面。特别是,一个关键因素是可以在内存中进行的处理的比例。随着磁盘的使用,性能会急剧下降。您可能需要为各种缓冲区分配更多内存以提高性能。【参考方案3】:
您是否在翻译(fk_id_translation_unit,id)上设置了复合索引?在我看来,这将有助于避免通过表访问 translation.id。
【讨论】:
您的意思是结合了fk_id_translation_unit
和id
列的复合索引?我没有,但可以尝试一下。
我看到的好处是翻译表本身根本不需要访问来检索所需的数据。
嗯,确实如此,因为我在结果集中需要TRANSLATION.id
。 PostgreSQL 性能论坛上的一个人刚刚建议对数据库进行非规范化并将fk_id_job
直接添加到TRANSLATION
。
这应该可以从索引中访问,而无需访问表。
我明白了。实际查询从TRANSLATION
获取所有列。以上是关于PostgreSQL:使用主键作为排序键的 ORDER BY 非常慢的主要内容,如果未能解决你的问题,请参考以下文章
PostgreSQL自增主键的用法以及在mybatis中的使用