DATE(created_at)查询无法使用普通索引是因为函数操作破坏了索引的有序性,MySQL 8.0起可用表达式索引CREATE INDEX idx_created_date ON orders ((DATE(created_at)))解决,但仅匹配完全一致的DATE(created_at)写法。

为什么 DATE(created_at) 查询走不了普通索引?
因为 DATE() 是函数,对索引列 created_at 做了计算,MySQL 无法直接用 B+ 树索引做范围匹配。即使 created_at 上有索引,执行 WHERE DATE(created_at) = '2024-03-15' 也会触发全表扫描或索引全扫。
MySQL 8.0 支持表达式索引(也叫函数索引),允许你把常用表达式结果持久化进索引结构,绕过运行时计算开销。
怎么创建 DATE(created_at) 的表达式索引?
语法很简单,但必须注意两点:列名要加括号、索引名建议带语义:
CREATE INDEX idx_created_date ON orders ((DATE(created_at)));
关键细节:
-
DATE(created_at)外层必须用一对圆括号包裹,否则报错ERROR 1064 (42000): You have an error in your SQL syntax - 不能写成
INDEX idx_created_date (DATE(created_at))—— 括号必须在表达式外侧,不是列定义位置 - 该索引只加速
DATE(created_at) = ?、DATE(created_at) IN (?)、DATE(created_at) BETWEEN ? AND ?这类等值或离散范围查询,不支持>/对日期部分的范围扫描(比如查“3月以后”仍需原列索引) - 表达式索引会额外占用磁盘空间,且 INSERT/UPDATE 时需维护,高写入场景要权衡
查询时必须严格匹配表达式写法才能命中
索引是按你定义的表达式构建的,优化器不会自动“推导等价”。下面这些写法 都不会命中 刚才建的 idx_created_date:
WHERE created_at >= '2024-03-15' AND created_at —— 推荐用这个,走原created_at索引更高效-
WHERE DATE(created_at + INTERVAL 0 DAY) = '2024-03-15'—— 多了个无意义计算,表达式不一致 -
WHERE CAST(created_at AS DATE) = '2024-03-15'—— 函数不同,不算同一表达式
只有完全一致的 DATE(created_at) 才能命中。可以用 EXPLAIN 验证:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
EXPLAIN SELECT * FROM orders WHERE DATE(created_at) = '2024-03-15';
看到 key 列显示 idx_created_date 才算成功。
替代方案:用生成列 + 普通索引更灵活?
如果业务中既要查“某天”,又要查“某天之后”“某月内”,表达式索引覆盖不全。这时可以加一个虚拟生成列:
ALTER TABLE orders ADD COLUMN created_date DATE AS (DATE(created_at)) STORED;
然后给它建普通索引:
CREATE INDEX idx_created_date_col ON orders (created_date);
好处是:
- 可同时支持
WHERE created_date = ?和WHERE created_date >= ? AND created_date - 生成列能被
ORDER BY、GROUP BY直接引用,SQL 更直观 - 缺点是多占一点存储(
STORED类型),而表达式索引不新增列
实际选哪个,取决于你是否需要范围扫描能力——如果只是大量“查某天订单”,表达式索引更轻量;如果查询模式多样,生成列更稳。










