索引列参与计算还能用吗
在实际开发中,经常会遇到这样的场景:为了查询某个字段的特定格式数据,直接在 WHERE 条件里对索引列做了计算。比如,有一张订单表,create_time 字段上有索引,但想查“今天凌晨到现在过了两小时的订单”,于是写出这样的 SQL:
SELECT * FROM orders WHERE DATE(create_time) = CURDATE();看起来没问题,但这时候索引其实已经失效了。因为 DATE() 函数作用在了 create_time 列上,数据库无法直接使用该列的 B+ 树索引,只能全表扫描。
为什么不能对索引列做计算
索引的本质是排序后的数据结构,比如 B+ 树。它按照原始值排序存储,一旦你在查询时对列进行函数处理或数学运算,比如加减、DATE()、YEAR()、UPPER() 等,数据库就无法利用已有的排序规则快速定位数据。
举个生活中的例子:你有一本按拼音排序的通讯录,想找姓“张”的人很容易。但如果要求“把所有名字转成大写后再找以 Z 开头的”,那你这本拼音排序的通讯录就没法直接用了,只能一页页翻。
怎么写才能继续用索引
关键在于:把计算移到右边,让索引列保持“原样”出现在条件左侧。还是上面的例子,可以改写为:
SELECT * FROM orders WHERE create_time >= CURDATE() AND create_time < CURDATE() + INTERVAL 1 DAY;这样,create_time 没有被任何函数包裹,数据库可以直接利用索引快速定位范围。
再比如,想查某个月的数据,不要写 YEAR(create_time) = 2024 AND MONTH(create_time) = 5,而是写:
SELECT * FROM orders WHERE create_time >= '2024-05-01' AND create_time < '2024-06-01';虽然代码多了一点,但执行效率高得多。
特殊情况:函数索引
有些数据库支持函数索引,比如 PostgreSQL 和 MySQL 8.0+。你可以创建一个基于表达式的索引:
CREATE INDEX idx_date_create ON orders (DATE(create_time));这时候再用 DATE(create_time) 做查询,就能命中索引了。但这属于特例,且会增加维护成本,并不适合所有场景。
大多数情况下,最稳妥的方式还是避免在 WHERE 条件中对索引列做任何计算,把逻辑调整到常量一侧。这样既能保证性能,也兼容性更好。