Postgresql ORDER BY - 选择正确的索引

Posted

技术标签:

【中文标题】Postgresql ORDER BY - 选择正确的索引【英文标题】:Postgresql ORDER BY - choosing right index 【发布时间】:2014-11-25 08:01:04 【问题描述】:

有一个表 T(user, timestamp,...) 有 100 毫升以上的记录 (PostgreSQL 9.1)。

形式的查询

SELECT * 
FROM T 
WHERE user='abcd' 
ORDER BY timestamp 
LIMIT 1

当有大约 100000 条用户记录时,正在使用 timestamp 索引而不是用户索引。

使用时间戳索引总是会产生糟糕的结果(20+ 秒),因为它最终会扫描所有记录。通过将查询更改为使用ORDER BY DATE(timestamp) 来绕过timestamp 索引将导致查询求助于用户索引并给出小于100 毫秒的结果。

总内存:64 GB shared_buffers:16 GB work_mem:32 MB

为什么 postgresql 忽略 user 索引而使用 timestamp 索引(时间戳索引需要查看所有记录)? 是否有任何 postgresql 配置参数可以更改以使查询使用用户名索引本身?

【问题讨论】:

wiki.postgresql.org/wiki/SlowQueryQuestions 发布EXPLAIN 结果 最可能的问题 - 表上的统计信息陈旧或配置错误。 这是一个有效的问题,请不要关闭它! @vyegorov 我不能代表另一个关闭投票,但我的投票是迁移到 dba.stackexchange.com,而不是关闭问题 【参考方案1】:

好问题,我前段时间就遇到过这个问题。

为什么会这样?

您应该像这样查看stats 中user='abcd' 值的数量:

SELECT attname, null_frac, ag_width, n_distinct,
       most_common_vals, most_common_freqs, histogram_bounds
  FROM pg_stats
 WHERE table_name='T';

我的猜测是——这个值经常出现,你会在most_common_vals 输出中找到它。 从most_common_freqs 中选取相同的元素,您将获得该值的比率,将其乘以总行数(可以从pg_class 获得)以获得估计的行数拥有'abcd' 值。

Planner 假定所有值都具有线性分布。事实上,事情当然是不同的。 另外,目前没有correlated stats (although some work is being done in this direction)。

所以,让我们取user='abcd' 值,在对应的most_common_freqs 条目中具有0.001 比率(每个问题)。这意味着值将每 1000 行出现一次(假设线性分布)。看来,如果我们以任何方式扫描表,我们将在大约 1000 行中找到我们的user='abcd'。听起来应该很快!规划师“认为”相同,并在 timestamp 列上选择索引。

但事实并非如此。如果我们假设您的表T 包含用户活动日志,并且user='abcd' 过去3 周都在休假,那么这意味着我们必须从timestamp 中读取很多行索引(价值 3 周的数据)在我们实际到达我们想要的行之前。好吧,您作为 DBA 知道这一点,但规划器假定线性分布。

那么,如何解决?

你必须欺骗计划者使用你需要的东西,因为你对你的数据有更多的了解。

    OFFSET 0 trick 与子查询一起使用:

    SELECT *
      FROM
      (
         SELECT * FROM T WHERE user='abcd' OFFSET 0
      )
      ORDER BY timestamp 
      LIMIT 1;
    

    这个技巧可以保护查询免于内联,因此内部部分是自己执行的。

    使用CTE(命名子查询):

    WITH s AS (
            SELECT * FROM T WHERE user='abcd'
    )
    SELECT *
      FROM s
     ORDER BY timestamp 
     LIMIT 1;
    

    根据文档:

    WITH 查询的一个有用特性是,每次执行父查询时,它们只被评估一次,即使它们被父查询或同级 WITH 查询多次引用。 p>

    使用count(*) 进行聚合查询:

    SELECT min(session_id), count(*) -- instead of simply `min(session_id)`
      FROM T 
     WHERE user='abcd' 
     ORDER BY timestamp 
     LIMIT 1;
    

    这个不太适用,但我想提一下。

请考虑升级到 9.3。

附:更多关于行 estiamtes in the docs of course.

【讨论】:

谢谢 vyegorov。这完美地解释了这种行为。我通过检查 pg_stats 确认。 9.3 有什么改进吗? @Anoop,不适用于这种情况,抱歉。但坚持当前的主要版本通常是个好主意,因为它包括其他性能和安全改进。

以上是关于Postgresql ORDER BY - 选择正确的索引的主要内容,如果未能解决你的问题,请参考以下文章

order by 在 POSTGRESQL 中的 partition by 子句中不起作用?

PostgreSQL ORDER BY 语句

PostgreSQL ORDER BY 语句

带有order_by的Django查询,对Postgresql的不同和限制

PostgreSQL order by 排序问题

PostgreSQL:使用主键作为排序键的 ORDER BY 非常慢