索引优化必须基于表结构设计来展开
单独建索引却忽略字段类型、长度、是否允许 NULL、主键选择等,大概率会失效或拖慢写入。MySQL 的
EXPLAIN显示
type=ALL或
key=NULL,往往不是索引没建,而是字段定义和索引不匹配。
VARCHAR(255)上建前缀索引要小心:如果业务常查前 10 字符,
INDEX(col(10))可行;但若
WHERE col = 'xxx'是完整值匹配,而索引只截了前 5 位,就无法用上 联合索引顺序必须贴合查询条件顺序:
INDEX(a, b, c)能加速
WHERE a=1 AND b=2,但对
WHERE b=2 AND c=3无效
TINYINT和
ENUM做状态字段时,如果经常
ORDER BY status DESC,记得在索引里包含该字段并显式声明
DESC(MySQL 8.0+ 支持)
NULL 值处理直接影响索引可用性
MySQL 中,含
NULL的列在 B+ 树索引中会被特殊存储,导致部分场景下无法使用索引做范围扫描或排序。比如
status TINYINT NULL,即使加了
INDEX(status),
WHERE status != 1也可能走全表扫描——因为
NULL != 1恒为 true,优化器不敢依赖索引过滤。 能用
NOT NULL就别留空:除非业务语义明确需要“未知”,否则默认设
NOT NULL DEFAULT 0查询中避免对可空字段用
!=或
NOT IN,改用
IS NOT NULL AND status != 1显式排除 联合索引里尽量把可能为
NULL的字段放后面,否则前导列一旦为
NULL,整条索引路径就断了
字符集与排序规则影响索引比较逻辑
同一个
VARCHAR字段,在
utf8mb4_general_ci和
utf8mb4_0900_as_cs下,索引的匹配行为完全不同。大小写敏感(
_cs)或重音敏感(
_as)会导致隐式转换,让索引失效。 查拼音首字母时,
WHERE name LIKE 'Z%'在
_ci排序下能走索引;但在
_cs下若数据存的是小写
zhang,就匹配不上 联表字段字符集不一致(如 A 表用
utf8mb4_unicode_ci,B 表用
utf8mb4_general_ci),即使都有索引,
JOIN也可能放弃使用索引,转为 Block Nested-Loop 新建表统一用
utf8mb4_0900_as_cs(MySQL 8.0+),避免旧排序规则的模糊匹配陷阱
大字段(TEXT/BLOB)和虚拟列对索引的限制
MySQL 不允许直接对
TEXT或
BLOB列建普通索引,也不支持在它们上面建函数索引(如
JSON_EXTRACT()结果)。但可通过生成列(generated column)绕过。 想对 JSON 字段里的
$.user_id建索引?先加生成列:
user_id INT AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.user_id'))) STORED,再对
user_id建索引
TEXT字段需前缀索引时,长度不能超 3072 字节(InnoDB 页面限制),且前缀长度必须是字符数,不是字节数——
utf8mb4下一个汉字占 4 字节,
INDEX(content(100))实际最多覆盖 100 个字符,不是 100 字节 虚拟列(
STORED)会占用磁盘空间,但能被索引和
EXPLAIN正确识别;
VIRTUAL列不存盘,但多数版本不支持对其建索引 实际调优时,最常被跳过的动作是:改完表结构后忘记
ANALYZE TABLE更新统计信息。InnoDB 的索引选择严重依赖这些数据,旧统计可能导致优化器坚持走错路。
