兩類型同樣顯示 YYYY-MM-DD HH:MM:SS,但底層機制差異巨大,選錯會導致跨時區業務錯誤、2038 年溢位、隱性預設值陷阱。本文從儲存、時區、範圍、預設值四大維度全方位對比。
| 維度 | DATETIME | TIMESTAMP |
|---|---|---|
| 儲存空間 | 8 bytes | 4 bytes |
| 儲存內容 | 字面日期時間 (不含時區) | UTC 秒數 (1970-01-01 起) |
| 時區感知 | ❌ 無感知 | ✅ 寫入轉 UTC,讀取轉回 Session 時區 |
| 支援範圍 | 1000-01-01 ~ 9999-12-31 | 1970-01-01 ~ 2038-01-19 (32-bit 有符號整數上限) |
| NULL 預設行為 | 允許 NULL,無隱性預設 | NOT NULL + 隱性 DEFAULT CURRENT_TIMESTAMP (視 sql_mode) |
| 自動更新 | 需觸發器 | ON UPDATE CURRENT_TIMESTAMP 原生支援 |
| 索引/排序效能 | 略慢 (8 bytes 比較) | 略快 (4 bytes 整數比較) |
CREATE TABLE t8 (dt DATETIME, ts TIMESTAMP);
INSERT INTO t8 VALUES (NOW(), NOW()); -- 假設 Session 時區 +08:00,寫入 2024-06-15 14:30:00
SET time_zone = '+09:00'; -- 切換到東京時區
SELECT * FROM t8;
-- 結果:
-- dt: 2024-06-15 14:30:00 (不變)
-- ts: 2024-06-15 15:30:00 (+1 小時,自動轉換)
結論:
• 需保留「使用者當地時間」原貌 (如預約、日曆) → DATETIME。
• 需記錄「絕對發生時刻」並跨時區顯示 (如日誌、審計、訂單創建) → TIMESTAMP。
-- MySQL 5.6 / 非嚴格模式下
CREATE TABLE t (ts TIMESTAMP);
SHOW CREATE TABLE t\G
-- 實際建立:
-- ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-- 後果:INSERT 不給 ts 值 → 自動填當前時間;UPDATE 其他欄位 → ts 也自動刷新
-- 極易導致審計欄位「無故變更」
修正:顯式聲明
CREATE TABLE t (
ts TIMESTAMP NULL DEFAULT NULL, -- 可為 NULL,無自動填充
-- 或
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 僅插入時自動
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
2,147,483,647 秒 = 2038-01-19 03:14:07 UTC。int64),範圍延至 3000 年後。需要跨時區正確顯示?
├─ 是 → 範圍是否超過 2038 年?
│ ├─ 否 → TIMESTAMP (4 bytes 省空間)
│ └─ 是 → DATETIME (8 bytes) 或 BIGINT 毫秒時間戳
└─ 否 → 保留使用者原始時間 (預約/日曆)?
├─ 是 → DATETIME
└─ 否 → BIGINT 毫秒時間戳 (應用層處理時區,最高效、無年限)
結語:DATETIME 是「日曆時間」,TIMESTAMP 是「絕對時刻」。理解時區轉換機制、2038 年限制、隱性預設值行為,並按業務語義選型,才能避開生產環境的時間陷阱。