选择加入巨大表格的查询需要7个小时

时间:2012-07-11 15:00:47

标签: sql performance oracle

我们的系统面临从3800万行表中选择行的性能问题。

这个包含3800万行的表存储来自客户/供应商等的信息。这些信息出现在许多其他表格中,例如发票。

主要问题是我们的数据库远未标准化。 Clients_Suppliers表有一个由3列组成的复合键,Code - varchar2(16),Category - char(2),最后一个是up_date,一个日期。一个客户端地址中的每个更改都存储在具有新日期的同一个表中。所以我们可以有这样的记录:

code             ca   up_date
---------------- --   --------
1234567890123456 CL   01/01/09
1234567890123456 CL   01/01/10
1234567890123456 CL   01/01/11
1234567890123456 CL   01/01/12
6543210987654321 SU   01/01/10
6543210987654321 SU   08/03/11

最糟糕的是,在每个使用客户端信息的表中,而不是完整的复合键,只存储代码和类别。例如,发票有自己的密钥,包括发票日期。所以我们可以这样:

invoice_no serial_no emission code             ca
---------- --------- -------- ---------------- --
1234567890 12345     05/02/12 1234567890123456 CL

我的具体问题是我必须生成一个客户列表,其中包含在给定时间段内创建的发票。由于我必须从客户端获取最新信息,因此我必须使用max(up_date)。

所以这是我的查询(在Oracle中):

SELECT
  CL.CODE,
  CL.CATEGORY,
  -- other address fields
FROM
  CLIENTS_SUPPLIERS CL
  INVOICES I
WHERE
  CL.CODE = I.CODE AND
  CL.CATEGORY = I.CATEGORY AND
  CL.UP_DATE = 
    (SELECT
       MAX(CL2.UP_DATE)
     FROM
       CLIENTS_SUPPLIERS CL2
     WHERE
       CL2.CODE = I.CODE AND
       CL2.CATEGORY = I.CATEGORY AND
       CL2.UP_DATE <= I.EMISSION
    ) AND
  I.EMISSION BETWEEN DATE1 AND DATE2

选择178,000行需要长达7个小时。发票在DATE1和DATE2之间有300,000行。

这是一个(非常,非常,非常)糟糕的设计,我提出了一个事实,我们应该通过规范化表来改进它。这将涉及为客户端创建一个表,每个代码/类别具有一个新的int主键,另一个用于Adresses(使用客户端主键作为外键),然后在每个表中使用Adresses的主键给客户。

但这意味着改变整个系统,所以我的建议已被避开。我需要找到一种不同的提高性能的方法(显然只使用SQL)。

我已经尝试了索引,视图和临时表,但没有一个在性能方面有任何显着改进。我没有想法,有人有解决方案吗?

提前致谢!

5 个答案:

答案 0 :(得分:1)

SELECT   
  CL2.CODE,
  CL2.CATEGORY,
  ... other fields
FROM 
  CLIENTS_SUPPLIERS CL2 INNER JOIN (
    SELECT DISTINCT
      CL.CODE,
      CL.CATEGORY,
      I.EMISSION
    FROM
      CLIENTS_SUPPLIERS CL INNER JOIN INVOICES I ON CL.CODE = I.CODE AND CL.CATEGORY = I.CATEGORY
    WHERE
      I.EMISSION BETWEEN DATE1 AND DATE2) CL3 ON CL2.CODE = CL3.CODE AND CL2.CATEGORY = CL3.CATEGORY
WHERE
  CL2.UP_DATE <= CL3.EMISSION
GROUP BY
  CL2.CODE,
  CL2.CATEGORY
HAVING
  CL2.UP_DATE = MAX(CL2.UP_DATE)

我们的想法是将流程分开:首先我们告诉oracle给我们提供您想要的期间发票的客户列表,然后我们得到它们的最后一个版本。在你的版本中有一个针对MAX 38000000次的检查,我真的认为这是在查询中花费的大部分时间。

但是,假设它们设置正确,我不会要求索引......

答案 1 :(得分:1)

DBA有什么要说的?

他/她是否尝试过:

  • 合并表空间
  • 增加并行查询从属
  • 将索引移动到单独物理磁盘上的单独表空间
  • 收集相关表/索引的统计信息
  • 运行解释计划
  • 通过索引优化器运行查询

