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 BYLIMIT 子句时,需要 19.5 秒ANALYZE 已在执行查询之前对所有表运行。

对于这个特定的查询,这些是满足条件的记录数:

+------------------+-------------+
| Table            |     Records |
+------------------+-------------+
| JOB              |          1  |
| DOCUMENT         |       1200  |
| TRANSLATION_UNIT |    210,000  |
| TRANSLATION      |    210,000  |
+------------------+-------------+

查询计划:

不带ORDER BYLIMIT的修改查询计划为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 BYLIMIT 的情况下运行查询,它需要 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_unitid 列的复合索引?我没有,但可以尝试一下。 我看到的好处是翻译表本身根本不需要访问来检索所需的数据。 嗯,确实如此,因为我在结果集中需要TRANSLATION.id。 PostgreSQL 性能论坛上的一个人刚刚建议对数据库进行非规范化并将fk_id_job 直接添加到TRANSLATION 这应该可以从索引中访问,而无需访问表。 我明白了。实际查询从TRANSLATION 获取所有列。

以上是关于PostgreSQL:使用主键作为排序键的 ORDER BY 非常慢的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL自增主键的用法以及在mybatis中的使用

如何在不明确指定主键的情况下使用 Dapper Extensions 将对象插入 PostGreSql?

如何使用 SQLAlchemy 创建一个不是主键的标识列?

Mysql什么时候建索引什么时候不适合建索引

用SQL语句获得PostgreSQL表的主键

使用 uint 作为主键的代码优先方法