今天开发的同事下午反馈给我一个问题,说有操作直接卡住了,听这个描述,感觉很可能是查询慢了。
于是连接到环境中,查看了一下正在执行的sql语句情况,发现下面的语句已经执行了一段时间。
语句类似下面的形式:
select t1.SECURITY_PHONE as MOBILE_PHONE, t1.SECURITY_EMAIL as OTHER_EMAIL,
t2.* from accstat.ACCOUNT_DELTA t1, bidata.TMP_CN06 t2
where t1.CN_MASTER = t2.CN
其实对于这个查询,看起来条件也蛮简单的,但是为什么查询慢呢。
首先得了解一下这个问题的背景。
目前的这个库是一个统计库,库里的数据是从账号库中分库分表的12个用户中得来,就如同左边所示,是放在了4个分库,12个用户中,表名都是account_delta
目前采用是物化视图的增量刷新来实现,使得数据能够每天按时增量刷新到统计库中。统计库中也存在一套类似的结构,也是12个相似的表,不过在统计库中为了增量刷新我们采用了物化视图。
然后对外是使用一个account_delta的视图来实现。
所以现在的情况是account_delta和另外一个临时表关联,则实际意味着实际上是12个物化视图和1个表在关联。
那么到底慢在哪里了,我们来看看执行计划,可以看到12个物化视图都毫无例外走了全表扫描。当然整个执行计划的消耗那是非常惊人的。
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 622M| 95G| | 7160K (1)| 23:52:02 |
|* 1 | HASH JOIN | | 622M| 95G| 2056K| 7160K (1)| 23:52:02 |
| 2 | TABLE ACCESS FULL | TMP_CN06 | 80953 | 1106K| | 2294 (1)| 00:00:28 |
| 3 | VIEW | ACCOUNT_DELTA | 620M| 87G| | 2357K (2)| 07:51:25 |
| 4 | UNION-ALL | | | | | | |
| 5 | MAT_VIEW ACCESS FULL| ACC00_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:17 |
| 6 | MAT_VIEW ACCESS FULL| ACC02_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:18 |
| 7 | MAT_VIEW ACCESS FULL| ACC04_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:17 |
| 8 | MAT_VIEW ACCESS FULL| ACC11_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:18 |
| 9 | MAT_VIEW ACCESS FULL| ACC13_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:17 |
| 10 | MAT_VIEW ACCESS FULL| ACC15_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:18 |
| 11 | MAT_VIEW ACCESS FULL| ACC20_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:17 |
| 12 | MAT_VIEW ACCESS FULL| ACC22_ACCOUNT_DELTA | 47M| 6880M| | 196K (2)| 00:39:16 |
| 13 | MAT_VIEW ACCESS FULL| ACC24_ACCOUNT_DELTA | 52M| 7200M| | 196K (2)| 00:39:18 |
| 14 | MAT_VIEW ACCESS FULL| ACC31_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:18 |
| 15 | MAT_VIEW ACCESS FULL| ACC33_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:18 |
| 16 | MAT_VIEW ACCESS FULL| ACC35_ACCOUNT_DELTA | 52M| 7201M| | 196K (2)| 00:39:17 |
PLAN_TABLE_OUTPUT
---------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T1"."CN_MASTER"="T2"."CN")
Note
-----
- dynamic sampling used for this statement (level=2)
初步怀疑是索引导致的,但是发现两个表中的cn字段索引都存在。
然后继续查看发现了一个不同之处。TMP_CN06中的字段cn是varchar2(70),而account_delta中的cn_master是varchar2(50),感觉这里似乎有点关联,但是自己实在是想不出到底哪里可能有问题,于是把TMP_CN06中的字段cn改为了varchar2(50),其实内容是在varchar2(50)之内的。但是改了之后查看执行计划还是没有任何改善,还是全表扫描。
这个时候问题催的也非常着急,这个时候也在犹豫是不是因为多个物化视图导致了这个问题。
为了尽快修复问题,一边排查一遍开始准备复制一份数据来,表中的数据量非常大,最后开了并行的复制。最后还是一个ora错误收场。这个时候时间又过去了十多分钟。
create table accstat.ACCOUNT_DELTA_ALL as select *from accstat.ACCOUNT_DELTA
*
ERROR at line 1:
ORA-12801: error signaled in parallel query server P010
ORA-01652: unable to extend temp segment by 8192 in tablespace ACCSTAT_DATA
Elapsed: 00:16:14.85
这个时候尝试分片思想。把第二个分片的数据导入表中,大概持续了8分钟左右。不过按照这个速度还是有很大的差距。剩下的11个分片数据量都不小。
SQL> insert into accstat.ACCOUNT_DELTA_all select *from ACCSTAT.ACC02_ACCOUNT_DELTA ;
commit;
52074945 rows created.
Elapsed: 00:08:07.24
好了,我们还是放弃这种数据复制的方法,开始琢磨到底能不能做点什么。
继续分片,拿出一个分片和表TMP_CN06关联,然后查看执行计划,发现这个时候就走了索引扫描,而且执行的代价也小了很多。
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------
Plan hash value: 3717601510
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 80953 | 12M| 26604 (1)| 00:05:20 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 80953 | 12M| 26604 (1)| 00:05:20 |
| 3 | TABLE ACCESS FULL | TMP_CN06 | 80953 | 1106K| 2294 (1)| 00:00:28 |
|* 4 | INDEX RANGE SCAN | ACC00_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 5 | MAT_VIEW ACCESS BY INDEX ROWID| ACC00_ACCOUNT_DELTA | 1 | 151 | 1 (0)| 00:00:01 |
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("T1"."CN_MASTER"="T2"."CN")
Note
-----
- dynamic sampling used for this statement (level=2)
好了,这些尝试都做完了,我们来看看末尾的dynamic sampling的情况,一般的物化视图可能我们也就是纯粹为了增量刷新,也基本没有动过统计信息。我采用了下面的方式来收集统计信息。
exec DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'ACCSTAT', TABNAME =>'ACC04_ACCOUNT_DELTA' ,CASCADE =>TRUE,METHOD_OPT=>'FOR ALL INDEXED COLUMNS SIZE 1',DEGREE =>4, GRANULARITY =>'ALL');
剩下的11个都是如法炮制,操作很快就完成了。
那么等我做完11个之后,再次查看执行计划还是全表扫描,还是提示dynamic sampling。直到我收集完全之后,再次查看执行计划。就变成了下面的形式。
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 949K| 143M| 1169K (1)| 03:53:49 |
| 1 | NESTED LOOPS | | 949K| 143M| 1169K (1)| 03:53:49 |
| 2 | TABLE ACCESS FULL | TMP_CN06 | 80953 | 1106K| 2294 (1)| 00:00:28 |
| 3 | VIEW | ACCOUNT_DELTA | 1 | 145 | 14 (0)| 00:00:01 |
| 4 | UNION ALL PUSHED PREDICATE | | | | | |
| 5 | MAT_VIEW ACCESS BY INDEX ROWID| ACC00_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | ACC00_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 7 | MAT_VIEW ACCESS BY INDEX ROWID| ACC02_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN | ACC02_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 9 | MAT_VIEW ACCESS BY INDEX ROWID| ACC04_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 10 | INDEX RANGE SCAN | ACC04_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 11 | MAT_VIEW ACCESS BY INDEX ROWID| ACC11_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 12 | INDEX RANGE SCAN | ACC11_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 13 | MAT_VIEW ACCESS BY INDEX ROWID| ACC13_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 14 | INDEX RANGE SCAN | ACC13_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 15 | MAT_VIEW ACCESS BY INDEX ROWID| ACC15_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 16 | INDEX RANGE SCAN | ACC15_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 17 | MAT_VIEW ACCESS BY INDEX ROWID| ACC20_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 18 | INDEX RANGE SCAN | ACC20_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 19 | MAT_VIEW ACCESS BY INDEX ROWID| ACC22_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 20 | INDEX RANGE SCAN | ACC22_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 21 | MAT_VIEW ACCESS BY INDEX ROWID| ACC24_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 22 | INDEX RANGE SCAN | ACC24_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 23 | MAT_VIEW ACCESS BY INDEX ROWID| ACC31_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 24 | INDEX RANGE SCAN | ACC31_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 25 | MAT_VIEW ACCESS BY INDEX ROWID| ACC33_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 26 | INDEX RANGE SCAN | ACC33_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
| 27 | MAT_VIEW ACCESS BY INDEX ROWID| ACC35_ACCOUNT_DELTA | 1 | 145 | 1 (0)| 00:00:01 |
|* 28 | INDEX RANGE SCAN | ACC35_IND_CCMNN | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("CN_MASTER"="T2"."CN")
8 - access("CN_MASTER"="T2"."CN")
10 - access("CN_MASTER"="T2"."CN")
12 - access("CN_MASTER"="T2"."CN")
14 - access("CN_MASTER"="T2"."CN")
16 - access("CN_MASTER"="T2"."CN")
18 - access("CN_MASTER"="T2"."CN")
20 - access("CN_MASTER"="T2"."CN")
22 - access("CN_MASTER"="T2"."CN")
24 - access("CN_MASTER"="T2"."CN")
26 - access("CN_MASTER"="T2"."CN")
28 - access("CN_MASTER"="T2"."CN")
虽然看起来似乎会有些冗长,不过总体来看还是不错的。因为我们确实需要TMP_CN06走全表扫描。
那么我们再次尝试这个过程,时间就变为了惊人的3秒。TMP_CN06表中有近10万的记录,也没有走并行。
create table test_201551214 as select t1.SECURITY_PHONE as MOBILE_PHONE, t1.SECURITY_EMAIL as OTHER_EMAIL,
* t2.* from accstat.ACCOUNT_DELTA t1, bidata.TMP_CN06 t2 where t1.CN_MASTER = t2.CN;
Table created.
Elapsed: 00:00:03.27 所以从这个程度来看,物化视图堆叠起来的视图性能其实也差不了,用不好就会感觉差。也算是对物化视图的一个重新认识吧。
这个问题其实之前有同事反馈过,当时也是思路全在物化视图日志上下功夫了,准备解析物化视图日志来做一个merge的操作,最后也是无功而返,也对物化视图的操作产生了一些误解,看来这种情况下,性能也照样差不了。我已经试过水了,所以这种情况还是值得推广的。