在WHERE条件中对时间戳字段使用函数会导致索引失效,应改用时间范围比较,如WHERE created_at >= '2026-06-01 00:00:00' AND created_at < '2026-06-02 00:00:00'。

WHERE里别对时间戳字段用函数
只要你在 WHERE 条件里对时间戳字段套了 DATE()、FROM_UNIXTIME()、YEAR() 这类函数,索引基本就失效了——MySQL没法拿函数结果去匹配索引树里的原始值。
- 错误写法:
WHERE DATE(created_at) = '2026-06-01'→ 触发全表扫描 - 正确做法:把日期转成时间范围,直接比较字段本身,例如:
WHERE created_at >= '2026-06-01 00:00:00' AND created_at - 如果字段是
INT或BIGINT类型的 Unix 时间戳(比如created_at_ts),用UNIX_TIMESTAMP()转换边界值:WHERE created_at_ts >= UNIX_TIMESTAMP('2026-06-01') AND created_at_ts - 注意:字符串字面量(如
'2026-06-01 00:00:00')会被 MySQL 按当前会话时区解释,再转为 UTC 存储值比对;确保你的应用层和数据库时区一致,否则边界可能偏移
建索引要避开 NULL 和隐式类型转换
TIMESTAMP 字段默认允许 NULL,但 NULL 不参与 B+ 树排序,会影响 MRR 优化效果,也容易让优化器放弃使用索引。
- 建索引前先确认业务逻辑是否允许为空;若不允许,加
NOT NULL约束更稳妥 - 避免用
ALTER TABLE ADD INDEX不指定名字,后续删索引得查SHOW INDEX FROM table_name才知道名字 - 示例语句:
CREATE INDEX idx_created_at ON orders(created_at)(字段已设NOT NULL) - 别为了“看起来全面”加复合索引,比如
(status, created_at)——除非你每次查询都固定带status = 'paid'+ 时间范围,否则单列索引更轻、更易命中
EXPLAIN 必须看 key 和 type,不能只信“快了”
加完索引不验证执行计划,等于没加。重点盯两个字段:
-
type必须是range或ref,不是ALL;ALL就是没走索引 -
key列必须显示你刚建的索引名,比如idx_created_at;如果是NULL,说明优化器主动弃用了它 -
Extra出现Using filesort或Using temporary?说明排序或分组没被索引覆盖,考虑加覆盖索引(比如SELECT id, created_at FROM orders WHERE ...可建(created_at, id)) - 测试时别用
CURDATE()或NOW(),它们会让EXPLAIN的预估行数不准,改用具体时间字符串
分区表和 MRR 是加分项,不是必选项
大表(千万级以上)才值得考虑分区;MRR 在时间范围查询中默认开启,但有前提条件。
- 分区适合冷热数据分离场景,比如按月分区:
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at));但维护成本高,小表没必要 - MRR 加速依赖两点:必须走二级索引(比如你建的
created_at索引)、且查询需回表(即SELECT *或字段不在索引里);如果用了覆盖索引,MRR 不触发也没关系 -
read_rnd_buffer_size默认值太小(通常 256KB)可能限制 MRR 效果,可适当调大,但别超过 4MB,避免内存争抢
真正容易被忽略的是时区一致性与 NULL 处理——哪怕索引建对了、SQL 写对了,这两点一错,EXPLAIN 里看着走索引,实际性能还是掉档。



















