这不是“添加索引并检查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。该系统通过授予对表order
和ordercontent
的访问权来返回订单。前者包含特定订单,后者包含与订单中的订单项对应的行。我已在本地同步所有这些数据,但我需要重新创建外键关系。显然,这个同步对于更新几百个订单来说并不会非常缓慢,但是现在我正在将多个API的所有订单同步到过去两年。有没有办法提高这个查询的速度?
答案 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更新它似乎附加了两个问题:
更新相当昂贵,因为您不仅需要找到所需的值,而且需要将行重新插入表中并删除原始数据(通过真空)。消除值的查找,或仅更新,将更有效。
顺便说一下,索引可能不会更有效 - 在Oracle中我希望获得该类查询的散列连接,并且当你必须访问索引时索引通常是有限的帮助整个桌子。
答案 2 :(得分:0)
我同意你的意见,这不是“添加索引并检查EXPLAIN”问题。由于您似乎正在更新大部分表,因此散列连接可能是最好的方法。如果你有太多的索引,这些都需要在批量更新期间保持,并且最好放弃一些,至少在更新期间。
使用(ANALYZE,BUFFERS)运行它会更有用,特别是如果你先打开track_io_timing。另外,你说你想要它更快,但它需要多快?
批量更新的通常建议是确保检查点不会经常发生。
我会在更新运行时监视服务器(例如top),看看服务器是IO绑定还是CPU绑定。