-- 表結構
CREATE TABLE events (id NUMBER, event_time DATE, ...);
CREATE INDEX idx_events_time ON events(event_time);
-- Java 代碼 (問題版)
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM events WHERE event_time BETWEEN ? AND ?");
ps.setTimestamp(1, startTs); // java.sql.Timestamp -> Oracle TIMESTAMP
ps.setTimestamp(2, endTs);
ResultSet rs = ps.executeQuery(); -- 执行计划显示 FULL TABLE SCAN
setTimestamp() 綁定變量類型為 TIMESTAMP (含納秒)。WHERE event_time = SYS_EXTRACT_UTC(:b1) 或 WHERE CAST(event_time AS TIMESTAMP) = :b1。ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 12';
-- 執行查詢
-- tkprof 分析 trace 文件,觀察:
-- 1. EXEC 步驟中的 TYPECHECK 錯誤或隱式轉換標記
-- 2. FETCH 步驟的 cr= 大量一致性讀 (全表掃描特徵)
| 方案 | 代碼示例 | 優點 | 缺點 |
|---|---|---|---|
| setString (推薦) | ps.setString(1, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(start)); |
繞過 JDBC 類型映射,Oracle 隱式轉換字符串→DATE 且不破壞索引;兼容性最強 | 需應用層格式化,極微小 CPU 開銷 |
| setDate (java.sql.Date) | ps.setDate(1, new java.sql.Date(start.getTime())); |
映射為 Oracle DATE,理論匹配 | 精度僅至天,失去時分秒;部分驅動版本仍會轉 TIMESTAMP |
| Oracle 專用 API | ((OraclePreparedStatement)ps).setDATE(1, new DATE(start)); |
精確類型控制 | 強耦合 ojdbc.jar,遷移困難 |
| 會話級 NLS 參數 | ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'; |
無需改代碼 | 影響全會話,風險不可控 |
oracle.jdbc.V8Compatible=true 或 mapDateToTimestamp=false 連接屬性)。setString + yyyy-MM-dd HH24:mi:ss 格式。setTimestamp + Calendar.getInstance(TimeZone.getTimeZone("UTC"))。setTimestamp/setObject。EXPLAIN PLAN 驗證索引使用情況。一個 setTimestamp 就能讓千萬行表的索引失效,這是 JDBC 類型系統與 Oracle 類型系統阻抗失配的經典案例。「字符串綁定 DATE」看似醜陋,實則是最高兼容、零風險的工程選擇。將此規約寫入團隊開發手冊,可避免 90% 同類生產事故。