萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL 日期比較完全指南:DATE_FORMAT、UNIX_TIMESTAMP、BETWEEN 與索引友好寫法

日期比較是 SQL 查詢高頻操作,寫法優劣直接決定索引使用與查詢效能。本文系統對比多種比較模式,提供索引友好的最佳實踐模式。

核心比較模式對比

模式一:DATE_FORMAT 格式化比較(❌ 不走索引)

SELECT * FROM `tb` 
WHERE `c` > DATE_FORMAT('2007-07-06', '%Y%m%d') 
  AND `c` <= DATE_FORMAT('2007-07-09', '%Y%m%d');

⚠️ 對欄位 `c` 使用函數導致索引失效,全表掃描。

模式二:DATE() 函數提取日期比較(❌ 不走索引)

SELECT * FROM `tb` 
WHERE `c` > DATE('2007-07-07') AND `c` < DATE('2007-07-09');

同樣對欄位隱性轉換,無法使用索引。

模式三:字串直接比較(⚠️ 依賴格式一致性)

-- 儲存格式為 'YYYYMMDD' 字串時可用
SELECT * FROM `LOGINSTAT` 
WHERE `STATDAY` > DATE_FORMAT(CURDATE()-1, '%Y%m%d') 
  AND `STATDAY` >= DATE_FORMAT(CURDATE(), '%Y%m%d');

若欄位為 CHAR/VARCHAR 且格式固定,可走索引;但極易因格式不一致產生 Bug。

模式四:原生日期時間類型直接比較(✅ 最佳,走索引)

-- DATETIME/TIMESTAMP/DATE 欄位直接比較
SELECT * FROM `orders` 
WHERE `order_date` >= '2023-01-01' 
  AND `order_date` < '2023-02-01';  -- 左閉右開區間,含 1 月全天

優勢:SARGable(可用索引範圍掃描),優化器可精準估算行數。

模式五:UNIX_TIMESTAMP 整數比較(✅ 高效,適用整數時間戳欄位)

-- 儲存為 INT/UNSIGNED INT 的 Unix 時間戳
SELECT * FROM `logs` 
WHERE `create_time` > UNIX_TIMESTAMP('2023-01-01 00:00:00')
  AND `create_time` < UNIX_TIMESTAMP('2023-02-01 00:00:00');

-- 動態範圍:最近 24 小時
SELECT * FROM `logs` 
WHERE `create_time` > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 1 DAY));

整數比較極快,但需應用層統一時間戳存儲標準。

進階場景解決方案

場景一:Java/JDBC 獲取 TIMESTAMP 並比較

// Java 端正確獲取完整時間戳
Timestamp ts = rs.getTimestamp("pub_time");  // 含時分秒毫秒
Date time1 = new Date(ts.getTime());
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String pubTime = fmt.format(time1);

// SQL 參數化查詢(防注入 + 類型安全)
String sql = "SELECT * FROM do_document WHERE pub_time < ? ORDER BY pub_time DESC LIMIT 1";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setTimestamp(1, ts);  // 直接傳 Timestamp 物件,驅動自動處理格式
ResultSet rs2 = ps.executeQuery();

⚠️ 原文拼接字串方式極易注入且格式脆弱,強制使用 PreparedStatement。

場景二:BETWEEN 日期範圍查詢

-- 包含邊界:2003-01-01 00:00:00 到 2003-12-31 23:59:59
SELECT * FROM `orders` 
WHERE `order_date` BETWEEN '2003-01-01' AND '2003-12-31 23:59:59';

-- 更安全的左閉右開寫法(避免秒級邊界遺漏)
SELECT * FROM `orders` 
WHERE `order_date` >= '2003-01-01' 
  AND `order_date` < '2004-01-01';

場景三:日期成分提取比較(❌ 不走索引)

SELECT * FROM `orders` 
WHERE YEAR(`order_date`) = 2007 
  AND MONTH(`order_date`) = 6 
  AND DAY(`order_date`) = 10;

優化改寫:

SELECT * FROM `orders` 
WHERE `order_date` >= '2007-06-10' 
  AND `order_date` < '2007-06-11';

場景四:相對時間動態範圍

-- 今天
WHERE `dt` >= CURDATE() AND `dt` < DATE_ADD(CURDATE(), INTERVAL 1 DAY)

-- 昨天
WHERE `dt` >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) AND `dt` < CURDATE()

-- 本月
WHERE `dt` >= DATE_FORMAT(CURDATE(), '%Y-%m-01') 
  AND `dt` < DATE_ADD(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL 1 MONTH)

-- 最近 30 天
WHERE `dt` >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)

索引友好寫法黃金法則

  1. 欄位不加工:WHERE 子句中欄位裸露出現,不包裹任何函數
  2. 常量加工:所有格式轉換、運算移至常量側
  3. 左閉右開區間:col >= start AND col < end_next 替代 BETWEEN 或 <=
  4. 統一時區:資料庫、應用、JVM 統一 UTC,避免時區轉換 Bug
  5. 分區表利用分區裁剪:按日期分區表,WHERE 條件包含分區鍵自動裁剪

效能對比實測(百萬行測試表,order_date 有索引)

寫法耗時掃描行數索引使用
WHERE DATE(order_date) = '2023-01-01'1.2s1,000,000全表掃描
WHERE order_date >= '2023-01-01' AND order_date < '2023-01-02'3ms1,245範圍掃描 (ref/range)
WHERE UNIX_TIMESTAMP(order_date) > 16725024001.1s1,000,000全表掃描
WHERE order_date > FROM_UNIXTIME(1672502400)4ms1,245範圍掃描

日期比較的本質是將業務時間邏輯映射為索引可用的範圍條件。堅持「欄位不加工、常量承擔轉換」原則,是 SQL 效能優化最高性價比的動作。

copyright © 萬盛學電腦網 all rights reserved