在生产库看到这样一条sql,没有走索引。
10:26:05 SQL> EXPLAIN plan FOR
10:27:06 2 SELECT thread_id
10:27:06 3 FROM (SELECT thread_id, rownum rn
10:27:06 4 FROM (SELECT b.thread_id
10:27:06 5 FROM test_blog b
10:27:06 6 WHERE b.STATUS != -1
10:27:06 7 ORDER BY b.gmt_modified DESC)
10:27:06 8 WHERE rownum <= :1)
10:27:06 9 WHERE rn >= :2;
PLAN_TABLE_OUTPUT
---------------------------------
-------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost |
-------------------------------------------
| 0 | SELECT STATEMENT | | 1429K| 35M| | 6123 |
|* 1 | VIEW | | 1429K| 35M| | 6123 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | VIEW | | 1429K| 17M| | 6123 |
|* 4 | SORT ORDER BY STOPKEY| | 1429K| 23M| 76M| 6123 |
|* 5 | TABLE ACCESS FULL | TEST_BLOG | 1429K| 23M| | 798 |
-----------------------------------
在status、gmt_modified、thread_id上有个组合索引IDX_BLOG_STATUS,大家都知道”不等于”是不会用到index,分析了一下业务,完全可以把status != -1改为status = 0,这里我还多考虑了一步,加个hint,固定它走IDX_BLOG_STATUS。
看一下加hint后的执行计划,感觉没有什么大的问题
10:34:04 SQL> EXPLAIN plan FOR
10:34:20 2 SELECT thread_id
10:34:20 3 FROM (SELECT /*+ index(b IDX_BLOG_STATUS) */thread_id, rownum rn
10:34:20 4 FROM (SELECT b.thread_id
10:34:20 5 FROM test_blog b
10:34:20 6 WHERE b.STATUS = 0
10:34:20 7 ORDER BY b.gmt_modified DESC)
10:34:20 8 WHERE rownum <= 500)
10:34:20 9 WHERE rn >= 450;
Explained.
Elapsed: 00:00:00.00
10:34:21 SQL> SELECT * FROM TABLE(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------
----------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
----------------------------------------------
| 0 | SELECT STATEMENT | | 500 | 13000 | 5174 |
|* 1 | VIEW | | 500 | 13000 | 5174 |
|* 2 | COUNT STOPKEY | | | | |
| 3 | VIEW | | 1429K| 17M| 5174 |
|* 4 | INDEX RANGE SCAN DESCENDING| IDX_BLOG_STATUS | 1429K| 23M| 12935 |
----------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
--------------------------
1 - filter("from$_subquery$_001"."RN">=450)
2 - filter(ROWNUM<=500)
4 - access("B"."STATUS"=0)
Note: cpu costing IS off
19 rows selected.
Elapsed: 00:00:00.08
10:34:25 SQL> SET autot traceonly
10:35:33 SQL> SELECT thread_id
10:35:35 2 FROM (SELECT thread_id, rownum rn
10:35:35 3 FROM (SELECT /*+index( b IDX_BLOG_STATUS)*/b.thread_id
10:35:35 4 FROM test_blog b
10:35:35 5 WHERE b.STATUS =0
10:35:35 6 ORDER BY b.gmt_modified DESC)
10:35:35 7 WHERE rownum <= 500)
10:35:35 8 WHERE rn >= 450;
51 rows selected.
Elapsed: 00:00:52.97
Execution Plan
----------------------------------------
SELECT STATEMENT Optimizer=CHOOSE (Cost=10499 Card=500 Bytes=13000)
0 VIEW (Cost=10499 Card=500 Bytes=13000)
1 COUNT (STOPKEY)
2 VIEW (Cost=10499 Card=1429964 Bytes=18589532)
3 SORT (ORDER BY STOPKEY) (Cost=10499 Card=1429964 Bytes=24309388)
4 INDEX(RANGE SCAN) OF 'IDX_BLOG_STATUS' (NON-UNIQUE)(Cost=12935Card=1429964Bytes=24309388)
Statistics
------------------------------
0 recursive calls
0 db block gets
43836 consistent gets
43058 physical reads
7988 redo size
1530 bytes sent via SQL*Net TO client
689 bytes received via SQL*Net FROM client
5 SQL*Net roundtrips TO/FROM client
1 sorts (memory)
0 sorts (disk)
51 rows processed
实际走得就有问题了,多了个sort
hint加得有点问题,改成/*+ index_desc( b IDX_BLOG_STATUS) */,因为都能在索引中完成(包括sort),不用回表。
10:47:18 SQL> SELECT thread_id
10:47:19 2 FROM (SELECT thread_id, rownum rn
10:47:19 3 FROM (SELECT /*+index_desc( b IDX_BLOG_STATUS)*/b.thread_id
10:47:19 4 FROM test_blog b
10:47:19 5 WHERE b.STATUS =0
10:47:19 6 ORDER BY b.gmt_modified DESC)
10:47:19 7 WHERE rownum <= 500)
10:47:19 8 WHERE rn >= 450;
51 rows selected.
Elapsed: 00:00:06.81
Execution Plan
------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=5174 Card=500 Bytes=13000)
1 0 VIEW (Cost=5174 Card=500 Bytes=13000)
2 1 COUNT (STOPKEY)
3 2 VIEW (Cost=5174 Card=1429964 Bytes=18589532)
4 3 INDEX (RANGE SCAN DESCENDING) OF 'IDX_BLOG_STATUS' (NON-UNIQUE)
(Cost=12935 Card=1429964 Bytes=24309388)
Statistics
-----------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1529 bytes sent via SQL*Net TO client
689 bytes received via SQL*Net FROM client
5 SQL*Net roundtrips TO/FROM client
0 sorts (memory)
0 sorts (disk)
51 rows processed
逻辑读从43836下降到10个。
总结:explain plan也不可信呀,关键还是要看实际跑得怎么样!