一、寫在前面:為什么“星期幾”也能成為面試題
很多開發者認為 `DAYOFWEEK` 只是 MySQL 里一個微不足道的日期函數,直到他們在報表里發現“周一”變成了“周日”,或者在跨系統對接時因為 0 與 1 的偏移導致整周數據錯位。本文以 MySQL 的 `DAYOFWEEK` 為核心,用近四千字帶你走完從歷法規則、函數語義、邊界陷阱、性能優化到跨系統兼容的完整旅程,讓你在面對“今天是星期幾”時不再踩坑。
二、歷法溯源:從儒略歷到 ISO-8601
公歷把一年分成 52 周余 1~2 天,每周 7 天的循環是人類最古老的計時方式之一。
- 儒略歷:公元前 45 年確立,7 天一循環,但閏年規則粗糙。
- 格里高利歷:1582 年修正閏年,成為現代公歷。
- ISO-8601:1971 年規定“周一為每周第一天”,成為國際商務與金融事實標準。
MySQL 的日期函數家族,正是在這套歷法規則之上做了數學封裝。
三、MySQL 日期家族:DAYOFWEEK 的定位
在 MySQL 中,與“星期”相關的函數有:
- DAYOFWEEK(date) → 1 到 7(周日為 1)
- WEEKDAY(date) → 0 到 6(周一為 0)
- DAYNAME(date) → 字符串 Monday 到 Sunday
- WEEK(date[, mode]) → 周數
DAYOFWEEK 的獨特之處:
- 返回范圍 1-7,而非 0-6;
- 周日為 1,與北美傳統一致,卻與 ISO-8601 沖突;
- 無參數可指定“周從哪天開始”,完全由函數語義決定。
五、邊界場景:閏年、跨年、周界
1. 閏年 2 月 29 日
2024-02-29 的 DAYOFWEEK 為 4(周四),不影響函數計算,但周數函數可能跳變。
2. 跨年夜
2023-12-31 → 1(周日),2024-01-01 → 2(周一),周序號從 52 變為 1。
3. 周界對齊
若報表要求“周一為每周第一天”,需用 WEEKDAY 或手動偏移。
六、性能視角:函數成本與索引
DAYOFWEEK 是確定性函數,可直接用于表達式索引或生成列,避免全表掃描。
- 場景:查詢“本周訂單”
WHERE DAYOFWEEK(order_date) BETWEEN 2 AND 6
若 order_date 有索引,優化器可轉換范圍掃描;
若無索引,需回表計算。
建議:
- 用生成列存儲 DAYOFWEEK(order_date),再建索引;
- 避免在 WHERE 子句中對日期列做函數運算,破壞索引。
七、實戰心法:報表與統計
1. 周維度匯總
SELECT DAYNAME(order_date) AS week_day, COUNT(*)
FROM orders
GROUP BY week_day;
2. 周一為周首日
SELECT CASE WHEN DAYOFWEEK(order_date)=1 THEN 7 ELSE DAYOFWEEK(order_date)-1 END AS iso_day;
3. 工作日/周末
利用 CASE WHEN DAYOFWEEK(order_date) IN (2,3,4,5,6) THEN 'workday' ELSE 'weekend' END 分類。
八、跨系統兼容:0 與 1 的戰爭
- Excel:WEEKDAY(date,2) 返回 1-7,周一為 1;
- Python:datetime.weekday() 返回 0-6,周一為 0;
- JavaScript:getDay() 返回 0-6,周日為 0;
- MySQL:DAYOFWEEK 返回 1-7,周日為 1。
對接策略:
- 統一偏移:MySQL 結果 -1 再取模 7;
- 中間層轉換:ETL 腳本統一映射到 0-6;
- 文檔約定:在 API 文檔中顯式說明“周一=0”或“周一=1”。
九、時區陷阱:UTC vs 本地
DAYOFWEEK 基于會話時區,若服務器為 UTC,客戶端為東八區,同一物理時間可能跨日。
解決:
- 所有日期存儲為 UTC;
- 前端按本地時區渲染;
- 報表統一用 UTC 計算周界。
十、高級用法:生成列與分區
- 生成列:ALTER TABLE orders ADD COLUMN week_day TINYINT AS (DAYOFWEEK(order_date)) STORED;
- 分區:按周分區 LIST COLUMNS(week_day),快速清理歷史周數據。
注意事項:生成列不可更新,分區鍵需與查詢條件對齊。
十一、常見誤區與排查
誤區 1:DAYOFWEEK 返回 0
實際返回 1-7,0 只出現在 WEEKDAY。
誤區 2:周日=0 導致報表錯位
顯式偏移后統一周界。
誤區 3:函數索引失效
避免在索引列使用函數,改用生成列。
十二、未來展望:MySQL 8.0 與標準庫
MySQL 8.0 引入 `JSON_TABLE`、`EXTRACT(WEEK FROM date)` 與 ISO 周函數,進一步簡化周維度計算。
建議:新項目優先使用 `EXTRACT(WEEK FROM date)` 與 `DAYNAME` 組合,避免手動偏移。
十三、每日一練:親手驗證一周
1. 準備:創建一個含 7 天日期的表。
2. 查詢:用 DAYOFWEEK 分組,確認周日=1。
3. 轉換:用 CASE 把周日映射到 7,周一映射到 1。
4. 校驗:與 Python 的 weekday() 對比,確認偏移一致。
5. 復盤:記錄差異,寫入團隊規范。
十四、結語:把“星期幾”寫進團隊規范
DAYOFWEEK 看似簡單,卻隱藏著文化差異、系統差異、性能差異。
真正的工程素養,是把“1-7 還是 0-6”、“周日還是周一”寫進文檔、寫進代碼、寫進測試。
當下一次跨系統對接時,請記得:
不是 MySQL 錯了,而是規范尚未對齊。