Oracle加入百万记录表会降低查询性能

Posted

技术标签:

【中文标题】Oracle加入百万记录表会降低查询性能【英文标题】:Oracle Join a Million Record Table slow down query performance 【发布时间】:2018-05-06 09:50:28 【问题描述】:

我的要求是找到每个客户的空闲时间。要首先找到空闲客户,我必须获取 注册表,它有100万条记录。要找出每个客户的最后交易时间,我必须 加入有 6000 万条记录的事务日志表。下面是我的查询。

SELECT CUSTOMERNAME,MOBILENUMBER,ACCOUNTNUMBER,
   CUSTOMERID,LASTTXNDATE, 
   FLOOR(SYSDATE - to_date(TO_CHAR(LASTTXNDATE, 'DD/MM/YYYY'),'DD/MM/YYYY')) AS "IDLE DAYS" 
FROM REGN_MAST
LEFT JOIN 
 ( SELECT TXNMOBILENUMBER,MAX(TXNDT) AS LASTTXNDATE 
   FROM TXN_DETL 
   GROUP BY TXNMOBILENUMBER
 ) 
ON MOBILENUMBER=TXNMOBILENUMBER;

explain plan for
SELECT CUSTOMERNAME,MOBILENUMBER,ACCOUNTNUMBER,
   CUSTOMERID,LASTTXNDATE, 
   FLOOR(SYSDATE - to_date(TO_CHAR(LASTTXNDATE, 'DD/MM/YYYY'),'DD/MM/YYYY')) AS "IDLE DAYS" 
FROM REGN_MAST
LEFT JOIN 
 ( SELECT TXNMOBILENUMBER,MAX(TXNDT) AS LASTTXNDATE 
   FROM TXN_DETL 
   GROUP BY TXNMOBILENUMBER
 ) 
ON MOBILENUMBER=TXNMOBILENUMBER;


Plan hash value: 403296370

------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name                      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                           |  1231K|   102M|       |  1554K  (1)| 05:10:59 |       |       |
|*  1 |  HASH JOIN RIGHT OUTER               |                           |  1231K|   102M|    58M|  1554K  (1)| 05:10:59 |       |       |
|   2 |   VIEW                               |                           |  1565K|    40M|       |  1535K  (1)| 05:07:07 |       |       |
|   3 |    HASH GROUP BY                     |                           |  1565K|    37M|  2792M|  1535K  (1)| 05:07:07 |       |       |
|   4 |     PARTITION RANGE ALL              |                           |    80M|  1926M|       |  1321K  (1)| 04:24:24 |     1 |1048575|
|   5 |      PARTITION HASH ALL              |                           |    80M|  1926M|       |  1321K  (1)| 04:24:24 |     1 |     4 |
|   6 |       TABLE ACCESS FULL              | TXN_DETL                  |    80M|  1926M|       |  1321K  (1)| 04:24:24 |     1 |1048575|
|   7 |   PARTITION RANGE ALL                |                           |  1231K|    70M|       | 12237   (1)| 00:02:27 |     1 |1048575|
|   8 |    PARTITION HASH ALL                |                           |  1231K|    70M|       | 12237   (1)| 00:02:27 |     1 |     4 |
|   9 |     TABLE ACCESS BY LOCAL INDEX ROWID| REGN_MAST                 |  1231K|    70M|       | 12237   (1)| 00:02:27 |     1 |1048575|
|  10 |      BITMAP CONVERSION TO ROWIDS     |                           |       |       |       |            |          |       |       |
|  11 |       BITMAP INDEX FULL SCAN         | IDX_REGN_MAST_7           |       |       |       |            |          |     1 |1048575|
------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("MOBILENUMBER"="TXNMOBILENUMBER"(+))

Note
-----
   - dynamic sampling used for this statement (level=11)

------------------------------------------------------------------------------------------------------------------------------------------------

此查询耗时超过 25 分钟。如何提高此查询的性能。

