在日常数据库操作中,日期和时间处理是每个开发者都无法回避的重要课题。作为关系型数据库的标杆产品,MySQL提供了丰富而强大的日期时间函数库,能够满足从简单日期提取到复杂时间计算的各种需求。这些函数不仅能够帮助我们高效处理业务数据中的时间维度,还能大幅简化原本需要应用程序层实现的复杂逻辑。
我在实际项目中经常遇到这样的场景:需要计算用户注册后的第30天、统计月度活跃用户、生成时间序列报表等。如果没有掌握好MySQL的日期时间函数,就不得不把数据全部取到应用层处理,既低效又增加了代码复杂度。而合理运用这些内置函数,往往能用一个SQL查询就解决问题。
这三个函数是日常开发中使用频率最高的基础函数:
sql复制SELECT NOW(); -- 返回当前日期和时间,格式:'YYYY-MM-DD HH:MM:SS'
SELECT CURDATE(); -- 仅返回当前日期,格式:'YYYY-MM-DD'
SELECT CURTIME(); -- 仅返回当前时间,格式:'HH:MM:SS'
在实际项目中,我习惯用NOW()记录数据创建和更新时间戳:
sql复制CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_data JSON NOT NULL,
created_at DATETIME DEFAULT NOW(),
updated_at DATETIME DEFAULT NOW() ON UPDATE NOW()
);
注意:NOW()返回的是SQL语句开始执行时的时间,而SYSDATE()返回的是函数调用时的时间。在高并发或长时间运行的SQL中,两者结果可能不同。
MySQL提供了一系列从日期时间值中提取特定部分的函数:
sql复制SELECT
YEAR('2023-04-01') AS year_part, -- 2023
MONTH('2023-04-01') AS month_part, -- 4
DAY('2023-04-01') AS day_part, -- 1
HOUR('12:45:31') AS hour_part, -- 12
MINUTE('12:45:31') AS minute_part, -- 45
SECOND('12:45:31') AS second_part; -- 31
这些函数在生成月度报表时特别有用。比如统计每月订单量:
sql复制SELECT
MONTH(created_at) AS month,
COUNT(*) AS order_count
FROM orders
WHERE YEAR(created_at) = 2023
GROUP BY MONTH(created_at);
这两个函数允许我们对日期进行精确的加减运算:
sql复制-- 加1天
SELECT DATE_ADD('2023-04-01', INTERVAL 1 DAY); -- '2023-04-02'
-- 减1个月
SELECT DATE_SUB('2023-04-01', INTERVAL 1 MONTH); -- '2023-03-01'
-- 复杂的加减运算
SELECT DATE_ADD('2023-04-01 12:00:00', INTERVAL '1 2:30' DAY_MINUTE); -- '2023-04-02 14:30:00'
支持的INTERVAL单位包括:
sql复制-- 计算两个日期之间的天数差
SELECT DATEDIFF('2023-04-01', '2023-01-01'); -- 90
-- 计算两个时间之间的差值
SELECT TIMEDIFF('12:45:31', '08:30:00'); -- '04:15:31'
这些函数在计算服务时长、产品有效期等场景非常实用:
sql复制-- 计算订单处理时长(小时)
SELECT
order_id,
TIMESTAMPDIFF(HOUR, created_at, shipped_at) AS process_hours
FROM orders
WHERE status = 'shipped';
DATE_FORMAT()允许我们按照自定义格式显示日期时间:
sql复制SELECT
DATE_FORMAT(NOW(), '%Y年%m月%d日 %H时%i分%s秒') AS formatted_date,
DATE_FORMAT(NOW(), '%W, %M %e, %Y') AS another_format;
常用格式说明符:
与DATE_FORMAT()相反,STR_TO_DATE()将字符串转换为日期:
sql复制SELECT STR_TO_DATE('April 1, 2023', '%M %d, %Y'); -- '2023-04-01'
这在处理不同格式的导入数据时特别有用。
sql复制-- 将Unix时间戳转换为日期时间
SELECT FROM_UNIXTIME(1672531200); -- '2023-01-01 00:00:00'
-- 将日期时间转换为Unix时间戳
SELECT UNIX_TIMESTAMP('2023-01-01 00:00:00'); -- 1672531200
sql复制-- 获取某月的最后一天
SELECT LAST_DAY('2023-04-01'); -- '2023-04-30'
-- 获取日期所在的周数
SELECT WEEK('2023-04-01'); -- 13
sql复制-- 计算次日留存率
SELECT
COUNT(DISTINCT day2.user_id) / COUNT(DISTINCT day1.user_id) AS retention_rate
FROM
(SELECT user_id FROM user_logins WHERE DATE(login_time) = '2023-04-01') day1
LEFT JOIN
(SELECT user_id FROM user_logins WHERE DATE(login_time) = '2023-04-02') day2
ON day1.user_id = day2.user_id;
sql复制-- 生成最近7天的日期序列
SELECT
DATE_SUB(CURDATE(), INTERVAL n DAY) AS date_series
FROM
(SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6) numbers;
sql复制-- 不好的写法(无法使用索引)
SELECT * FROM orders WHERE YEAR(created_at) = 2023;
-- 好的写法(可以使用索引)
SELECT * FROM orders WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31';
sql复制-- 查看当前时区设置
SELECT @@global.time_zone, @@session.time_zone;
-- 设置会话时区
SET time_zone = '+08:00';
函数选择:对于只需要日期的场景,使用CURDATE()比NOW()更高效,因为它不需要处理时间部分。
边界条件:处理月末日期时要特别小心,因为不同月份的天数不同:
sql复制-- 安全的月末处理方式
SELECT DATE_ADD(LAST_DAY('2023-01-31'), INTERVAL 1 MONTH); -- '2023-02-28'
我在实际项目中曾遇到一个性能问题:一个统计查询需要计算过去30天的每日活跃用户数,最初使用DATE_SUB(CURDATE(), INTERVAL 1 DAY)等函数在WHERE条件中,导致全表扫描。优化后改为预先计算日期范围,性能提升了10倍以上。