还是时间惹的祸
還是時間惹的禍,今天看到玄爺的db周報中一條sql的邏輯讀很高,執行量每小時也有幾千次,瞟了一眼sql,發現了異常情況,那就是t.gmt_create >= :2 ? and t.gmt_create <= :3,此時我想這條sql應該有救了。
先說一下知識點吧,java.sql定義時間類型包括三個類,date, time, 和 timestamp,分別用來表示日期(無時間信息,eg: yyyy-mm-dd),時間(只處理時間,無日期部分, eg: hh:mm:ss)和時間戳(精確到納秒級別)。在它們都繼承自java.util.date。
而oracle與jdbc之間的類型映射
date ?java.sql.date
date ?java.sql.time
timestamp ? ?java.sql.timestamp
oracle數據庫字段類型主要有date、timestamp。
在9i以后、11g以前的oracle jdbc驅動中存在一個會丟失date類型字段的時間信息的bug,原因是其jdbc驅動將oracle的date類型處理為java.sql.date 類型,這就丟失了時間部分;事實上,如果是使用ibatis,pojo屬性的類型設置為java.util.date,確保 jdbctype不為 date或者time,則避免了這個bug。因為此時ibatis會以java.sql.timestamp來處理該字段;
簡而言之,oracle在處理ibatis傳入到數據庫的變量時候,解析為timestamp類型,如果數據庫中時間字段定義為date類型,由于timestamp類型的精度比date類型的精度高,所以oracle會做出隱身轉換,將date類型轉換為timestamp類型,那么創建在date類型上的索引將不會使用到;
select count(*) ?from order t ?where (o_type = 2 or o_type = 3) ? and p_type = 1 ? and t.o_status = :1 ? and t.gmt_create >= :2 ? and t.gmt_create <= :3 ? ?and t.m_type = :4 ? and t.a_id = :5 ? and t.a_item_id = :6
——————————————————————————————
| operation ? ? ? ? ? ? ? ? ? ? ?| phv/object name ? ? ? ? ? ? ? | ?rows | bytes| ? cost |
——————————————————————————————
|select statement ? ? ? ? ? ? ? ?|———- 1348628516 ———| ? ? ? | ? ? ?| ? ? ?3 |
|sort aggregate ? ? ? ? ? ? ? ? ?| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? 74 | ? ? ? ?|
| filter?| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? | ? ? ?| ? ? ? ?|
| ?index range scan ? ? ? ? ? ? ?|ind_o_artid ? ? ? ? ? ?| ? ? 1 | ? 74 | ? ? ?3 |
——————————————————————————————
索引idx_artid(a_id, p_type, gmt_create, o_type, m_type, a_item_id, o_status)
跟蹤一下綁定變量的值:
child_number ? position name ?datatype_string ? max_length ? last_captured ? value_string
5 ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1 ? ? ? ? ? :1 ? ? ? ? number ? ? ? ? ? ? ? ? ? ?22 ? ? ? ? 2011-04-25 16:30:07 ? ? ? 3
5 ? ? ? ? ? ? ? ? ? ? ? ? ? ?2 ? ? ? ? ? ?:2 ? ?timestamp?11 ? ? ? ? 2011-04-25 16:30:07
5 ? ? ? ? ? ? ? ? ? ? ? ? ? 3 ? ? ? ? ? ? :3 ? ?timestamp?11 ? ? ? ?2011-04-25 16:30:07
5 ? ? ? ? ? ? ? ? ? ? ? ? ?4 ? ? ? ? ? ? ?:4 ? ?number ? ? ? ? ? ? ? ? ? ? ? 22 ? ? ?2011-04-25 16:30:07 ? ? ? ? ?6
5 ? ? ? ? ? ? ? ? ? ? ? ? ?5 ? ? ? ? ? ? ?:5 ? ?number ? ? ? ? ? ? ? ? ? ? ? ?22 ? ? ?2011-04-25 16:30:07 ? ? ? ? 940
5 ? ? ? ? ? ? ? ? ? ? ? ? ?6 ? ? ? ? ? ? ? :6 ? ?number ? ? ? ? ? ? ? ? ? ? ? 22 ? ? ? 2011-04-25 16:30:07 ? ? ?1791
可以看到oracle將gmt_create綁定為timestamp, 奇怪的是我們沒有在value_string中看到有gmt_create的值;從執行計劃上我們看到filter,原表中gmt_create為date數據類型,這里可以斷定是由于隱式轉換導致了時間字段在索引中排序沒有作用了,所以還要過濾filter,在索引中的gmt_create字段沒有起到過濾的作用,從而導致了大量的邏輯讀;
驗證如下:
11:24:02 bss2@ crm>select /*+index(t idx_artid)*/ count(*)
11:24:25 ? 2 ? ?from order t
11:24:25 ? 3 ? where (o_type = 2 or o_type = 3)
11:24:25 ? 4 ? ? and p_type = 1
11:24:25 ? 5 ? ? and t.o_status = 3
11:24:25 ? 6 ? ? and t.gmt_create >= ‘2011-04-10 11:20:22’
11:24:25 ? 7 ? ? and t.gmt_create <= ‘2011-04-13 11:20:22’—變量值傳入時間的字符串
11:24:25 ? 8 ? ? and t.m_type = 6
11:24:25 ? 9 ? ? and t.a_id = 965
11:24:25 ?10 ? ? and t.a_item_id =1863;
1 row selected.
elapsed: 00:00:00.01
execution plan
———————————————————
plan hash value: 3276776702
—————————————————————————————–
| id ?| operation ? ? ? ? | name ? ? ? ? ? ? ? ?| rows ?| bytes | cost (%cpu)| time ? ? |
—————————————————————————————–
| ? 0 | select statement ?| ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? ?74 | ? ? 2 ? (0)| 00:00:01 |
| ? 1 | ?sort aggregate ? | ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? ?74 | ? ? ? ? ? ?| ? ? ? ? ?|
|* ?2 | ? index range scan| idx_artid | ? ? 1 | ? ?74 | ? ? 2 ? (0)| 00:00:01 |
—————————————————————————————–
predicate information (identified by operation id):
—————————————————
2 – access(“t”.”a_id”=965 and “p_type”=1 and
“t”.”gmt_create”>=to_date(‘ 2011-04-10 11:20:22’, ‘yyyy-mm-dd hh24:mi:ss’) and
“t”.”m_type”=6 and “t”.”a_item_id”=1863 and “t”.”o_status”=3 and
“t”.”gmt_create”<=to_date(‘ 2011-04-13 11:20:22’, ‘yyyy-mm-dd hh24:mi:ss’))
filter((“o_type”=2 or “o_type”=3) and “t”.”m_type”=6 and
“t”.”a_item_id”=1863 and “t”.”o_status”=3)
statistics
———————————————————-
1 ?recursive calls
0 ?db block gets
6 ?consistent gets
0 ?physical reads
0 ?redo size
11:28:11 bss2@ crm>select /*+index(t idx_artid)*/ count(*)
11:28:12 ? 2 ? ?from order t
11:28:12 ? 3 ? where (o_type = 2 or o_type = 3)
11:28:12 ? 4 ? ? and p_type = 1
11:28:12 ? 5 ? ? and t.o_status = 3
11:28:12 ? 6 ? ? and t.gmt_create >= to_date(‘2011-04-10 11:20:22′,’yyyy-mm-dd hh24:mi:ss’)
11:28:12 ? 7 ? ? and t.gmt_create <= to_date(‘2011-04-13 11:20:22′,’yyyy-mm-dd hh24:mi:ss’)–用to_date轉換一下
11:28:12 ? 8 ? ? and t.m_type = 6
11:28:12 ? 9 ? ? and t.a_id = 965
11:28:12 ?10 ? ? and t.a_item_id =1863;
1 row selected.
elapsed: 00:00:00.01
execution plan
———————————————————-
plan hash value: 3276776702
—————————————————————————————–
| id ?| operation ? ? ? ? | name ? ? ? ? ? ? ? ?| rows ?| bytes | cost (%cpu)| time ? ? |
—————————————————————————————–
| ? 0 | select statement ?| ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? ?74 | ? ? 2 ? (0)| 00:00:01 |
| ? 1 | ?sort aggregate ? | ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? ?74 | ? ? ? ? ? ?| ? ? ? ? ?|
|* ?2 | ? index range scan| idx_artid | ? ? 1 | ? ?74 | ? ? 2 ? (0)| 00:00:01 |
—————————————————————————————–
predicate information (identified by operation id):
—————————————————
2 – access(“t”.”a_id”=965 and “p_type”=1 and
“t”.”gmt_create”>=to_date(‘ 2011-04-10 11:20:22’, ‘syyyy-mm-dd hh24:mi:ss’) and
“t”.”m_type”=6 and “t”.”a_item_id”=1863 and “t”.”o_status”=3 and
“t”.”gmt_create”<=to_date(‘ 2011-04-13 11:20:22’, ‘syyyy-mm-dd hh24:mi:ss’))
filter((“o_type”=2 or “o_type”=3) and “t”.”m_type”=6 and
“t”.”a_item_id”=1863 and “t”.”o_status”=3)
statistics
———————————————————-
0 ?recursive calls
0 ?db block gets
6 ?consistent gets
0 ?physical reads
0 ?redo size
11:24:26 bss2@ crm>select /*+index(t idx_artid)*/ count(*)
11:26:31 ? 2 ? ?from order t
11:26:31 ? 3 ? where (o_type = 2 or o_type = 3)
11:26:31 ? 4 ? ? and p_type = 1
11:26:31 ? 5 ? ? and t.o_status = 3
11:26:31 ? 6 ? ? and t.gmt_create >= to_timestamp(‘2011-04-10 11:20:22′,’yyyy-mm-dd hh24:mi:ss’)
11:26:31 ? 7 ? ? and t.gmt_create <= to_timestamp(‘2011-04-13 11:20:22′,’yyyy-mm-dd hh24:mi:ss’)–模仿沒有經過處理的變量值,ibatis轉換為timestamp
11:26:31 ? 8 ? ? and t.m_type = 6
11:26:31 ? 9 ? ? and t.a_id = 965
11:26:31 ?10 ? ? and t.a_item_id =1863;
1 row selected.
elapsed: 00:00:00.20
execution plan
———————————————————-
plan hash value: 2438882768
——————————————————————————————
| id ?| operation ? ? ? ? ?| name ? ? ? ? ? ? ? ?| rows ?| bytes | cost (%cpu)| time ? ? |
——————————————————————————————
| ? 0 | select statement ? | ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? ?74 | ? ? 3 ? (0)| 00:00:01 |
| ? 1 | ?sort aggregate ? ?| ? ? ? ? ? ? ? ? ? ? | ? ? 1 | ? ?74 | ? ? ? ? ? ?| ? ? ? ? ?|
|* ?2 | ? filter ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? | ? ? ? | ? ? ? | ? ? ? ? ? ?| ? ? ? ? ?|
|* ?3 | ? ?index range scan| idx_artid | ? ? 1 | ? ?74 | ? ? 3 ? (0)| 00:00:01 |
——————————————————————————————
predicate information (identified by operation id):
—————————————————
2 – filter(to_timestamp(‘2011-04-10 11:20:22′,’yyyy-mm-dd
hh24:mi:ss’)<=to_timestamp(‘2011-04-13 11:20:22′,’yyyy-mm-dd hh24:mi:ss’))
3 – access(“t”.”a_id”=965 and “p_type”=1 and “t”.”m_type”=6 and
“t”.”a_item_id”=1863 and “t”.”o_status”=3)
filter((“o_type”=2 or “o_type”=3) and “t”.”m_type”=6 and
“t”.”a_item_id”=1863 and internal_function(“t”.”gmt_create”)>=to_timestamp(‘
2011-04-10 11:20:22′,’yyyy-mm-dd hh24:mi:ss’) and
internal_function(“t”.”gmt_create”)<=to_timestamp(‘2011-04-13
11:20:22′,’yyyy-mm-dd hh24:mi:ss’) and “t”.”o_status”=3)
statistics
———————————————————-
1 ?recursive calls
0 ?db block gets
698 ?consistent gets
0 ?physical reads
0 ?redo size
可以看到當我們采用to_timestatmp轉換gmt_create傳入的值后,其執行計劃是和沒有做任何處理查詢變量的是一樣的,
所以接下來的兩種方案可以為:
使用cast(:2 as date)轉換一下;
變量定義為字符串,在采用to_date(:2,’yyyy-mm-dd hh24:mi:ss’);
程序中綁定為date:gmt_create<#2:date#;
結論:在處理時間類型的時候,我們需要特別的謹慎,避免隱式轉換的發生.
總結
- 上一篇: 设计模式之Composite模式(笔记)
- 下一篇: Apache Velocity官方指南-