索引过滤性不好导致SQL异常缓慢
刚刚监控到一条SQL占用了大量的I/O,SQL如下:
SELECT ORD_ID,
UNT_PRD_ID,
ORD_QTY,
STKO_WH_NO,
ORD_WH_NO,
ORD_MDA_CD,
STOCK_MDA_CD
from (SELECT ORD_ID,
UNT_PRD_ID,
SUM(ORD_QTY) ORD_QTY,
STKO_WH_NO,
ORD_WH_NO,
ORD_MDA_CD,
STOCK_MDA_CD
FROM ORD_ORD_DTL_D T
WHERE ORD_STS_CD = :1
AND ORD_PTR_CD = '100'
AND UNT_PRD_ID = :2
AND ORD_ACP_DTM >= TO_DATE(:3, 'YYYY-MM-DD HH24:MI:SS')
GROUP BY ORD_ID,
UNT_PRD_ID,
STKO_WH_NO,
STOCK_MDA_CD,ORD_WH_NO,
ORD_MDA_CD)
ORDER BY ORD_ID
这是一张分区表,ORD_ACP_DTM就是date类型的分区字段,看这条SQL的执行计划,并没什么太大的问题。
SQL> explain plan for
2 SELECT ORD_ID,
3 UNT_PRD_ID,
4 ORD_QTY,
5 STKO_WH_NO,
6 ORD_WH_NO,
7 ORD_MDA_CD,
8 STOCK_MDA_CD
9 from (SELECT ORD_ID,
10 UNT_PRD_ID,
11 SUM(ORD_QTY) ORD_QTY,
12 STKO_WH_NO,
13 ORD_WH_NO,
14 ORD_MDA_CD,
15 STOCK_MDA_CD
16 FROM ORD_ORD_DTL_D T
17 WHERE ORD_STS_CD = :1
18 AND ORD_PTR_CD = '100'
19 AND UNT_PRD_ID = :2
20 AND ORD_ACP_DTM >= TO_DATE(:3, 'YYYY-MM-DD HH24:MI:SS')
21 GROUP BY ORD_ID,
22 UNT_PRD_ID,
23 STKO_WH_NO,
24 STOCK_MDA_CD,ORD_WH_NO,
25 ORD_MDA_CD)
26 ORDER BY ORD_ID
27 ;
Explained.
SQL> set lines 200
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------
Plan hash value: 67455230
------------------------------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost (%CPU)|Time |Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | 4| 224| 3696 (1)|00:00:45| | |
| 1| SORT GROUP BY | | 4| 224| 3696 (1)|00:00:45| | |
| 2| PARTITION RANGE ITERATOR | | 4| 224| 3695 (1)|00:00:45| KEY |1048575|
|*3| TABLE ACCESS BY LOCAL INDEX ROWID|ORD_ORD_DTL_D | 4| 224| 3695 (1)|00:00:45| KEY |1048575|
|*4| INDEX RANGE SCAN |IX_ORD_ORD_DTL_D_16|1152| | 3054 (1)|00:00:37| KEY |1048575|
-------------------------------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("ORD_STS_CD"=:1 AND "ORD_PTR_CD"='100' AND "ORD_ACP_DTM">=TO_DATE(:3,'YYYY-MM-DD HH24:MI:SS'))
4 - access("UNT_PRD_ID"=:2)
17 rows selected.
这里面使用了IX_ORD_ORD_DTL_D_16这个索引,可是从监控看,这条SQL使用了IX_ORD_ORD_DTL_D_06索引,下面查看一下这两个索引的信息。
SQL> select INDEX_NAME,COLUMN_NAME,COLUMN_POSITION from user_ind_columns where index_name in ('IX_ORD_ORD_DTL_D_16','IX_ORD_ORD_DTL_D_06') order by 1,3;
INDEX_NAME COLUMN_NAME COLUMN_POSITION
------------------------------ ------------------------------ ---------------
IX_ORD_ORD_DTL_D_06 ORD_ACP_DTM 1
IX_ORD_ORD_DTL_D_06 ORD_STS_CD 2
IX_ORD_ORD_DTL_D_06 ORD_DTL_STS_CD 3
IX_ORD_ORD_DTL_D_16 UNT_PRD_ID 1
IX_ORD_ORD_DTL_D_16 UNT_SEQ 2
SQL> select segment_name,sum(bytes)/1024/1024/1024 from user_segments where segment_name in ('IX_ORD_ORD_DTL_D_06','IX_ORD_ORD_DTL_D_16') group by segment_name;
SEGMENT_NAME SUM(BYTES)/1024/1024/1024
---------------------------------- -------------------------
IX_ORD_ORD_DTL_D_16 3.65283203
IX_ORD_ORD_DTL_D_06 4.8057251
可以看到这两个都是复合索引,还都不小(因为是分区表的LOCAL索引,每个分区上的索引都不大),因为我对业务还算熟悉,除了时间字段,其他字段基本都是记录标识信息的字段,重复度相当的高,过滤性极差。
SQL> select num_rows from user_tables where table_name='ORD_ORD_DTL_D'; NUM_ROWS ---------- 72778422
上面的查询可以看到,数据库大概有7千多万的记录,再看下这些索引列的重复度,这里看几个关键的列就可以。
SQL> select COLUMN_NAME,NUM_DISTINCT from user_tab_columns where TABLE_NAME='ORD_ORD_DTL_D' and column_name in
('ORD_STS_CD','ORD_DTL_STS_CD','UNT_PRD_ID');
COLUMN_NAME NUM_DISTINCT
------------------------------ ------------
ORD_STS_CD 12
ORD_DTL_STS_CD 19
UNT_PRD_ID 64300
是不是很坑爹,监控发现这条SQL根据ORD_ACP_DTM和ORD_STS_CD字段的过滤条件,选择了IX_ORD_ORD_DTL_D_06这个包含ORD_ACP_DTM、ORD_STS_CD、ORD_DTL_STS_CD三个字段的复合索引,关键是ORD_STS_CD只有12个不同的值,过滤性极差,而且这个SQL执行频率还很高,好在当我发现的时候,这条SQL已经选择了过滤性相对好一点的IX_ORD_ORD_DTL_D_16索引。造成这种情况的原因可能是SQL的WHERE条件的时间跨度较大或者是ORD_STS_CD字段的值过滤性相对较好导致的。
数据库存在很多垃圾索引,我们之前通过索引监控,数据库业务用户一共1400多的索引,一年多的时候,有800多一次没有用到过,除去这800多的垃圾索引,还有一些像IX_ORD_ORD_DTL_D_06这样被使用过,但是会严重降低SQL性能的不应该存在索引,这些索引都是业务上线的时候,领导要求创建的,我们向上面反映过这些问题,也遇到过这样因为选择错误的索引导致SQL性能严重下降的问题,但领导不同意,我们也不敢去删,我们只能利用11g的新特性将这样的索引设置成不可用状态,这样索引还是会随着数据的变化而维护,需要的时候在设置回来就行,因为索引会随着数据的变化而维护,不但占用磁盘空间,还会影响数据库的性能,索引也是会有热点块的,也是会有竞争的。