2

我有四到五个大小非常大的表,它们使用以下查询进行了外部连接。有什么办法可以重写它以提高性能?

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

4

2 回答 2

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)为了获得更好的性能,在or上创建索引(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但是和之间的连接仍然存在问题t5t1如果和之间存在 1:1 的关系t4,最好在第二行写下这样的内容:

(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 中可用,否则您可以使用流水线函数

检查这个SQL Fiddle,尤其是执行计划的差异。

于 2014-11-02T21:23:59.487 回答
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;

这种重写不需要创建额外的,否则无用的索引。

于 2014-11-02T20:48:55.783 回答