【问题标题】:Optimize performance for queries on recent rows of a large table优化大表最近行的查询性能
【发布时间】:2014-01-11 08:16:48
【问题描述】:

我有一张大桌子:

CREATE TABLE "orders" (
"id" serial NOT NULL,
"person_id" int4,
"created" int4,
CONSTRAINT "orders_pkey" PRIMARY KEY ("id")
);

90% 的请求是关于过去 2-3 天 person_id 的订单,例如:

select * from orders
where person_id = 1
and created >= extract(epoch from current_timestamp)::int - 60 * 60 * 24 * 3;

如何提高性能?

我知道Partitioning,但是现有的行呢?看起来我需要每 2-3 天手动创建一次 INHERITS 表。

【问题讨论】:

  • 你知道Indexing吗?
  • 是的,当然,我已经有索引(person_id,created)
  • created 上创建索引有什么问题?我看不出分区在这里有什么帮助。它将作为一个低效的索引。所以首先创建索引。

标签: sql performance postgresql indexing postgresql-performance


【解决方案1】:

(person_id, created) 上的 partial, multicolumn index 带有伪IMMUTABLE 条件会有所帮助(很多)。需要不时重新创建以保持性能。

注意,如果您的表不是很大,您可以在很大程度上简化并使用普通的多列索引。
或者考虑 Postgres 12 或更高版本中的table partitioning(该功能最终成熟)。

原始 函数 提供一个恒定的时间点,3 天或更长时间(在您的情况下由 unix 纪元表示):

CREATE OR REPLACE FUNCTION f_orders_idx_start()
  RETURNS int LANGUAGE sql IMMUTABLE PARALLEL SAFE COST 1 AS
'SELECT 1387497600';

PARALLEL SAFE 仅适用于 Postgres 10 或更高版本。
1387497600 是以下结果:

SELECT extract(epoch from now())::integer - 259200;
-- 259200 being the result of 60 * 60 * 24 * 3

将您的部分索引建立在这个伪IMMUTABLE 条件上:

CREATE INDEX orders_created_recent_idx ON orders (person_id, created)
WHERE created >= f_orders_idx_start();

您的查询基于相同的条件:

SELECT *
FROM   orders
WHERE  person_id = 1
AND    created >= f_orders_idx_start()  -- match partial idx condition
AND    created >= extract(epoch from now())::integer - 259200;  -- actual condition

AND created >= f_orders_idx_start() 行似乎是多余的,但有助于说服 Postgres 使用部分索引。

一个不时重新创建函数和索引的函数。可能每晚都有一个 cron-job:

CREATE OR REPLACE FUNCTION f_orders_reindex_partial()
  RETURNS void AS
$func$
DECLARE
   -- 3 days back, starting at 00:00
   _start int := extract(epoch from now()::date -3)::int;
BEGIN       
   IF _start = f_orders_idx_start() THEN
      -- do nothing, nothing changes.
   ELSE
      DROP INDEX IF EXISTS orders_created_recent_idx;
      -- Recreate IMMUTABLE function
      EXECUTE format('
         CREATE OR REPLACE FUNCTION f_orders_idx_start()
           RETURNS int LANGUAGE sql IMMUTABLE PARALLEL SAFE COST 1 AS
         $$SELECT %s $$'
       , _start
      );
      -- Recreate partial index
      CREATE INDEX orders_created_recent_idx ON orders (person_id, created)
      WHERE created >= f_orders_idx_start();
   END IF;    
END
$func$  LANGUAGE plpgsql;

然后,要重新设置索引,调用(理想情况下很少或没有并发负载):

SELECT f_orders_reindex_partial();  -- that's all

如果由于并发负载而无法删除和重新创建索引,请考虑 Postgres 12 或更高版本中的 REINDEX CONCURRENTLY。很简单:

REINDEX INDEX orders_created_recent_idx;

所有查询都会继续工作,即使您从未调用此函数。随着部分索引的增长,性能会随着时间慢慢下降。

我在几个大表和类似的要求上成功地使用了这个机制。 非常快。

对于 Postgres 9.2 或更高版本,如果您的表只有很少的小列,并且如果表没有大量写入,则将其设为 covering index 可能是值得的:

CREATE INDEX orders_created_recent_idx ON orders (person_id, created, id)
WHERE created >= f_orders_idx_start();

在 Postgres 11 或更高版本中,您可能希望改用 INCLUDE

CREATE INDEX orders_created_recent_idx ON orders (person_id, created) INCLUDE (id)
WHERE created >= f_orders_idx_start();

【讨论】:

    【解决方案2】:

    建议:-

    它可以帮助你。 由于表大小在增长,您的查询性能将逐渐下降。最好维护 3-5 天(如果您非常确定只访问 2-3 天)记录,并定期将旧记录迁移到备份表。

    【讨论】:

    • 是的,谢谢。我考虑过这个选项,但它需要进行相当多的更改,因为 10% 的请求必须进入备份表。
    • 我认为有一种比手动拆分表更简单的方法,然后手动或每 2-5 天通过脚本维护一次。
    • 如果您不依赖旧记录,您可以拥有相同结构的备份表,并使用 db_link 轻松迁移数据。然后将其作为批处理(自动化)迁移操作
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-24
    • 2021-12-06
    • 2016-12-01
    • 2020-07-05
    • 2013-07-02
    相关资源
    最近更新 更多