一條簡單的日期範圍查詢,明明有索引卻走全表掃描?罪魁禍首是 Oracle 9.2+ 引入 TIMESTAMP 類型後,JDBC 驅動默認將 java.util.Date 綁定為 TIMESTAMP,觸發隱式轉換 TO_TIMESTAMP(date_col) = :bind,導致 CBO 放棄索引。本文深度解析機制並給出三大解決方案。
-- 表結構
CREATE TABLE t1 (end_date DATE);
CREATE INDEX idx_t1_end_date ON t1(end_date);
-- Java 代碼
PreparedStatement ps = conn.prepareStatement(
"SELECT COUNT(*) FROM t1 WHERE end_date >= ? AND end_date <= ?");
ps.setTimestamp(1, startDate); // java.util.Date
ps.setTimestamp(2, endDate);
執行計劃:TABLE ACCESS FULL(預期 INDEX RANGE SCAN)
DATE,setTimestamp 綁定類型為 DATE。TIMESTAMP(納秒精度),驅動為「精度更高」默認綁定為 TIMESTAMP。TO_TIMESTAMP(end_date) = :bind_ts。| 方案 | 實施方式 | 優點 | 缺點/前提 |
|---|---|---|---|
| 1. 升級驅動 + 11g+ | 使用 ojdbc6/7/8 + 11g,調用 setObject(idx, date, OracleTypes.DATE) 或新 API setDate 重載 | 根治,類型精確匹配 | 需升級數據庫與驅動 |
| 2. 列類型遷移為 TIMESTAMP | ALTER TABLE t1 MODIFY end_date TIMESTAMP | 類型一致,無隱式轉換 | 影響所有應用,存儲空間略增,需回歸測試 |
| 3. V8Compatible 連接屬性(推薦快速修復) | 連接屬性/URL 設置 V8Compatible=true 或 oracle.jdbc.V8Compatible=true | 零代碼變更,驅動強制綁定為 DATE | 僅適用 10g/11g 驅動,12c+ 可能棄用 |
-- 開啟 10046 追蹤或查看 V$SQL_BIND_DATA
SELECT sql_text, bind_type, datatype
FROM v$sql_bind_capture
WHERE sql_id = '...';
確認 DATATYPE = 12 (DATE) 而非 180 (TIMESTAMP)。
TIMESTAMP WITH TIME ZONE + 驅動新 API。V8Compatible=true,最低改動成本。java.sql.Date(純日期)或 java.sql.Timestamp,避免 java.util.Date 歧義。TO_TIMESTAMP/TO_DATE 隱式轉換模式。