時間維度查詢是業務開發中最高頻的場景之一。本文系統整理 MySQL 中針對 DATE/DATETIME、Unix 時間戳(BIGINT)兩類字段的精確查詢、相對時間查詢(今日、本月、上月等)及效能優化要點。
-- 某一天(忽略時間部分)
SELECT * FROM `orders` WHERE DATE(`created_at`) = '2024-01-15';
-- 某個月
SELECT * FROM `orders` WHERE YEAR(`created_at`) = 2024 AND MONTH(`created_at`) = 1;
-- 某年
SELECT * FROM `orders` WHERE YEAR(`created_at`) = 2024;
-- 今天
SELECT * FROM `orders` WHERE DATE(`created_at`) = CURDATE();
-- 昨天
SELECT * FROM `orders` WHERE DATE(`created_at`) = DATE_SUB(CURDATE(), INTERVAL 1 DAY);
-- 本週(週一為起始)
SELECT * FROM `orders` WHERE YEARWEEK(`created_at`, 1) = YEARWEEK(CURDATE(), 1);
-- 本月
SELECT * FROM `orders` WHERE YEAR(`created_at`) = YEAR(CURDATE()) AND MONTH(`created_at`) = MONTH(CURDATE());
-- 上月
SELECT * FROM `orders` WHERE YEAR(`created_at`) = YEAR(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
AND MONTH(`created_at`) = MONTH(DATE_SUB(CURDATE(), INTERVAL 1 MONTH));
-- 最近 30 天(含今天)
SELECT * FROM `orders` WHERE `created_at` >= DATE_SUB(CURDATE(), INTERVAL 29 DAY);
-- 某一天完整範圍 [2024-01-15 00:00:00, 2024-01-16 00:00:00)
SELECT * FROM `orders`
WHERE `created_at` >= '2024-01-15 00:00:00'
AND `created_at` < '2024-01-16 00:00:00';
-- 本月範圍
SELECT * FROM `orders`
WHERE `created_at` >= DATE_FORMAT(CURDATE(), '%Y-%m-01 00:00:00')
AND `created_at` < DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01 00:00:00');
核心原則:避免在索引列上使用函數(如 DATE()、MONTH()),改用 顯式範圍條件(>= 和 <),使優化器能走索引範圍掃描(type=range)。
FROM_UNIXTIME(ts):時間戳 → DATETIME(受 time_zone 影響)。UNIX_TIMESTAMP(dt):DATETIME → 時間戳。-- 今天(假設 reg_time 存儲秒級時間戳)
SELECT * FROM `users`
WHERE DATE(FROM_UNIXTIME(`reg_time`)) = CURDATE();
-- 本月(推薦:轉換為時間戳範圍,避免函數操作列)
SELECT * FROM `users`
WHERE `reg_time` >= UNIX_TIMESTAMP(DATE_FORMAT(CURDATE(), '%Y-%m-01 00:00:00'))
AND `reg_time` < UNIX_TIMESTAMP(DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01 00:00:00'));
-- 上月
SELECT * FROM `users`
WHERE `reg_time` >= UNIX_TIMESTAMP(DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01 00:00:00'))
AND `reg_time` < UNIX_TIMESTAMP(DATE_FORMAT(CURDATE(), '%Y-%m-01 00:00:00'));
FROM_UNIXTIME() 受會話 time_zone 影響。若應用層寫入 UTC 時間戳,但數據庫會話時區為 +08:00,查詢「今天」會產生偏差。統一規範:應用層、數據庫會話、系統時區全部使用 UTC,或在查詢前顯式設置 SET time_zone = '+00:00';。
| 反模式 | 問題 | 優化寫法 |
|---|---|---|
WHERE DATE(created_at) = '2024-01-15' | 函數操作列,導致全表掃描 | WHERE created_at >= '2024-01-15' AND created_at < '2024-01-16' |
WHERE MONTH(created_at) = 1 | 同上,無法用索引 | 範圍查詢首尾日期 |
WHERE FROM_UNIXTIME(ts) > '2024-01-01' | 每行轉換,極慢 | 將常量轉為時間戳:WHERE ts > UNIX_TIMESTAMP('2024-01-01') |
若表按 RANGE (YEAR(created_at)) 或 RANGE (TO_DAYS(created_at)) 分區,上述範圍查詢可觸發 分區剪枝,僅掃描相關分區,效能提升數量級。注意:分區鍵必須為整數列或返回整數的表達式,DATE/DATETIME 需配合 TO_DAYS()/YEAR() 使用。
>= start AND < end 範圍查詢,利用索引。DATE_SUB(CURDATE(), INTERVAL ...) 計算邊界,再轉為範圍條件。