日期時間類型是業務表的核心屬性,正確選型、DDL 變更、應用層寫入讀取、服務端格式配置四環扣一環,缺一不可。
| 類型 | 儲存字節 | 範圍 | 精度 | 適用場景 |
|---|---|---|---|---|
DATE | 3 | 1000-01-01 ~ 9999-12-31 | 天 | 生日、入職日期、業務單據日期 |
DATETIME | 8 (5.6.4 前) | 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 | 秒 | 創建/更新時間、操作日誌 (不帶時區) |
TIMESTAMP | 4 | 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC | 秒 | 自動更新時間戳、跨時區系統 (會按會話時區轉換) |
DATETIME(6) | 8 | 同 DATETIME | 微秒 | 高精度計費、金融流水、分佈式追蹤 |
💡 推薦:業務時間用 DATETIME (無時區歧義),修改時間用 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP。
// 依賴: mysql-connector-java 8.0+
// 連接串建議顯式指定時區
String url = "jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false";
try (Connection conn = DriverManager.getConnection(url, "user", "pass");
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO orders (biz_date, pay_time) VALUES (?, ?)")) {
// 1. 寫入 java.time.LocalDate / LocalDateTime (JDBC 4.2+ 直接支援)
ps.setObject(1, LocalDate.of(2024, 6, 15)); // DATE
ps.setObject(2, LocalDateTime.of(2024, 6, 15, 14, 30, 45, 123_000_000)); // DATETIME(3)
ps.executeUpdate();
// 2. 讀取
try (ResultSet rs = conn.createStatement().executeQuery("SELECT biz_date, pay_time FROM orders WHERE id=1")) {
if (rs.next()) {
LocalDate bizDate = rs.getObject(1, LocalDate.class);
LocalDateTime payTime = rs.getObject(2, LocalDateTime.class);
System.out.println(bizDate + " | " + payTime);
}
}
}
✅ 棄用 java.sql.Date/Timestamp、SimpleDateFormat,全鏈路使用 java.time.* (不可變、線程安全、ISO-8601 原生)。
MySQL 內部儲存為二進制,顯示格式由客戶端決定。若確需調整服務端預設輸出格式 (影響 SELECT 直接輸出文本):
-- my.cnf / my.ini [mysqld] 區段 [mysqld] # 8.0 可動態修改 # date_format = '%Y-%m-%d' -- 預設已是此格式 # datetime_format = '%Y-%m-%d %H:%i:%s' # time_format = '%H:%i:%s' -- 會話級臨時修改 SET SESSION date_format = '%Y/%m/%d'; SELECT CURDATE(); -- 輸出 2024/06/15
⚠️ 生產環境強烈不建議修改,應用層統一用 DateTimeFormatter 格式化,避免跨語言/驅動版本兼容性問題。
| 坑點 | 後果 | 規避 |
|---|---|---|
TIMESTAMP` 2038 年溢出 | 長期業務數據截斷 | 用 DATETIME 或 BIGINT` 存毫秒時間戳 |
| 時區不一致 | 讀寫時間偏移 | 連接串 serverTimezone=Asia/Shanghai,JVM -Duser.timezone=Asia/Shanghai |
DATE` 插入 | 截斷時間部分 | 用 CURDATE() 或應用層 LocalDate.now() |
DATETIME` 無微秒精度 | 高併發唯一性衝突 | 用 DATETIME(3/6) + 唯一索引 |
| 字符串隱式轉換 | 格式不匹配報錯/錯誤數據 | PreparedStatement + setObject(LocalDateTime) |