1

order_item のすべての行を更新しようとしています。Status は新しく作成された列であり、order_update テーブルからの最新の値を持っている必要があります。1 つのアイテムに複数の更新を含めることができます。

PostgreSQL 9.1 を使用しています

私はこの更新SQLを持っています。
テーブルorder_itemには 800K のレコードがあります。
テーブルorder_updateには 5Mil のレコードがあります。

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を最適な方法で実行するにはどうすればよいですか。更新には時間がかかることはわかっていますが、できるだけ早く更新したいだけです。

5Mil レコードでこの 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秒かかります。

1Mil レコードで同じ 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;
4

3 に答える 3

3

私の経験に基づいた推測: これはかなり高速になります。

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、最後の行を で簡略化できます<>

  • このような複数列のインデックスが役立つ場合があります。

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

    2 列目の降順は必須です。
    ただし、1 回のスキャンで両方のテーブルのすべてまたはほとんどを使用しているため、インデックスはおそらく役に立ちません。おそらく Postgres 9.2 以降では、カバリングindexを除いて:

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

EXPLAINPostgresが思いついた計画だけを提供します。プランナーの見積もりとコスト パラメーターが正確に設定されていない場合、これらの数値は大きくずれることがあります。実際のパフォーマンス データを取得するには、実行する必要EXPLAIN ANALYZEがあります。もちろん、クエリをテスト実行するため、大きなテーブルの場合は時間がかかります。

于 2013-11-27T04:10:47.923 に答える
0

次の再構築はどうですか?

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 は内部クエリを 1 回だけ実行して、一時テーブルAを作成します。この後、次のように単純に動作しますupdate order_item set status = (select a.production_stage from a where a.order_item_id = order_item.id);A はすでに作成されており、更新全体の固定テーブルとして使用できるため、これは非常に高速です。order_item_id ごとに再作成されるわけではありません。

于 2013-11-27T02:12:54.730 に答える