【问题标题】:Update query on big table slow大表更新查询慢
【发布时间】:2013-11-27 00:42:19
【问题描述】:

我正在尝试更新 order_item 中的每一行。 Status 是一个新创建的列,并且必须具有 order_update 表中的最新值。一个项目可以有多个更新。

我使用的是 PostgreSQL 9.1

我有这个更新 sql。
order_item 表有 800K 条记录。
order_update 有 5 百万条记录。

update order_item
set status = (
    select production_stage
    from order_update
    where id = (
        select max(id)
        from order_update
        where order_item_id = order_item.id
    )
);

我怎样才能让这个 sql 以最好的方式执行。我知道更新需要一些时间,我只想尽快更新。

我发现在 50 万条记录上执行此 sql 时。

select max(id) from order_update where order_item_id = 100;

解释:

Result  (cost=784.10..784.11 rows=1 width=0)"   InitPlan 1 (returns $0)
    ->  Limit  (cost=0.00..784.10 rows=1 width=8)
          ->  Index Scan Backward using order_update_pkey on order_update  (cost=0.00..104694554.13 rows=133522 width=8)
                Index Cond: (id IS NOT NULL)
                Filter: (order_item_id = 100)

大约需要 6 秒。

当我在 10 万条记录中执行相同的 sql 时:
解释:

Aggregate  (cost=13.43..13.44 rows=1 width=8)   ->  Index Scan using
order_update_order_item_id_idx on order_update  (cost=0.00..13.40
rows=11 width=8)
        Index Cond: (order_item_id = 100)

大约需要 11 毫秒。
11 毫秒与 6 秒。为什么会有这么大的差异?

为了缩小范围,我试试这个:

select id from order_update where order_item_id = 100 order by id asc
limit 1 
Total query runtime: 41 ms.

然后这个:

select id from order_update where order_item_id = 100 order by id desc
limit 1 
Total query runtime: 5310 ms.

所以 asc 和 desc 的差异很大。

解决方案: 创建索引:

CREATE INDEX order_update_mult_idx ON order_update (order_item_id, id DESC);

更新:

UPDATE order_item i
SET    test_print_provider_id = u.test_print_provider_id
FROM  (
   SELECT DISTINCT ON (1)
          test_print_provider_id
   FROM   orders
   ORDER  BY 1, id DESC
   ) u
WHERE  i.order_id = u.id
AND    i.test_print_provider_id IS DISTINCT FROM u.test_print_provider_id;

【问题讨论】:

  • 如果不熟悉 PostgreSQL,我可能会尝试将聚合查询的结果放在临时表中,然后在 UPDATE 语句中引用它。
  • 您能否确认一下,您似乎想更新order_items 表中的每条记录(全部为 800k)。那是对的吗?我问是因为感觉“状态”列只会在需要时更新某些记录。
  • 你应该解释(用简单的英语)你想要做什么。此外,与往常一样,您的 Postgres 版本和表定义的相关部分(psql 中的\d tbl)...

标签: sql postgresql sql-update greatest-n-per-group postgresql-performance


【解决方案1】:

我有根据的猜测:这会大大更快。

UPDATE order_item i
SET    status = u.production_stage
FROM  (
   SELECT DISTINCT ON (1)
          order_item_id, production_stage
   FROM   order_update
   ORDER  BY 1, id DESC
   ) u
WHERE  i.id = u.order_item_id
AND    i.status IS DISTINCT FROM u.production_stage;   -- avoid empty updates
  • 问题中的查询存在细微的差异。原来的更新每一order_item。如果在order_update 中没有找到匹配的行,这将导致status 被设置为NULL。此查询将保留这些行(保留原始值,不更新)。

  • 在这个密切相关的答案中使用DISTINCT ON 进行子查询的详细说明:
    Select first row in each GROUP BY group?

  • 一般来说,单个子查询的性能应该很容易胜过使用相关子查询的方法。优化查询更是如此。

  • 如果order_item.status应该定义为NOT NULL,最后一行可以简化为<>

  • 这样的multicolumn index 可能会有所帮助:

    CREATE INDEX order_update_mult_idx ON order_update(order_item_id, id DESC);
    

    第二列的降序是必不可少的。
    但是,由于您在一次扫描中使用了两个表的全部或大部分,因此索引可能无济于事。除了covering index,可能在 Postgres 9.2 或更高版本中:

    CREATE INDEX order_update_mult_idx
    ON order_update(order_item_id, id DESC, production_stage);
    

EXPLAIN 只为您提供 Postgres 提出的计划。如果计划者的估计和成本参数设置不准确,这些数字可能会有所偏差。要获得实际的性能数据,您必须运行 EXPLAIN ANALYZE - 当然,对于大表来说这将需要很长时间,因为它会测试执行查询。

【讨论】:

    【解决方案2】:

    如果您在包含 order_item_id 和 production_stage 的 Id 上的 order_update 中有一个索引,将会有所帮助。 除此之外,这相当简单。使用临时表而不是子查询可能是一种选择,但我看不出还有什么可以改进的。

    【讨论】:

      【解决方案3】:

      下面的重构呢?

      update order_item
      set status = (
          select a.production_stage from (
              select ou.id, ou.production_stage
              from order_update ou
              where ou.order_item_id = order_item.id
              order by ou.id desc
          ) a limit 1
      );
      

      编辑:既然上面比较慢,那么下面的重构呢?

      update order_item
      set status = (
          select a.production_stage from (
      /********************************************** INNER QUERY START **/
              select ou.order_item_id, ou.production_stage
              from order_update ou
              INNER JOIN (
                  select order_item_id, max(id) as max_id
                  from order_update
                  group by order_item_id
              ) ou_max ON (ou.order_item_id = ou_max.order_item_id
                           AND ou.id = ou_max.max_id)
      /********************************************** INNER QUERY END **/
          ) a where a.order_item_id = order_item.id
      );
      

      在这种情况下,您的 DBMS 将执行 内部查询 一次 以创建临时表 A。在此之后,它将简单地表现为:update order_item set status = (select a.production_stage from a where a.order_item_id = order_item.id);。这将非常快,因为 A 已经创建并且可用作整个更新的固定表 - 它不会为每个 order_item_id 重新创建。

      【讨论】:

      • 这个其实比:update order_item set status = (select production_stage from order_update where id = (select max(id) from order_update where order_item_id = order_item.id));
      猜你喜欢
      • 1970-01-01
      • 2017-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-26
      • 2019-05-14
      • 2015-11-08
      相关资源
      最近更新 更多