对索引列使用函数会导致索引失效,因破坏b+树有序性;应将函数移至等号右侧或改用范围查询,如where name = upper('john')或where created_at >= '2024-01-01' and created_at
WHERE 条件里对索引列用函数会直接让索引失效
MySQL、PostgreSQL、SQL Server 都一样:
WHERE UPPER(name) = 'JOHN'或WHERE DATE(created_at) = '2024-01-01'这类写法,即使name或created_at有索引,优化器也大概率放弃使用。因为函数改变了原始值的有序性,B+ 树无法做范围跳查。实操建议:
- 把函数移到等号右边:用
WHERE name = UPPER('john')替代UPPER(name) = 'JOHN'(前提是大小写不敏感 collation)- 日期范围改用闭区间:用
WHERE created_at >= '2024-01-01' AND created_at 替代 <code>DATE(created_at) = '2024-01-01'- 实在要函数转换,考虑生成列 + 索引:PostgreSQL 支持
CREATE INDEX ON t ((UPPER(name)));MySQL 5.7+ 可建函数索引列ALTER TABLE t ADD COLUMN name_upper VARCHAR(64) STORED AS (UPPER(name)),再给该列加索引UPDATE 语句中 SET 子句对索引列赋值可能触发额外索引维护
比如
UPDATE users SET status = 'active', updated_at = NOW() WHERE id = 123,如果updated_at是二级索引的一部分(比如复合索引(status, updated_at)),每次更新都会导致该索引页分裂或重排,尤其在高并发写场景下明显拖慢性能。实操建议:
- 避免在
SET中无谓更新索引列:确认字段值是否真有变化,可用WHERE status != 'active' AND id = 123提前过滤- 把高频更新字段和低频查询字段拆到不同索引中:别把
updated_at和常用于WHERE的status绑死在一个复合索引里- 对时间戳字段,优先用
CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP自动更新,减少 SQL 层显式赋值WHERE 条件含隐式类型转换也会绕过索引
典型错误:
WHERE user_id = '123',而user_id是INT类型。MySQL 会把所有user_id值转成字符串比对,索引失效;PostgreSQL 则直接报错或拒绝隐式转换(取决于配置)。实操建议:
- 参数类型必须和字段类型严格一致:应用层传参时别用字符串拼接数字 ID,用预编译参数绑定
- 检查执行计划:MySQL 用
EXPLAIN看type是否为const/ref,而非ALL或index;PostgreSQL 用EXPLAIN ANALYZE看是否出现Seq Scan- 字符串字段用
COLLATE显式声明排序规则,避免因连接字符集不同引发隐式转换批量 UPDATE 没控制范围容易锁表或超时
一条
UPDATE orders SET status = 'shipped' WHERE created_at 如果匹配百万行,在未加 LIMIT 或未分批次时,可能持有行锁/间隙锁过久,阻塞其他事务,甚至触发锁等待超时(<code>Lock wait timeout exceeded)。实操建议:
- 永远带
LIMIT+ 循环更新:先SELECT id FROM orders WHERE created_at ,再用这批 <code>id做UPDATE ... WHERE id IN (...)- WHERE 条件尽量走主键或唯一索引:避免全表扫描式更新,否则
LIMIT也救不了性能- 在低峰期执行,同时设置会话级超时:
SET innodb_lock_wait_timeout = 10(MySQL),防止一个长事务拖垮整个库最常被忽略的是:开发阶段用小数据量测不出问题,上线后数据量一上去,函数索引失效、隐式转换、批量锁表就全冒出来——不是 SQL 写错了,是它在生产环境根本没走你预期的索引路径。











