MySQL中查本周/本月数据应避免函数包裹字段导致索引失效,正确做法是用时间范围查询:本周用DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)计算周一,本月用DATE_FORMAT(NOW(), '%Y-%m-01')和LAST_DAY(NOW());同时需统一时区,防止NOW()返回时间偏移。

MySQL中用WEEK()和YEARWEEK()查本周数据容易出错
直接用WEEK(NOW())对比WEEK(create_time)看似合理,但默认模式下周起始日是周日,且跨年时WEEK()会重置为0,导致去年12月最后一周被算成“本周”。更稳妥的方式是用YEARWEEK(create_time, 1)和YEARWEEK(NOW(), 1)——第二个参数1表示周一为每周开始、且第1周包含4天以上的一周(ISO标准)。
实操建议:
- 统一使用
YEARWEEK(col, 1)做比较,避免跨年断层 - 若业务要求“自然周”(周日~周六),改用
YEARWEEK(col, 0),但需额外判断是否跨年 - 索引有效前提:字段
create_time需是DATETIME或TIMESTAMP类型,且有普通索引;函数包裹列(如YEARWEEK(create_time))无法走索引,应改写为范围查询(见下一条)
真正能走索引的“本周数据”写法是时间范围查询
用函数提取周信息会丢失索引能力。正确做法是先算出本周一00:00和下周一是00:00两个时间点,再用BETWEEN。
示例(查本周一到周日的数据):
SELECT * FROM orders WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) AND create_time < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY);
说明:
-
WEEKDAY(CURDATE())返回周一=0、周日=6,所以DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)恒为本周一 - 右边界用
<而非<=,避免重复计入下周第一秒 - 如果表里
create_time是DATE类型(无时分秒),可简化为create_time BETWEEN '2024-06-10' AND '2024-06-16'
查本月数据别只用MONTH()和YEAR()
写WHERE MONTH(create_time) = MONTH(NOW()) AND YEAR(create_time) = YEAR(NOW())会导致全表扫描——因为对字段用了函数。同样应转为范围查询。
实操建议:
- 用
DATE_FORMAT(NOW(), '%Y-%m-01')得本月第一天,再用LAST_DAY(NOW())得本月最后一天 - 完整条件:
create_time >= DATE_FORMAT(NOW(), '%Y-%m-01') AND create_time <= LAST_DAY(NOW()) - 注意
LAST_DAY()返回的是当日23:59:59,若字段含时分秒且业务要求严格到秒,建议改用< DATE_ADD(DATE_FORMAT(NOW(), '%Y-%m-01'), INTERVAL 1 MONTH)作为右边界
时区问题常被忽略,尤其在跨地域部署时
如果MySQL服务器时区和业务所在时区不一致(例如服务器设为UTC,而业务在中国),NOW()返回的是UTC时间,会导致“本周/本月”计算偏移8小时。
检查与修正方法:
- 查当前会话时区:
SELECT @@session.time_zone; - 临时设置(当前连接生效):
SET time_zone = '+08:00'; - 长期方案:在
my.cnf中配置default-time-zone = '+08:00',并确保应用层不覆盖该设置 - 若无法改服务器配置,所有时间计算统一用
CONVERT_TZ(NOW(), '+00:00', '+08:00')转换后再参与比较
时间范围类查询一旦涉及时区,就不再是单纯拼SQL的事——它和你的服务器配置、连接初始化逻辑、甚至ORM默认行为都绑在一起。


















