使用函数如year(created_at)=2023会导致索引失效,即使explain显示走索引;应改用范围查询created_at>='2023-01-01' and created_at

WHERE 条件里用函数导致索引失效
查得慢,但 EXPLAIN 显示走了索引?大概率是写了类似 WHERE YEAR(created_at) = 2023 这种。MySQL 无法对函数结果复用索引,哪怕 created_at 有 B+ 树索引,也会退化为全表扫描。
改成范围查询:用 WHERE created_at >= '2023-01-01' AND created_at ,索引就能真正生效。
- 所有对字段加函数的操作(
UPPER()、DATE()、CONCAT())都可能触发这个问题 - 如果必须用函数过滤,考虑在生成列上建索引(MySQL 5.7+):
ALTER TABLE t ADD COLUMN created_year INT AS (YEAR(created_at)) STORED,再对created_year建索引 - PostgreSQL 对函数索引支持更好,可直接
CREATE INDEX ON t (upper(name)),但 MySQL 不行
JOIN 顺序不对让执行计划崩掉
写多表 JOIN 时,别以为优化器总能选对驱动表。尤其当小表没走主键、大表没走索引,或关联字段类型不一致时,EXPLAIN 里常看到 type: ALL 或 rows 高得离谱。
显式控制驱动顺序:用 STRAIGHT_JOIN(MySQL),把确定的小表放前面,强制它先查;或者把大表的关联条件提前写进 ON,而不是塞到 WHERE 末尾。
- 检查
JOIN字段是否同类型——INT和VARCHAR关联会隐式转换,索引失效 -
LEFT JOIN的右表如果没加WHERE限制,可能被当成“必须全扫”,哪怕你只想要左表某几条 - 用
EXPLAIN FORMAT=JSON看used_columns和key_length,确认实际用了索引哪几列
ORDER BY + LIMIT 在深分页场景下卡死
SELECT * FROM orders ORDER BY id DESC LIMIT 100000, 20 这类语句,MySQL 得先排序 100020 行,再扔掉前 100000 条。数据量一上十万,响应就秒变分钟级。
WebShop网上商店系统专注中小企业、个人的网上购物电子商务解决方案,淘宝商城系统用户/个人首选开店的购物系统!综合5500多用户的意见或建议,从功能上,界面美观上,安全性,易用性上等对网店系统进行了深度的优化,功能更加强大,界面模板可直接后台选择。WebShop网上商店系统特点:1 对于中小企业、个体、个人、店主和淘宝易趣等卖家,可利用WebShop快速建立购物网。2 源代码开放,利用WebS
改用游标分页:记录上一页最后的 id 值,下一页查 WHERE id 。前提是排序字段有索引且唯一(或组合唯一)。
- 别依赖
OFFSET做后台导出——导出全量时直接去掉LIMIT,用流式读取 - 如果排序字段不唯一(比如按时间),用
(created_at, id)组合做游标,避免漏/重 - MyISAM 表在
ORDER BY时容易临时文件溢出磁盘,InnoDB 更稳,但也要注意sort_buffer_size设置过小会频繁归并
统计 COUNT(*) 却没走索引覆盖
查总行数写 SELECT COUNT(*) FROM t,结果 EXPLAIN 显示 type: ALL?说明表没走索引,而是扫了聚簇索引整棵树。InnoDB 不像 MyISAM 那样缓存行数,每次都要算。
优先走二级索引:只要存在非空列的二级索引(比如 status),就用 COUNT(status);更稳妥的是建一个最小覆盖索引:CREATE INDEX idx_dummy ON t (id),然后 COUNT(id) 就能只扫这棵轻量索引树。
-
COUNT(*)和COUNT(1)在 InnoDB 中性能一致,都是扫索引;但COUNT(col)会跳过NULL,别误用 - 如果只是估算总数,
SHOW TABLE STATUS LIKE 't'的Rows字段够用,误差在 40% 内,但不准用于业务逻辑判断 - 分区表要注意:
COUNT(*)会跨分区汇总,比单表更耗时,别在未加WHERE的分区键上盲目 COUNT
最麻烦的不是慢,是慢得没征兆——比如某个 JOIN 条件字段悄悄加了空格,或字符集从 utf8mb4 换成 utf8 导致隐式转换。这类问题不会报错,只会在高峰期突然拖垮整个服务。










