使用许多左外连接和重表来调整/重写sql查询

时间:2014-10-31 09:29:43

标签: sql oracle performance left-join sql-tuning

我有四到五张表,它们的大小非常大,并且使用下面的查询将它们外部连接起来。是否有任何方法可以重写它以便提高性能?

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外连接。我只是想知道是否有任何方法可以重写它以提高性能。

2 个答案:

答案 0 :(得分:1)

假设idt1中的主键,您的查询可能(或可能不会,取决于您的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);

t4t5之间的联接仍然存在问题。 如果t1t4之间存在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,尤其是执行计划的差异。