具有深层次结构的非常慢的postgresql查询

时间:2017-04-01 19:11:43

标签: sql database postgresql

我有一个查询,我想在我的数据库中获取特定用户(所有者表)的所有事务。数据库非常规范化,因此从事务到所有者遍历许多表。我的相关外键表格如下:

**owners**
-------
id

**store_shops**
-----------
id
owner_id

**service_shops**
-------------
id
owner_id

**products**
-------------
id
store_shop_id

**services**
------------
id
service_shop_id

**order_services**
------------------
id
service_id
order_id

**order_products**
------------------
id
product_id
order_id


**orders**
----------
id
transaction_id


**transactions**
----------------
id
refund_transaction_id
amount

我有以下查询:

SELECT DISTINCT ON (sales.id) sales.id, sales.amount FROM transactions sales 
LEFT OUTER JOIN transactions refunds ON refunds.id = sales.refund_transaction_id
LEFT OUTER JOIN orders ON orders.transaction_id = trans.id OR orders.transaction_id = refunds.id
LEFT OUTER JOIN order_services ON order_services.order_id = orders.id
LEFT OUTER JOIN order_products ON order_products.order_id = orders.id
LEFT OUTER JOIN products ON  products.id = order_products.product_id
LEFT OUTER JOIN services ON services.id = order_services.service_id
LEFT OUTER JOIN service_shops ON service_shops.id = services.service_shop_id
LEFT OUTER JOIN store_shops ON store_shops.id = products.store_shop_id
LEFT OUTER JOIN owners service_shop_owners ON service_shop_owners.id = service_shops.owner_id
LEFT OUTER JOIN owners store_shop_owners ON store_shop_owners.id = store_shops.owner_id
WHERE (service_shop_owners.id = 26930 OR store_shop_owners.id = 26930)

这给了我想要的结果。唯一的麻烦是,在数十万条记录的数据集中,它变得非常缓慢。

对于SQL,我不是很先进,但我意识到所有LEFT OUTER JOIN都不是很有效。

我有更好的方法来处理此查询吗?或者我是否需要对数据库进行非规范化并在事务表中存储更多信息?

更新 使用下面的Wyzard答案,我现在有了这个问题:

SELECT trans.id, trans.amount, refunds.id
FROM
  service_shops
  JOIN services ON services.service_shop_id = service_shop.id
  JOIN order_services ON order_services.service_id = services_id
  JOIN orders ON orders.id = order_services.order_id
  JOIN transactions trans ON trans.id = orders.transaction_id
  LEFT JOIN transactions refunds ON refunds.id = trans.refund_transaction_id
WHERE service_shops.owner_id = 26930
UNION
SELECT trans.id, trans.amount, refunds.id
FROM
  store_shops
  JOIN products ON store_shops.id = products.store_shop_id
  JOIN order_products ON order_products.product_id = products.id
  JOIN orders ON orders.id = order_products.order_id
  JOIN transactions trans ON trans.id = orders.transaction_id
  LEFT JOIN transactions refunds ON refunds.id = trans.refund_transaction_id
WHERE store_shops.owner_id = 2693

这是非常快的并且是一个很大的进步。现在唯一的问题是这两个LEFT JOIN transactions refunds ON refunds.id = trans.refund_transaction_id似乎没有抓住相关的退款transactions.我假设这是因为他们没有直接与他们关联order,所以{ {1}}子句将其过滤掉。

2 个答案:

答案 0 :(得分:2)

改变这个:

WHERE (service_shop_owners.id = 26930 OR store_shop_owners.id = 26930)

对此:

WHERE 26930 IN (service_shop_owners.id, store_shop_owners.id)

使用OR通常意味着不会使用索引,但应该与IN一起使用。

上述变化应足以产生重大影响。要进一步改进查询,请颠倒表的顺序,尤其是列出service_shop_owners作为FROM子句中的第一个表。优化器应该为您执行此操作,但通常不会。

答案 1 :(得分:1)

首先,EXPLAIN是您的朋友:它告诉您数据库将用于运行查询的查询计划,以便您可以看到瓶颈所在。最初可能难以理解输出,但如果使用pgAdmin,则其EXPLAIN菜单命令命令会为您提供更加直观的nice graphical visualization

其次,WHERE子句中使用的值位于一长串外连接的末尾,这是低效的,因为数据库可能必须执行所有连接并生成每个候选行才能获得所有者ID,仅放弃大部分行,因为所有者ID与WHERE条件不匹配。

看起来您已经以这种方式构建了查询,因为从销售到所有者有两条不同的路径:通过产品或通过服务。这意味着您基本上一次执行两个不同的查询,其方式是强制数据库处理实际来自服务的行上与产品相关的连接条件,反之亦然。使用UNION实际执行两个单独的查询可能会更有效率,并从您用于过滤的表中启动每个查询:

SELECT col1, col2, etc
FROM
  owners
  JOIN service_shops ON service_shops.owner_id = owners.id
  JOIN services ON services.service_shop_id = service_shop.id
  ...etc...
WHERE owners.id = 26930
UNION
SELECT col1, col2, etc
FROM
  owners
  JOIN store_shops ON store_shops.owner_id = owners.id
  JOIN products ON store_shops.id = products.store_shop_id
  ...etc...
WHERE owners.id = 26930

这应该允许数据库使用索引快速查找所有者,然后使用其他索引快速查找关联的商店,依此类推。 (假设您的FK列上有索引,例如service_shops.owner_id。如果没有,则应该。)

请注意,我已经写了JOIN而不是LEFT OUTER JOIN。由于您未在同一查询中混合使用产品数据和服务数据,因此您不会将与产品相关的行无法加入与服务相关的表格,反之亦然,因此你可能根本不需要外连接。

此外,如果除了ID之外,您不需要owners表中的任何属性,则可以将该表保留在查询之外。只需WHERE store_shops.owner_id = 26930

第三,我发现将FROM子句构造为仅在实际需要的地方使用外连接是有帮助的。假设您已经写过:

FROM
  foo
  LEFT JOIN bar ON bar.foo_id = foo.id
  LEFT JOIN baz ON baz.bar_id = bar.id

我们假设您需要获取foo数据,即使它没有关联bar,但您需要{ {1}}数据如果没有相关联的bar - 或者您可能知道那里 没有相关baz的{​​{1}}。在这种情况下,您可以像这样重写查询:

bar

根据我的经验,这在PostgreSQL中往往更有效。 (我不了解其他数据库。)