我有四到五张表,它们的大小非常大,并且使用下面的查询将它们外部连接起来。是否有任何方法可以重写它以便提高性能?
SELECT t1.id,
MIN(t5.date) AS first_pri_date,
MIN(t3.date) AS first_pub_date,
MAX(t3.date) AS last_publ_date,
MIN(t2.date) AS first_exp_date
FROM t1
LEFT JOIN t2 ON (t1.id = t2.id)
LEFT JOIN t3 ON (t3.id = t1.id)
LEFT JOIN t4 ON (t1.id = t4.id)
LEFT JOIN t5 ON (t5.p_id =t4.p_id)
GROUP BY t1.id
ORDER BY t1.id;
记录计数为:
t1
:6434323 t2
:6934562 t3
:9141420 t4
:11515192 t5
:3797768 大多数用于连接的列都有索引。解释计划中最耗费的部分是最后发生的t4
外连接。我只是想知道是否有任何方法可以重写它以提高性能。
答案 0 :(得分:1)
假设id
是t1
中的主键,您的查询可能(或可能不会,取决于您的Oracle PGA的设置)运行得更好,如下所示:
SELECT --+ leading(t1) use_hash(t2x,t3x,t45x) full(t1) no_push_pred(t2x) no_push_pred(t3x) no_push_pred(t45x) all_rows
t1.id,
t45x.first_pri_date,
t3.first_pub_date,
t3.last_publ_date,
t2.first_exp_date
FROM t1
LEFT JOIN (
SELECT t2.id,
MIN(t2.date) AS first_exp_date
FROM t2
GROUP BY t2.id
) t2x
ON t2x.id = t1.id
LEFT JOIN (
SELECT t3.id,
MIN(t3.date) AS first_pub_date,
MAX(t3.date) AS last_publ_date
FROM t3
GROUP BY t3.id
) t3x
ON t3x.id = t1.id
LEFT JOIN (
SELECT --+ leading(t5) use_hash(t4)
t4.id,
MIN(t5.date) AS first_pri_date
FROM t4
JOIN t5 ON t5.p_id = t4.p_id
GROUP BY t4.id
) t45x
ON t45x.id = t1.id
ORDER BY t1.id;
这种重写并不需要创建额外的,但无用的索引。
答案 1 :(得分:1)
我会说你的问题是你正在做很多LEFT JOIN并且在应用所有这些JOIN之后最终的结果集变得太大了。此类索引也不能以最快的方式计算MIN或MAX。通过充分利用索引,您应该能够非常快速地计算MIN或MAX。
我会写这样的查询:
SELECT t1.id,
(SELECT MIN(t5.date) FROM t5 JOIN t4 ON t5.p_id = t4.p_id WHERE t4.id = t1.id) AS first_pri_date,
(SELECT MIN(date) FROM t3 WHERE t3.id = t1.id) AS first_pub_date,
(SELECT MAX(date) FROM t3 WHERE t3.id = t1.id) AS last_publ_date,
(SELECT MIN(date) FROM t2 WHERE t2.id = t1.id) AS first_exp_date
FROM t1
ORDER BY t1.id;
为了获得更好的效果,请在(id, date)
或(p_id, date)
上创建索引。
所以你的索引是这样的:
CREATE INDEX ix2 ON T2 (id,date);
CREATE INDEX ix3 ON T3 (id,date);
CREATE INDEX ix5 ON T5 (p_id,date);
CREATE INDEX ix4 ON T4 (id);
但t4
和t5
之间的联接仍然存在问题。
如果t1
和t4
之间存在1:1的关系,那么在第二行写下这样的内容会更好:
(SELECT MIN(t5.date) FROM t5 WHERE t5.p_id = (SELECT p_id FROM t4 WHERE t4.id=t1.id)) AS first_pri_date,
如果它是1:N,并且如果CROSS APPLY和OUTER APPLY在您的Oracle版本上工作,您可以像这样重写第二行:
(SELECT MIN(t5min.PartialMinimum)
FROM t4
CROSS APPLY
(
SELECT PartialMinimum = MIN(t5.date)
FROM t5
WHERE t5.p_id = t4.p_id
) AS t5min
WHERE t4.id = t1.id)
AS first_pri_date
所有这些都是为了在计算MIN或MAX时最好地使用索引。 因此整个SELECT可以像这样重写:
SELECT t1.id,
(SELECT MIN(t5min.PartialMinimum)
FROM t4
CROSS APPLY
(
SELECT TOP 1 PartialMinimum = date
FROM t5
WHERE t5.p_id = t4.p_id
ORDER BY 1 ASC
) AS t5min
WHERE t4.id = t1.id) AS first_pri_date,
(SELECT TOP 1 date FROM t2 WHERE t2.id = t1.id ORDER BY 1 ASC) AS first_exp_date,
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 ASC) AS first_pub_date,
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 DESC) AS last_publ_date
FROM t1
ORDER BY 1;
这是我认为如何从历史数据表中获取MIN或MAX的最佳方式。
关键是,使用具有大量非索引值的MIN会使服务器将所有数据加载到内存中,然后根据非索引数据计算MIN或MAX,这需要很长时间,因为它对I的要求很高/ O操作。使用MIN或MAX时索引的错误使用可能会导致这种情况,您将所有历史表数据缓存在内存中,除了MIN或MAX计算之外不需要任何其他内容。
如果没有查询的CROSS APPLY部分,服务器需要从t5加载到内存中的所有日期,并从整个加载的结果集中计算MAX。
标记正确索引表上的MIN函数的行为类似于TOP 1 ORDER BY,这非常快。通过这种方式,您可以立即获得结果。
CROSS APPLY在Oracle 12C中可用,否则您可以使用pipelined functions。
检查此SQL Fiddle,尤其是执行计划的差异。