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 => 'enter SQL_ID here', type => 'text') from dual;
.
报告老化相对较快,因此您可能需要再次运行原始语句以生成监控数据。
非常感谢,我将生成 sql 监控报告并使用该报告更新问题以上是关于Oracle加入百万记录表会降低查询性能的主要内容,如果未能解决你的问题,请参考以下文章
Oracle数据库查询优化方案(处理上百万级记录如何提高处理查询速度)