【问题标题】:What is the best way to optimize sql query that hits/reads too many shared blocks?优化命中/读取太多共享块的 sql 查询的最佳方法是什么?
【发布时间】:2021-01-16 06:54:51
【问题描述】:

我的 postgres 数据库(版本 11)中有下表:

CREATE TABLE instance
(
  id           bigserial,
  period       DATERANGE   NOT NULL,
  status       TEXT        NOT NULL,  -- (wait, active, inactive, outdated)
  position     BIGINT      NOT NULL,

  CONSTRAINT instance_pk PRIMARY KEY (id),
  CONSTRAINT instance_period_check CHECK (NOT isempty(period))
);

我需要从 java 代码中按批次大小 1000 更改按位置排序的实例的状态:

        List<Instance> instances;
        Long position = null;
        do {
            instances = dao.getInstancesBeforePeriod(fromStatus, position);
            if (instances.isEmpty()) {
                break;
            }
            processBatch(toStatus, instances);
            position = instances.get(instances.size() - 1).getPosition();
        } while (true);

dao.getInstancesBeforePeriod(fromStatus, position) if position == null 调用查询:

SELECT id, status, period, position 
     FROM instance
             WHERE status = :status
                AND upper(period) < now()
            ORDER BY position
            LIMIT 1000;

如果 position != null 调用查询:

SELECT id, status, period, position 
     FROM instance
             WHERE status = :status
                AND upper(period) < now()
                AND position > :position
            ORDER BY position
            LIMIT 1000;

但是第一个查询命中/读取了太多共享块,因此查询失败并出现超时异常。 我该如何解决这个问题?

如果我要在实例表上添加索引会怎样:

create index concurrently instance_index_status_upper_period_position
on instance(status, upper(period), position)

但我希望索引保持实例按位置排序。是否可以?我是否应该通过在 where 子句中添加 position &gt; 0 来更改第一个查询以使用此类索引?

第一个查询的解释分析结果:

我会欣赏任何想法。谢谢! :)

【问题讨论】:

  • 你能发布慢查询的解释分析吗?另外,如果您想获取批次,为什么不使用游标?
  • 请edit您的问题并添加使用explain (analyze, buffers, format text)生成的execution plan(不是只是一个“简单”解释)为formatted text,并确保保留计划的缩进。粘贴文本,然后将``` 放在计划前一行和计划后一行。
  • upper(period)?那是指JANUARI 而不是别的东西吗?
  • 在问题中,我分享了基于生产中实际表格的简化表格。我还生成了一些测试数据。我想,我不允许在这里分享生产代码。我可以说在生产中Buffers: shared hit=175243
  • 您没有太多的共享缓冲区命中。缓冲区命中意味着 Postgres 发现必要的数据已经在内存中,从而避免了物理 i/o。缓冲区命中比任何 i/o 操作都要快几个数量级。也许您从 LIMIT 子句中得到了错误的印象,但是,该子句仅在查询以其他方式解决后才应用。您的查询读取每一行,对它们进行排序,然后才应用 LIMIT。

标签: sql postgresql indexing


【解决方案1】:

您查询的主要问题是谓词 (status = :status AND upper(period) &lt; now() AND position &gt; :position) 的选择性可能不好。

这个谓词选择了多少行(平均)?

请注意,LIMIT 子句与索引优化无关。

换句话说,这意味着无法以有效的方式将谓词分解为访问谓词和过滤谓词。也许 (status, upper(period)) 可以用作访问并且 (position) 作为过滤谓词,或者也许 (status, position) 可以用作访问并且 (upper(period)) 用作过滤。您应该尝试这两种组合,因为其中一种可能需要更少的 I/O 或更少的索引扫描。

create index ix1 on instance(status, upper(period), position); 
create index ix2 on instance(status, position, upper(period));

你已经有ix1了。

现在,如果平均选择性高于 5.0%,我的期望不会太高。全面扫描会更有效。

最后一个可能会有所帮助的技巧是使索引成为“覆盖索引”。也就是说,也包括其中的所有选定列。但是,我怀疑它会有很大的不同,因为它最多会在堆上 1000 次。换句话说,将您的索引增强到:

create index ix3 on instance(status, upper(period), position, period, id); 
create index ix4 on instance(status, position, upper(period), period, id);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    • 2014-07-19
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    相关资源
    最近更新 更多