日期時間處理是業務開發高頻需求。本文系統整理 MySQL 5.7/8.0 核心日期時間函數,分類為**當前時間獲取、日期時間拆解、運算運算、格式化輸出、時區轉換、範圍查詢**六大類,附帶實戰範例與效能提示。
| 函數 | 返回類型 | 特性 | 示例輸出 |
|---|---|---|---|
NOW() | DATETIME | 語句執行開始時刻(事務內固定) | 2023-08-08 14:30:45 |
SYSDATE() | DATETIME | 函數**實際執行**時刻(動態) | 2023-08-08 14:30:45.000001 |
CURDATE() / CURRENT_DATE() | DATE | 當前日期 | 2023-08-08 |
CURTIME() / CURRENT_TIME() | TIME | 當前時間 | 14:30:45 |
UTC_DATE() | DATE | UTC 日期 | 2023-08-08 |
UTC_TIME() | TIME | UTC 時間 | 06:30:45 |
UTC_TIMESTAMP() | DATETIME | UTC 日期時間 | 2023-08-08 06:30:45 |
NOW() vs SYSDATE():事務/存儲過程中 NOW() 固定為開始時間,SYSDATE() 每次調用取當前時間,影響複制一致性,建議統一用 NOW()。
SET @dt = '2023-08-08 14:30:45.123456';
SELECT DATE(@dt); -- 2023-08-08
SELECT TIME(@dt); -- 14:30:45.123456
SELECT YEAR(@dt); -- 2023
SELECT QUARTER(@dt); -- 3
SELECT MONTH(@dt); -- 8
SELECT WEEK(@dt); -- 31 (模式 0)
SELECT DAY(@dt); -- 8
SELECT DAYOFMONTH(@dt); -- 8
SELECT DAYOFWEEK(@dt); -- 3 (1=週日)
SELECT WEEKDAY(@dt); -- 1 (0=週一)
SELECT DAYOFYEAR(@dt); -- 220
SELECT HOUR(@dt); -- 14
SELECT MINUTE(@dt); -- 30
SELECT SECOND(@dt); -- 45
SELECT MICROSECOND(@dt); -- 123456
| 函數 | 語法 | 示例 | 結果 |
|---|---|---|---|
DATE_ADD / + INTERVAL | DATE_ADD(date, INTERVAL expr unit) | DATE_ADD('2023-01-01', INTERVAL 1 MONTH) | 2023-02-01 |
DATE_SUB / - INTERVAL | DATE_SUB(date, INTERVAL expr unit) | '2023-01-01' - INTERVAL 1 DAY | 2022-12-31 |
DATEDIFF | DATEDIFF(date1, date2) | DATEDIFF('2023-08-08', '2023-08-01') | 7 (date1 - date2) |
TIMEDIFF | TIMEDIFF(time1, time2) | TIMEDIFF('14:30:00', '12:00:00') | 02:30:00 |
TIMESTAMPDIFF | TIMESTAMPDIFF(unit, datetime1, datetime2) | TIMESTAMPDIFF(DAY, '2023-01-01', '2023-08-08') | 219 (精確單位) |
ADDDATE | ADDDATE(date, INTERVAL expr unit) | ADDDATE('2023-01-01', 10) | 2023-01-11 |
SUBDATE | SUBDATE(date, INTERVAL expr unit) | SUBDATE('2023-01-01', 10) | 2022-12-22 |
INTERVAL 單位:MICROSECOND、SECOND、MINUTE、HOUR、DAY、WEEK、MONTH、QUARTER、YEAR、SECOND_MICROSECOND、MINUTE_MICROSECOND、MINUTE_SECOND、HOUR_MICROSECOND、HOUR_SECOND、HOUR_MINUTE、DAY_MICROSECOND、DAY_SECOND、DAY_MINUTE、DAY_HOUR、YEAR_MONTH。
-- 格式化輸出
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'); -- 2023-08-08 14:30:45
SELECT DATE_FORMAT(NOW(), '%Y%m%d%H%i%s'); -- 20230808143045
SELECT TIME_FORMAT('14:30:45', '%H點%i分%s秒'); -- 14點30分45秒
-- 常用格式符
%Y 4位年 %y 2位年 %m 月(01-12) %c 月(1-12)
%d 日(01-31) %e 日(1-31) %H 時(00-23) %k 時(0-23)
%i 分(00-59) %s 秒(00-59) %f 微秒(000000-999999)
%W 週名 %a 週名縮寫 %M 月名 %b 月名縮寫
-- 字串解析為日期
SELECT STR_TO_DATE('2023-08-08 14:30:45', '%Y-%m-%d %H:%i:%s');
SELECT STR_TO_DATE('08/08/2023', '%m/%d/%Y');
-- 查看當前時區
SELECT @@session.time_zone, @@global.time_zone;
-- SYSTEM / +08:00 / UTC / Asia/Shanghai
-- 會話級設定時區
SET time_zone = '+08:00';
SET time_zone = 'Asia/Shanghai';
-- 全局設定(需權限,配置檔永久)
SET GLOBAL time_zone = '+08:00';
-- my.cnf: default-time-zone = '+08:00'
-- 轉換時區
SELECT CONVERT_TZ('2023-08-08 14:30:00', '+00:00', '+08:00'); -- 2023-08-08 22:30:00
-- ❌ 錯誤:函數包裹欄位 → 索引失效
SELECT * FROM orders WHERE DATE(create_time) = '2023-08-08';
SELECT * FROM orders WHERE YEAR(create_time) = 2023 AND MONTH(create_time) = 8;
-- ✅ 正確:欄位裸露,利用索引範圍掃描
SELECT * FROM orders
WHERE create_time >= '2023-08-08 00:00:00'
AND create_time < '2023-08-09 00:00:00';
-- 近 30 天
SELECT * FROM orders
WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY);
-- 本月
SELECT * FROM orders
WHERE create_time >= DATE_FORMAT(NOW(), '%Y-%m-01')
AND create_time < DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 1 MONTH), '%Y-%m-01');
-- 本週(週一開始)
SELECT * FROM orders
WHERE create_time >= DATE_SUB(NOW(), INTERVAL WEEKDAY(NOW()) DAY)
AND create_time < DATE_ADD(DATE_SUB(NOW(), INTERVAL WEEKDAY(NOW()) DAY), INTERVAL 7 DAY);
| 陷阱 | 後果 | 正確做法 |
|---|---|---|
WHERE DATE(col) = '...' | 全表掃描,索引失效 | 用範圍查詢 col >= '...' AND col < '...' |
BETWEEN '2023-08-01' AND '2023-08-31' 用於 DATETIME | 漏掉 23:59:59 後的毫秒 | col >= '2023-08-01' AND col < '2023-09-01' |
| NOW() 在複制/備份中不一致 | 主從數據不一致 | 用 SYSDATE() 或啟用 sysdate_is_now=1 |
| TIMESTAMP 2038 年問題 | 32 位有符號整數溢出 | 改用 DATETIME(支援到 9999 年) |
| 隱式轉型字串比較日期 | 索引失效、結果錯誤 | 用 STR_TO_DATE 統一格式 |