我不是说SQL是完美的,但如果性能随着时间的推移而降低,那么DBA真的需要看看它。

答案 2 :(得分:0)

您可以尝试重写查询以使用分析函数而不是相关子查询:

select *
from (SELECT CL.CODE, CL.CATEGORY,   -- other address fields
             max(up_date) over (partition by cl.code, cl.category) as max_up_date
      FROM CLIENTS_SUPPLIERS CL join
           INVOICES I
           on CL.CODE = I.CODE AND
              CL.CATEGORY = I.CATEGORY and
              I.EMISSION BETWEEN DATE1 AND DATE2 and
              up_date <= i.emission
     ) t
where t.up_date = max_up_date

您可能希望删除外部选择中的max_up_date列。

有些人已经注意到,这个查询与原始查询略有不同,因为它在所有日期都占用了up_date的最大值。原始查询具有以下条件:

CL2.UP_DATE <= I.EMISSION

然而,通过及物性,这意味着:

CL2.UP_DATE <= DATE2

因此,唯一的区别是更新日期的最大值小于原始查询中的DATE1。但是,这些行将通过与UP_DATE的比较过滤掉。

虽然这个查询的措辞略有不同,但我认为它也是一样的。我必须承认不是100%肯定,因为这是我不熟悉的数据的微妙情况。

答案 3 :(得分:0)

假设(代码,ca)的行数很小,我会尝试使用内联视图强制每张发票进行索引扫描,例如:

SELECT invoice_id, 
       (SELECT MAX(rowid) KEEP (DENSE_RANK FIRST ORDER BY up_date DESC
          FROM clients_suppliers c
         WHERE c.code = i.code
           AND c.category = i.category
           AND c.up_date < i.invoice_date)
  FROM invoices i
 WHERE i.invoice_date BETWEEN :p1 AND :p2

然后,您可以将此查询加入CLIENTS_SUPPLIERS,希望通过rowid触发连接(300k rowid读取可忽略不计)。

您可以使用SQL对象改进上述查询:

CREATE TYPE client_obj AS OBJECT (
   name     VARCHAR2(50),
   add1     VARCHAR2(50),
   /*address2, city...*/
);

SELECT i.o.name, i.o.add1 /*...*/
  FROM (SELECT DISTINCT
               (SELECT client_obj(
                         max(name) KEEP (DENSE_RANK FIRST ORDER BY up_date DESC),
                         max(add1) KEEP (DENSE_RANK FIRST ORDER BY up_date DESC)
                         /*city...*/
                       ) o
                  FROM clients_suppliers c
                 WHERE c.code = i.code
                   AND c.category = i.category
                   AND c.up_date < i.invoice_date)
          FROM invoices i
         WHERE i.invoice_date BETWEEN :p1 AND :p2) i

答案 4 :(得分:0)

相关的子查询可能会导致问题,但对我而言,真正的问题在于您的主要客户端表,您无法轻松获取最新数据而不会执行max(up_date)混乱。它实际上是历史和当前数据的混合,正如您所描述的那样设计不佳。

无论如何,它将帮助您在此和其他长时间运行的联接中拥有一个表/视图,其中只包含客户端的最新数据。因此,首先为此构建一个mat视图(未经测试):

create or replace materialized view recent_clients_view
tablespace my_tablespace
nologging
build deferred
refresh complete on demand
as
select * from 
(
  select c.*, rownumber() over (partition by code, category order by up_date desc, rowid desc) rnum
  from clients c
)
where rnum = 1;

在代码,类别上添加唯一索引。假设这将在一些非工作时间表上定期刷新,并且您使用此查询将可以显示上次刷新日期的AS AS数据。在DW env或报告中,这通常是常态。

此视图的快照表应比具有所有历史记录的完整客户端表小。

现在,您正在为这个较小的视图执行加入发票,并在代码,类别(date1和date2之间的排放)上进行等值连接。类似的东西:

select cv.*
from 
recent_clients_view cv,
invoices i
where cv.code = i.code
and cv.category = i.category
and i.emission between :date1 and :date2;

希望有所帮助。