【问题标题】:Why using MAX function in query cause postgresql performance issue?为什么在查询中使用 MAX 函数会导致 postgresql 性能问题?
【发布时间】:2020-04-15 15:26:09
【问题描述】:

我有一个包含三列 time_stampdevice_idstatus s.t status 的表格是 jsontime_stampdevice_id 列也有 index。我需要获取不为空的 id 1.3.6.1.4.1.34094.1.1.1.1.1 的最新状态值。

您可以使用下面的 MAX 找到以下命令 WithWithout 的查询执行时间。

使用 MAX 查询:

SELECT DISTINCT MAX(time_stamp) FROM device.status_events WHERE 
(device_id = 7) AND
   (status->'1.3.6.1.4.1.34094.1.1.1.1.1' != '{}');

不带 MAX 的查询:

SELECT DISTINCT time_stamp FROM device.status_events WHERE 
(device_id = 7) AND
   (status->'1.3.6.1.4.1.34094.1.1.1.1.1' != '{}');

第一个查询大约需要 3 秒,第二个查询只需 3 毫秒,有两个不同的计划。我认为两个查询应该有相同的查询计划,为什么它在计算 MAX 时不使用 index?如何提高首次查询的运行时间?

PS我使用的是 postgres 9.6(dockerized 版本)。

这也是表定义。

-- Table: device.status_events

-- DROP TABLE device.status_events;

CREATE TABLE device.status_events
(
  time_stamp timestamp with time zone NOT NULL,
  device_id bigint,
  status jsonb,
  is_active boolean DEFAULT true,
  CONSTRAINT status_events_device_id_fkey FOREIGN KEY (device_id)
      REFERENCES device.devices (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE
)
WITH (
  OIDS=FALSE
);
ALTER TABLE device.status_events
  OWNER TO monitoring;

-- Index: device.status_events__time_stamp

-- DROP INDEX device.status_events__time_stamp;

CREATE INDEX status_events__time_stamp
  ON device.status_events
  USING btree
  (time_stamp);

【问题讨论】:

  • 表是否经常被清理和分析? :select relname, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze from pg_stat_all_tables where relname='device' 的输出是什么;
  • @pifor 0 行用于您的查询。但是上表的名称是status_events,对于relname=status_events,输出是2020-04-09 04:09:54.616319+00,对于last_autoanalyze 列(其他列为空)。此外,我经常运行上述查询进行测试,结果与 Q 语句相同,第一个计划/运行时差,第二个计划/运行时好。
  • 可能是表 status_events 的统计信息不够准确,尤其是对于列 time_stamp。您可以尝试使用 alter table status_events alter column time_stamp set statistics 1000; 之类的内容更改统计设置并再次分析该表。
  • @pifor 我运行了您的查询,没有任何改变。 autoanalyze 用于 2020-04-09,两个查询的行为也不同。
  • 您是否运行过 ALTER TABLE 和 ANALYZE ?

标签: sql database postgresql


【解决方案1】:

您向我们展示的索引无法生成您向我们展示的第一个计划。使用该索引,计划必须为 jsonb 列应用过滤器,但事实并非如此。所以索引必须是部分索引,并且在索引级别应用过滤器,以便计划中不需要它。

PostgreSQL 使用最大查询的索引,它只是不是您想要的索引。

您所有的 devide_id=7 都必须具有低时间戳,但 PostgreSQL 不知道这一点。它认为通过沿着timestamps索引走下去,它会很快找到一个device_id=7,然后就完成了。但相反,它需要遍历索引的一大块才能找到这样的行。

您可以通过将聚合表达式更改为以下内容来强制它远离“错误”索引:

MAX(time_stamp + interval '0')

或者您可以改为构建一个更量身定制的索引,计划者将选择该索引而不是具有虚假吸引力的索引:

create index on device.status_events (device_id , time_stamp) 
    where status->'1.3.6.1.4.1.34094.1.1.1.1.1' != '{}';

【讨论】:

    【解决方案2】:

    我相信这应该会产生一个更好的计划

    SELECT time_stamp FROM device.status_events WHERE 
    (device_id = 7) AND
       (status->'1.3.6.1.4.1.34094.1.1.1.1.1' != '{}')
    ORDER BY timestamp DESC
    LIMIT 1
    

    让我知道这对你有什么作用。

    【讨论】:

    • 是的,逻辑上它具有相同的结果。我也对其进行了测试,但它的性能与第一个查询相同。我也很好奇为什么 psql 在第一个查询中不使用索引...
    猜你喜欢
    • 1970-01-01
    • 2011-05-26
    • 2022-11-18
    • 2014-12-04
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多