| 特性 | TIMESTAMP | DATETIME |
|---|---|---|
| 存儲大小 | 4 字節 | 8 字節 (5.6.4+) |
| 時間範圍 | 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC | 1000-01-01 ~ 9999-12-31 |
| 時區感知 | 是(存儲 UTC,顯示按會話時區轉換) | 否(原樣存儲原樣顯示) |
| 自動初始化/更新 | 支援 DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 5.6.5+ 支援 |
CREATE TABLE tz_test (dt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);
-- 會話時區設為東八區(北京時間)
SET time_zone = '+08:00';
INSERT INTO tz_test VALUES ('1970-01-01 08:00:01'); -- 東八區時間
SELECT * FROM tz_test; -- 顯示 1970-01-01 08:00:01
-- 切換會話時區為 UTC
SET time_zone = '+00:00';
SELECT * FROM tz_test; -- 顯示 1970-01-01 00:00:01 (UTC)
-- 切換為東一區
SET time_zone = '+01:00';
SELECT * FROM tz_test; -- 顯示 1970-01-01 01:00:01
關鍵結論:同一條記錄,不同會話時區下 SELECT 看到的時間不同,但底層存儲的 UTC 時間戳不變。
SET time_zone = '+08:00';
INSERT INTO tz_test VALUES ('1970-01-01 00:00:01'); -- 東八區時間早於 1970-01-01 08:00:01
-- Query OK, 1 row affected, 1 warning (0.00 sec)
SHOW WARNINGS;
-- Level: Warning, Code: 1264, Message: Out of range value adjusted for column 'dt' at row 1
SELECT * FROM tz_test;
-- 實際存儲為 0000-00-00 00:00:00
風險提示:僅產生 Warning 不報錯,應用層 try-catch 捕獲不到,數據靜默截斷極其危險。
-- my.cnf 全局設置 [mysqld] default-time-zone = '+08:00' # 或系統時區 -- 連接字串指定(JDBC 例) jdbc:mysql://host:3306/db?serverTimezone=Asia/Shanghai
// Java 8+ 最佳實踐
// 資料庫存 TIMESTAMP (UTC)
// 應用層用 Instant/ZonedDateTime
Instant instant = Instant.now(); // UTC
ZonedDateTime beijing = instant.atZone(ZoneId.of("Asia/Shanghai"));
// 寫入 DB:PreparedStatement.setTimestamp(1, Timestamp.from(instant))
// 讀出 DB:ResultSet.getTimestamp(1).toInstant()
-- 危險:字符串隱式轉換依賴會話時區
INSERT INTO tz_test VALUES ('2024-01-01 12:00:00');
-- 安全:顯式指定時區或使用 UTC 時間戳
INSERT INTO tz_test VALUES (CONVERT_TZ('2024-01-01 12:00:00', '+08:00', '+00:00'));
-- 或應用層傳入 UTC 時間
-- 批量修正被截斷為 0000-00-00 的記錄
-- 假設業務邏輯知曉原始時區為 +08:00
UPDATE tz_test
SET dt = CONVERT_TZ('1970-01-01 08:00:01', '+08:00', '+00:00')
WHERE dt = '0000-00-00 00:00:00';
TIMESTAMP(n) 支援微秒精度 (n=0~6)TIMESTAMP(0) 顯示寬度語法,僅保留精度語義TIMESTAMP WITH TIME ZONE 別名支援(標準 SQL 兼容)