WHERE中对索引列使用DATE()等函数会导致全表扫描,因MySQL无法利用索引;应改用范围查询(如created_at >= '2024-01-01 00:00:00' AND created_at < '2024-01-02 00:00:00')使优化器走索引扫描。

为什么WHERE里用DATE(created_at)会导致全表扫描
MySQL无法对函数返回值有效利用索引,比如WHERE DATE(created_at) = '2024-01-01',实际是把每行created_at都调用一次DATE()再比对——索引字段created_at本身被“隐藏”了。哪怕该列上有B+树索引,优化器也大概率放弃使用。
用范围查询替代DATE()、YEAR()等标量函数
把函数逻辑“外推”成索引可下推的区间条件,让优化器能走索引扫描而非全表扫描。
-
WHERE DATE(created_at) = '2024-01-01'→WHERE created_at >= '2024-01-01 00:00:00' AND created_at -
WHERE YEAR(created_at) = 2024→WHERE created_at >= '2024-01-01 00:00:00' AND created_at -
WHERE MONTH(created_at) = 3→ 不建议单独按月查(无法高效走索引),如必须,可结合年份:WHERE created_at >= '2024-03-01' AND created_at
遇到CONCAT()、UPPER()这类字符串函数怎么办
本质问题相同:函数包裹列后,索引失效。解决思路分两种场景:
- 若字段本身是规范存储(如邮箱全小写),就别在SQL里调
UPPER(email),改用WHERE email = LOWER(?))并确保参数已转小写 - 若要模糊前缀匹配,
WHERE UPPER(name) LIKE 'JOHN%'应改为WHERE name LIKE 'john%' COLLATE utf8mb4_0900_as_cs(注意校对规则区分大小写) - 完全避免
CONCAT(first_name, ' ', last_name)在WHERE中拼接——提前建生成列或冗余字段:ALTER TABLE users ADD full_name VARCHAR(255) GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) STORED,再在full_name上建索引
哪些情况真没法绕开函数?考虑生成列+索引
当业务逻辑确实依赖某类计算(如“最近7天活跃用户”、“按周分区统计”),又要求高频查询时,硬靠应用层拼条件不现实。这时优先用MySQL 5.7+的STORED生成列:
- 加生成列:
ALTER TABLE orders ADD order_week DATE GENERATED ALWAYS AS (DATE_SUB(order_time, INTERVAL WEEKDAY(order_time) DAY)) STORED - 建索引:
CREATE INDEX idx_order_week ON orders(order_week) - 查询直接写:
WHERE order_week = '2024-04-01'——此时order_week是真实索引字段,无函数调用
注意:生成列必须STORED(不能VIRTUAL),否则无法建索引;且表达式需确定性(不能含NOW()、RAND()等)。


