任何帮助将不胜感激!!!!!!

【问题讨论】:

您是否索引了连接操作中使用的列?为什么所有这些 TO_DATE / TO_CHAR?不能简化成sysdate - lasttxndate as "idle days"吗? 是的,我有手机号码和交易日期的索引 lasttxndate 是日期时间,所以我在计算前转换为日期 您应该索引 MOBILENUMBER 和 TXNMOBILENUMBER 列。如果 LASTTXNDATE 是“日期时间”,只需 TRUNC 它,即trunc(sysdate) - trunc(lasttxndate) 这里不需要索引。计算哈希时有两次全表扫描。然后散列连接两个表。如果两张桌子都很大,25 分钟是合理的时间。 【参考方案1】:

您的查询使用两个表中的所有数据,因此首选是使用FULL TABLE SCAN 检查执行计划。

记住FULL TABLE SCAN 很慢,但是从带有INDEX 的表中选择所有行要慢得多...

因此,您应该按以下方式制定执行计划:

------------------------------------------------------------------------------------------
| Id  | Operation            | Name      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |           |  1000K|    60M|       |   176K  (2)| 00:00:07 |
|*  1 |  HASH JOIN OUTER     |           |  1000K|    60M|    41M|   176K  (2)| 00:00:07 |
|   2 |   TABLE ACCESS FULL  | REGN_MAST |  1000K|    29M|       |  1370   (1)| 00:00:01 |
|   3 |   VIEW               |           |  1014K|    30M|       |   170K  (2)| 00:00:07 |
|   4 |    HASH GROUP BY     |           |  1014K|    16M|  1610M|   170K  (2)| 00:00:07 |
|   5 |     TABLE ACCESS FULL| TXN_DETL  |    60M|   972M|       | 49771   (1)| 00:00:02 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("MOBILENUMBER"="TXNMOBILENUMBER"(+))

根据您的硬件和内存配置,时间会有所不同,但在最近的硬件上,我预计时间会低于 10 分钟。

你可以进一步限制它使用

a) 并行查询

b) 保留保存最后交易日期的物化视图

这是我的测试,生成的数据需要 5 分钟以上(见下文)。 所以我的建议要么删除所有索引,要么提示 FULL 并重试。

SQL> set timi on
SQL> set autotrace traceonly
SQL> SELECT CUSTOMERNAME,MOBILENUMBER,ACCOUNTNUMBER,
  2     CUSTOMERID,LASTTXNDATE,
  3     FLOOR(SYSDATE - to_date(TO_CHAR(LASTTXNDATE, 'DD/MM/YYYY'),'DD/MM/YYYY')
) AS "IDLE DAYS"
  4  FROM REGN_MAST
  5  LEFT JOIN
  6   ( SELECT TXNMOBILENUMBER,MAX(TXNDT) AS LASTTXNDATE
  7     FROM TXN_DETL
  8     GROUP BY TXNMOBILENUMBER
  9   )
 10  ON MOBILENUMBER=TXNMOBILENUMBER;

1000000 rows selected.

Elapsed: 00:05:42.23

样本数据

create table REGN_MAST
as 
select 
'Name'||rownum CUSTOMERNAME,'00'||rownum MOBILENUMBER, 99*rownum ACCOUNTNUMBER, rownum CUSTOMERID 
from dual connect by level <= 1000000;

create table TXN_DETL
as 
with cust as (
select 
'00'||rownum TXNMOBILENUMBER 
from dual connect by level <= 1000000),
trans as (
select  DATE'2018-01-01' + rownum  TXNDT
from dual connect by level <= 60)
select TXNMOBILENUMBER, TXNDT
from cust CROSS join trans;  

【讨论】:

