优化SQL(40分钟运行时)

时间:2014-01-27 22:56:18

标签: sql database performance postgresql optimization

这不是“添加索引并检查EXPLAIN”问题。 我正在寻找一种更优化的方法来更新/设置一个表中的值在JOIN查询上。此特定查询不是一个大问题,因为它是一次性项目。我希望提高我对Postgres如何做事的了解,并更好地理解如何编写查询。

这是针对Postgres 9.3中的两个表运行的。

SQL查询:

UPDATE
    ordercontent t1
SET
    order_id = t2.id
FROM
    order t2
WHERE
    t1.pos_order_id = t2.pos_order_id AND
    t1.api_id = t2.api_id;

表统计数据:

ordercontent

  rows  | api_id
--------+--------
 265435 |      2
 120561 |      3
 164288 |      4

结构:

                                     Table "public.ordercontent"
    Column     |           Type           |                           Modifiers
---------------+--------------------------+----------------------------------------------------------------
 id            | integer                  | not null default nextval('ordercontent_id_seq'::regclass)
 created       | timestamp with time zone | not null
 updated       | timestamp with time zone | not null
 api_id        | integer                  | not null
 obj_id        | character varying(255)   | not null
 raw_object    | text                     |
 order_id      | integer                  |
 item_id       | character varying(255)   | not null
 pos_order_id  | character varying(255)   | not null
 item_id       | integer                  |
 menuitem_id   | integer                  |
Indexes:
    "ordercontent_pkey" PRIMARY KEY, btree (id)
    "ordercontent_api_id" btree (api_id)
    "ordercontent_item_id" btree (item_id)
    "ordercontent_pos_order_id" btree (pos_order_id)
    "ordercontent_menuitem_id" btree (menuitem_id)
    "ordercontent_obj_id" btree (obj_id)
    "ordercontent_order_id" btree (order_id)
Foreign-key constraints:
    "api_id_refs_apibase_ptr_id_9fe756a2" FOREIGN KEY (api_id) REFERENCES api(apibase_ptr_id) DEFERRABLE INITIALLY DEFERRED
    "item_id_refs_id_bf9d5193" FOREIGN KEY (item_id) REFERENCES lavu_menuitem(id) DEFERRABLE INITIALLY DEFERRED
    "menuitem_id_refs_menuitembase_ptr_id_0da7969d" FOREIGN KEY (menuitem_id) REFERENCES menuitemforcedmodifier(menuitembase_ptr_id) DEFERRABLE INITIALLY DEFERRED
    "order_id_refs_id_fd9de410" FOREIGN KEY (order_id) REFERENCES order(id) DEFERRABLE INITIALLY DEFERRED
Referenced by:
    TABLE "ordermodifier" CONSTRAINT "order_content_id_refs_id_fb05c9fc" FOREIGN KEY (order_content_id) REFERENCES ordercontent(id) DEFERRABLE INITIALLY DEFERRED

顺序

  rows  | api_id
--------+--------
 176808 |      2
  59207 |      3
 112849 |      4

结构:

                                     Table "public.order"
    Column     |           Type           |                        Modifiers
---------------+--------------------------+---------------------------------------------------------
 id            | integer                  | not null default nextval('order_id_seq'::regclass)
 created       | timestamp with time zone | not null
 updated       | timestamp with time zone | not null
 api_id        | integer                  | not null
 obj_id        | character varying(255)   | not null
 raw_object    | text                     |
 opened        | character varying(255)   |
 closed        | character varying(255)   |
 pos_order_id  | character varying(255)   | not null
 closed_on     | timestamp with time zone |
Indexes:
    "order_pkey" PRIMARY KEY, btree (id)
    "order_api_id_2190a0aad3aab788_uniq" UNIQUE CONSTRAINT, btree (api_id, obj_id)
    "order_api_id" btree (api_id)
    "order_closed_on" btree (closed_on)
    "order_pos_order_id" btree (lavu_order_id)
    "order_obj_id" btree (obj_id)
Foreign-key constraints:
    "api_id_refs_apibase_ptr_id_afc114a7" FOREIGN KEY (api_id) REFERENCES lavuapi(apibase_ptr_id) DEFERRABLE INITIALLY DEFERRED
Referenced by:
    TABLE "ordermodifier" CONSTRAINT "order_id_refs_id_92c3b747" FOREIGN KEY (order_id) REFERENCES order(id) DEFERRABLE INITIALLY DEFERRED
    TABLE "ordercontent" CONSTRAINT "order_id_refs_id_fd9de410" FOREIGN KEY (order_id) REFERENCES order(id) DEFERRABLE INITIALLY DEFERRED

