date_format 返回 null 主因是首参非合法日期,如空字符串、'0000-00-00' 或非法格式(如 '2024-13-01');mysql 严格模式拒解析,非严格模式转为 0 导致结果为 null。

DATE_FORMAT 为什么返回 NULL 而不是你想要的日期字符串
常见现象:DATE_FORMAT 返回 NULL,但字段明明有值。最可能的原因是传入的第一个参数不是合法的日期类型——比如传了空字符串、'0000-00-00'、或一个被 MySQL 隐式转成 0 的非法时间(如 '2024-13-01')。MySQL 在严格模式下会拒绝解析,非严格模式则转成 0000-00-00,而 DATE_FORMAT(NULL, ...) 或 DATE_FORMAT(0, ...) 都返回 NULL。
实操建议:
- 先用
SELECT col, IS_DATE(col), IS_VALID_DATE(col)(MySQL 8.0.29+)或SELECT col, STR_TO_DATE(col, '%Y-%m-%d')检查原始值是否可解析 - 避免直接对 VARCHAR 字段调用
DATE_FORMAT,先用STR_TO_DATE转一次,再格式化 - 注意时区:如果字段是
TIMESTAMP类型,DATE_FORMAT输出受会话时区影响;DATETIME则不受影响
MySQL 5.7 和 8.0 中 %w、%W、%a 的行为差异
%w(周日=0)、%W(全名,如 'Monday')、%a(缩写,如 'Mon')看着简单,但在不同版本和 locale 下容易出错。MySQL 5.7 默认用 C locale,%W 固定输出英文;8.0 开始支持更完整的 locale 设置,但需显式指定,否则仍走默认。
实操建议:
- 不要依赖系统 locale 自动切换语言,显式用
SET lc_time_names = 'zh_CN'再执行查询(该设置只对当前会话有效) -
%w始终返回数字(0–6),但它的“周日=0”是 MySQL 自定义,和 ISO 8601(周一=1)冲突,做周统计时务必确认业务规则 - 若需 ISO 周数,用
%x+%v(年份+周序号),但注意它按周一为每周起点,且 2023-12-31 可能属于 2024 年第 1 周
在 WHERE 条件里滥用 DATE_FORMAT 导致索引失效
典型错误:写 WHERE DATE_FORMAT(create_time, '%Y-%m') = '2024-04' 查询当月数据。这会让 create_time 上的索引完全失效,因为函数作用于列本身,优化器无法做范围扫描。
实操建议:
- 改用范围查询:
WHERE create_time >= '2024-04-01' AND create_time - 如果真要按格式分组统计,把
DATE_FORMAT放在SELECT或GROUP BY,别放WHERE - 对高频格式化需求,可建生成列(generated column)并加索引,例如:
ALTER TABLE t ADD COLUMN ym CHAR(7) STORED AS (DATE_FORMAT(create_time, '%Y-%m')),再对ym建索引
DATE_FORMAT 处理毫秒和微秒时的陷阱
DATE_FORMAT 不支持毫秒(%f 是微秒,且只对 DATETIME(6) 有效)。如果你传入 '2024-04-05 10:20:30.123'(三位小数),%f 会补零变成 '123000';如果是 '2024-04-05 10:20:30.123456',%f 才显示完整六位。
实操建议:
- 需要精确到毫秒显示,别用
%f,改用字符串拼接:CONCAT(DATE_FORMAT(dt, '%Y-%m-%d %H:%i:%s'), '.', LPAD(MICROSECOND(dt) DIV 1000, 3, '0')) -
DATE_FORMAT对TIMESTAMP类型字段的微秒部分,在时区转换后可能丢失(尤其跨夏令时),优先用DATETIME存带精度的时间 - PHP 或应用层处理毫秒更可控,数据库层尽量避免复杂格式化逻辑
真正难的不是记住所有格式符,而是每次写之前想清楚:这个格式化是给展示用,还是参与计算或过滤?前者可以松一点,后者一丁点隐式转换都可能让结果跑偏。










