1. 先说个场景:为什么好好的日期要来回转换
大概三年前,我接手过一个老项目,数据表里存了一堆"看起来是日期"的字符串,比如 "2024/03/15"、"20240315"、"15-03-2024" 这种。业务那边要按天统计用户注册量,当时写SQL的同事直接用了 WHERE create_date = '2024-03-15',结果查出来的数据时对时错,排查了半天才发现,有的行存的是 2024-03-15,有的行存的是 2024/03/15,还有极少数是 20240315。同样的筛选条件,字符串和字符串摆在一起,MySQL只能忠实地逐字符比较,匹配不上就是匹配不上,它根本不会自己去脑补"这俩其实是同一天"。
这个项目让我对MySQL的日期时间转换函数产生了执念。后来但凡涉及时间处理,我都会先想明白三个问题:数据在表里到底是什么类型、业务查询需要什么类型、中间要经过哪一层转换函数。如果这道坎迈不过去,后面写出来的SQL全是碰运气。
MySQL里的日期时间转换,STR_TO_DATE() 是最容易被提及的一个函数,它负责把字符串解析成日期/时间类型。但实际业务里你不会只用到它。数据从接口进来时是字符串,存库时要转成 DATE 或 DATETIME;报表查询时要按小时、按周、按季度分组,得把 DATETIME 格式化回字符串;跨时区统计时还要把时间从 UTC 转成某个具体时区。所以完整的主题不是"STR_TO_DATE这一个函数怎么用",而是这套转换体系怎么在你的SQL里配合使用。
这篇文章我打算先把 STR_TO_DATE 的参数、格式符、坑一个个拆开讲透,然后结合真实的场景(缓存失效、批量导入、统计报表)说明这些函数怎么落地。最后再整理一下 SQL_MODE、时区、索引这几个容易让日期转换翻车的隐藏因素。不管你是刚接触MySQL的初学者,还是写了好几年业务SQL的老手,这篇文章应该都能帮你少踩几个坑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. STR_TO_DATE的基础功:语法、格式符、返回值类型
2.1 函数签名和基本逻辑
STR_TO_DATE(str, format) 是MySQL官方提供的字符串解析函数,核心作用就是按照 format 指定的格式,把 str 解析成 DATE、DATETIME 或者 TIME 类型。它做的事情说白了就是"翻译":告诉MySQL,字符串里的哪一段是年份、哪一段是月份、哪一段是分钟。
sql复制SELECT STR_TO_DATE('2024-03-15', '%Y-%m-%d');
-- 输出: 2024-03-15
你可能会觉得,这不就是把字符串原封不动变回日期吗?对,但要注意,输出结果的类型已经不是 VARCHAR 了,而是 DATE 类型。这个区别非常关键。DATE 类型和 VARCHAR 类型的 '2024-03-15' 在比较、索引使用、排序上完全是两回事。
再来看一个稍微复杂点的例子:
sql复制SELECT STR_TO_DATE('15/03/2024 14:30:45', '%d/%m/%Y %H:%i:%s');
-- 输出: 2024-03-15 14:30:45
前面的字符串是按"日/月/年 时:分:秒"排列的,format 里就用 %d/%m/%Y 去对应。MySQL解析时严格按照 format 从左到右去匹配,多一个分隔符或者少一个分隔符都会导致返回 NULL。比如:
sql复制SELECT STR_TO_DATE('2024-03-15', '%Y/%m/%d');
-- 输出: NULL,因为字符串里是横杠,format里指定的是斜杠
2.2 格式符清单,哪些最容易搞混
MySQL的日期格式化符号有不少,网上能查到完整表格,我这里只挑重点和容易出问题的讲。完整对照可以参考官方文档,但日常写SQL最常用的其实就是下面这一组:
| 格式符 | 含义 | 示例 |
|---|---|---|
| %Y | 四位年份 | 2024 |
| %y | 两位年份 | 24 |
| %m | 两位月份(01-12) | 03 |
| %c | 月份(1-12,无前导零) | 3 |
| %d | 两位日(01-31) | 15 |
| %e | 日(1-31,无前导零) | 5 |
| %H | 24小时制(00-23) | 14 |
| %h / %I | 12小时制(01-12) | 08 |
| %i | 分钟(00-59) | 30 |
| %s / %S | 秒(00-59) | 45 |
| %p | AM 或 PM | AM |
| %W | 星期名 | Friday |
| %a | 缩写星期名 | Fri |
| %M | 月份名 | March |
| %b | 缩写月份名 | Mar |
| %j | 一年中的第几天(001-366) | 075 |
| %T | 时间,等于 %H:%i:%s | 14:30:45 |
| %f | 微秒(000000-999999) | 123456 |
最容易翻车的两组:一是 %m 和 %i,%m 是月份,%i 是分钟。我第一次写的时候把分钟写成了 %m,结果MySQL把字符串里的 30 当成月份去解析,直接返回 NULL,排查了很久才发现是格式化符号用错了。二是 %Y 和 %y,%Y 是四位年份,%y 是两位年份。STR_TO_DATE 对两位年份的解析规则是 00-69 转换为 2000-2069,70-99 转换为 1970-1999,如果业务数据里有跨世纪的日期,用 %y 就得非常小心。
另外,格式串里的普通字符需要和字符串严格匹配。字符串里用 - 分隔,format 里也要写 -;用 / 分隔,format 里就写 /。有次我图省事,字符串是 2024-03-15 14:30:45,format 写的是 %Y-%m-%d %H:%i,结果的时间部分没完全匹配上,返回了 NULL。我记得当时还怀疑是MySQL函数坏了,后来才意识到空格和分隔符必须一个字符不差。
2.3 返回类型由format的完整程度决定
STR_TO_DATE 的返回类型不是固定的,它取决于 format 里包含了哪些格式符。只包含年月日,返回 DATE;只包含时分秒,返回 TIME;日期时间都包含,返回 DATETIME。连续的时间部分甚至可以返回 TIME 类型。
sql复制SELECT STR_TO_DATE('14:30:45', '%H:%i:%s');
-- 输出: 14:30:45 (TIME类型)
SELECT STR_TO_DATE('2024-03-15 14:30:45', '%Y-%m-%d %H:%i:%s');
-- 输出: 2024-03-15 14:30:45 (DATETIME类型)
这个特性在处理