EXPLAIN结果:

Update on ordermodifier t1  (cost=83307.62..187654.96 rows=550908 width=330)
   ->  Hash Join  (cost=83307.62..187654.96 rows=550908 width=330)
         Hash Cond: (((t2.pos_order_id)::text = (t1.pos_order_id)::text) AND (t2.api_id = t1.api_id))
         ->  Seq Scan on order t2  (cost=0.00..52685.08 rows=351359 width=22)
         ->  Hash  (cost=56134.41..56134.41 rows=590803 width=320)
               ->  Seq Scan on ordermodifier t1  (cost=0.00..56134.41 rows=590803 width=320)

背景

我正在使用POS系统提供的外部API。该系统通过授予对表orderordercontent的访问权来返回订单。前者包含特定订单,后者包含与订单中的订单项对应的行。我已在本地同步所有这些数据,但我需要重新创建外键关系。显然,这个同步对于更新几百个订单来说并不会非常缓慢,但是现在我正在将多个API的所有订单同步到过去两年。有没有办法提高这个查询的速度?

3 个答案:

答案 0 :(得分:4)

  

这不是“添加索引并检查EXPLAIN”问题

抱歉,但是......确实如此。如果您没有索引来完成这项工作:

t1.pos_order_id = t2.pos_order_id AND
t1.api_id = t2.api_id

...然后查询对两个表和散列或合并连接执行seq扫描,或者错误的索引扫描(可能在第二个表上使用seq扫描)和散列或合并连接,并且计划基本上,好吧,完全糟透了。

您需要(pos_order_id, api_id)(api_id, pos_order_id)上的t1和t2索引。根据您是按pos_order_id, api_id订购还是反过来订购,您可能更喜欢右侧订购的列 - 如果不是,则可以使用。

无论如何,你仍然需要添加索引,检查解释,yada yada。

关于你的评论:

  

api_id是一个ForeignKey(应该被索引),而pos_order_id上有一个索引。

不... api_id未必编入索引。反正不在两个桌子上。 引用的表上有唯一的索引;是的除非您已添加它,否则不是引用表。

pos_order_id,虽然根据名称直觉上基数低于api_id,但如果你的表有足够多的可能与之匹配的行,那么这里显然没什么用处。即便如此,如果对立的桌子没有提供相同的索引,那么它在实践中几乎没用。

关于你(相当健谈)的解释,因为你已经编辑了你的问题:

Hash Cond: (((t2.pos_order_id)::text = (t1.pos_order_id)::text) AND (t2.api_id = t1.api_id))

您正在将每一行的列转换为文本。这不仅是非常非常非常错误的(近百万行);这是疯狂的慢......你也需要修复你的类型。 (在你的情况下,转换不会很慢,因为你要从varchar转到文本。但是比较int仍然会更快。)

大卫在评论中指出,鉴于基数,这两个指数可能没什么帮助。您当前计划中的统计信息可能已关闭。如果没有,您需要找到并更新550k左右的行,将每个行写入磁盘(两次:删除旧行,然后插入新行)。那一定很慢。

另一个问题可能是您将内容存储到磁盘。如果在更改列类型并添加两个索引后仍然很慢,increase your work_mem

答案 1 :(得分:1)

加载ordercontent表,然后使用order.id更新它似乎附加了两个问题:

  1. 您已经在表之间建立了联接,因此添加order.id似乎也是多余的。
  2. 如果您确实需要该表中的值,请考虑将order.id作为表加载的一部分进行查找,而不是作为附加步骤。
  3. 更新相当昂贵,因为您不仅需要找到所需的值,而且需要将行重新插入表中并删除原始数据(通过真空)。消除值的查找,或仅更新,将更有效。

    顺便说一下,索引可能不会更有效 - 在Oracle中我希望获得该类查询的散列连接,并且当你必须访问索引时索引通常是有限的帮助整个桌子。

答案 2 :(得分:0)

我同意你的意见,这不是“添加索引并检查EXPLAIN”问题。由于您似乎正在更新大部分表,因此散列连接可能是最好的方法。如果你有太多的索引,这些都需要在批量更新期间保持,并且最好放弃一些,至少在更新期间。

使用(ANALYZE,BUFFERS)运行它会更有用,特别是如果你先打开track_io_timing。另外,你说你想要它更快,但它需要多快?

批量更新的通常建议是确保检查点不会经常发生。

我会在更新运行时监视服务器(例如top),看看服务器是IO绑定还是CPU绑定。