1

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

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

主要问题是我们的数据库远未标准化。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 个小时。Invoices 在 DATE1 和 DATE2 之间有 300,000 行。

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

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

我尝试过索引、视图、临时表,但没有一个对性能有任何显着改善。我没有想法,有人有解决方案吗?

提前致谢!

4

5 回答 5

1

DBA 有什么要说的?

他/她是否尝试过:

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

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

于 2012-07-11T15:22:42.080 回答
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 次的检查,我真的认为这是在查询中花费的大部分时间。

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

于 2012-07-11T15:10:27.997 回答
0

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

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

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;

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

此视图的快照表应该比包含所有历史记录的完整客户端表小得多。

现在,您正在对这个较小的视图进行合并发票,并对代码、类别(日期 1 和日期 2 之间的排放)进行等值连接。就像是:

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;

希望有帮助。

于 2012-07-11T16:56:44.563 回答
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% 肯定的,因为这是我不熟悉的数据的微妙情况。

于 2012-07-11T15:10:44.510 回答
0

假设 (code,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
于 2012-07-11T16:08:41.197 回答