函数操作破坏b+树有序性,因索引存储原始值而函数计算需逐行执行,导致无法利用索引跳转、被迫全表扫描;正确做法是用范围查询替代函数,如create_time >= '2023-01-01 00:00:00' and create_time
函数操作破坏B+树的有序性
B+树索引能快速定位,靠的是叶子节点按原始值严格排序。一旦在索引列上套函数(比如
YEAR(create_time)或UPPER(name)),MySQL 就得对每一行先算出函数结果,再比对——而索引里存的不是这些计算值,是原始值。树的“目录”根本没法跳转,只能全表扫描。WHERE中写
DATE(create_time) = '2023-01-01'为什么不行这个查询等价于逐行执行
DATE(),然后判断是否等于字符串。B+树无法预知哪一行的DATE()结果是 '2023-01-01',因为日期范围被“打散”了。
- ✅ 正确写法:
create_time >= '2023-01-01 00:00:00' AND create_time- ⚠️ 注意:
BETWEEN包含边界,容易漏掉毫秒级数据,推荐用左闭右开区间- ? 验证方式:用
EXPLAIN看key是否非空、type是否为range或refMySQL 8.0+ 的函数索引不是万能解药
虽然可以建
INDEX idx_year ON t ((YEAR(create_time))),但要注意:
- 该索引只对
YEAR(create_time)这个**完全一致的表达式**生效,写成YEAR(create_time)+0就不走- 函数索引字段不能是主键或唯一约束的一部分(除非显式声明
STORED)- 维护成本更高:每次更新
create_time,都要同步更新函数索引项- 低版本(
隐式转换和函数操作本质是一回事
比如
WHERE phone = 13800138000(phone是VARCHAR),MySQL 实际执行的是CAST(phone AS SIGNED)——这和显式调用函数没区别,都是在索引列上做了一次不可索引的转换。真正容易被忽略的是:哪怕你写的 SQL 看起来“没动字段”,只要优化器内部做了隐式转换或表达式重写(比如某些 JOIN 条件推导),索引也可能悄悄失效。别只信自己写的,要信
- 最稳妥的做法:参数类型必须和字段定义完全一致
- ORM 框架里尤其容易踩坑,比如 MyBatis 的
#{}默认不带引号,${}又有注入风险,得结合字段类型配置 typeHandler- 上线前跑一遍
SHOW WARNINGS,看有没有隐式转换提示EXPLAIN FORMAT=TREE里那一行真实的执行计划。












