一、写在前面:为什么“星期几”也能成为面试题
很多开发者认为 `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 错了,而是规范尚未对齐。