Marmite Bomber,谢谢,使用并行后执行时间减少到 37 分钟。是否可以进一步减少。因为全表扫描。仅注册就需要花费所有时间。是否可以通过提示加快全表扫描? @Madhesh 我只能推荐 - 在开发实例中使用生成的数据(与您的大小相同)执行我的示例。查看经过的时间和执行计划(在我的中等硬件中花费了 不到 6 分钟)。将此设置与您的生产(表大小、执行计划、运行时间)进行比较。【参考方案2】:

我会尝试将查询重写为:

SELECT m.CUSTOMERNAME, m.MOBILENUMBER, m.ACCOUNTNUMBER,
       m.CUSTOMERID, t.TXNDT, 
       FLOOR(SYSDATE - TRUNC(TXNDT)) AS IDLE_DAYS 
FROM REGN_MAST m JOIN 
     TXN_DETL t
     ON m.MOBILENUMBER = t.TXNMOBILENUMBER
WHERE t.TXNDT = (SELECT MAX(t2.TXNDT) FROM TXN_DETL t2 WHERE m.MOBILENUMBER = t2.TXNMOBILENUMBER);

然后,确保您在TXN_DETL(TXNMOBILENUMBER, TXNDT) 上有一个索引以提高性能。

假设所有客户都有交易,我将LEFT JOIN 更改为INNER JOIN

这也简化了日期运算。这与性能的关系不如可读性。

【讨论】:

如果仅限于少数客户,此查询可以正常工作。应用于所有 1M 客户的索引访问将导致一个小时的经过时间(假设:一半的索引块不在内存中;100 个磁盘访问/秒)。因此,Oracle 1) 忽略索引,2) 执行 HASH Join 全扫描两个表 3) 后跟 WINDOW SORT。 (我在 12.1.0.2.0 上使用合成数据进行的测试)。直觉建议和我的结果表明,HASH GROUP BY 执行 before 连接并仅连接两个 1M 行源 优于 WINDOW SORT 在完整的 60M 行源上产生于加入。 @MarmiteBomber 。 . .我不希望有 6000 万行和这两列的索引会溢出内存。整个索引应该很容易放入内存中。 @Gordon Linoff 谢谢,按照您的建议重写查询后,事务表使用索引,但注册表进行全面扫描并在执行计划中显示 229 小时。有什么办法可以避免注册中的全扫描【参考方案3】:

TXN_DETL(TXNMOBILENUMBER,TXNDT) 上创建覆盖索引。

根据执行计划,86% 的成本用于 TXN_DETL 上的全表扫描。如果所有相关列上都有索引,Oracle 可以将该索引用作瘦表。 INDEX FAST FULL SCAN 操作的运行速度可能比 TABLE ACCESS FULL 快得多。

【讨论】:

,创建覆盖索引后执行计划使用事务表的索引。但是Registration表全表扫描需要4个小时。有什么办法可以避免Registration中的全扫描。 4 小时扫描 100 万行似乎异常缓慢。也许时间花在了一个糟糕的连接上。我们不必猜测执行计划中的哪个操作很慢,这将有助于生成实时 SQL 监视器报告。该报告将准确地告诉我们操作需要多长时间,以便我们知道应该关注哪个部分。要生成报告,首先通过过滤 SQL_FULLTEXT 列在 GV$SQL 中找到 SQL_ID。然后运行此语句并将整个结果发布到问题中。 select dbms_sqltune.report_sql_monitor(sql_id =&gt; 'enter SQL_ID here', type =&gt; 'text') from dual;. 报告老化相对较快,因此您可能需要再次运行原始语句以生成监控数据。 非常感谢,我将生成 sql 监控报告并使用该报告更新问题

以上是关于Oracle加入百万记录表会降低查询性能的主要内容,如果未能解决你的问题,请参考以下文章

Oracle数据库查询优化方案(处理上百万级记录如何提高处理查询速度)

mysql百万数据分页查询4秒,求教怎么优化

oracle数据库,搜索百万级别数据分页优化问题

SQL性能优化(Oracle)

Oracle:如何通过将子查询删除到条件或加入同一张表来提高查询?

MySQL 百万级/千万级表 总记录数查询