1. MySQL索引基础与核心概念
当数据库表中的数据量达到百万级时,没有索引的查询就像在图书馆无目录的藏书堆里找一本特定书籍。MySQL索引本质上是一种特殊的数据结构,它能以空间换时间的方式大幅提升数据检索效率。
1.1 索引的物理实现原理
InnoDB存储引擎默认使用B+树结构实现索引,这种多路平衡查找树具有以下特性:
- 所有数据都存储在叶子节点,且叶子节点间通过指针相连形成链表
- 非叶子节点只存储键值和子节点指针
- 树的高度通常维持在3-4层,千万级数据也只需3次IO即可定位
B+树相比哈希索引的优势在于支持范围查询,而哈希索引仅适合等值查询。这也是为什么WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'这类条件必须使用B+树索引。
1.2 索引的类型与适用场景
主键索引(PRIMARY KEY)
ALTER TABLE users ADD PRIMARY KEY (id);- 每个表只能有一个
- 自动创建聚簇索引(InnoDB)
- 值不允许NULL且必须唯一
唯一索引(UNIQUE KEY)
CREATE UNIQUE INDEX idx_email ON users(email);- 保证列值的唯一性
- 允许NULL值(但NULL值也需唯一)
- 常用于业务唯一约束字段
普通索引(INDEX)
CREATE INDEX idx_name ON users(name);- 最基本的索引类型
- 无唯一性约束
- 适合高频查询但无需唯一的列
组合索引(Composite Index)
ALTER TABLE orders ADD INDEX idx_composite (user_id, status, create_time);- 多列联合构建的索引
- 遵循最左前缀原则
- 字段顺序直接影响索引效果
注意:全文索引(FULLTEXT)在MySQL 5.6+的InnoDB上才支持,适用于文本内容的模糊搜索,但性能不如专业搜索引擎。
2. 索引创建实战指南
2.1 单列索引创建标准流程
步骤1:分析查询模式
-- 查看慢查询日志中的TOP SQL SHOW VARIABLES LIKE 'slow_query_log%';步骤2:验证字段选择性
-- 计算字段的选择性(越接近1越好) SELECT COUNT(DISTINCT status)/COUNT(*) AS selectivity FROM orders;步骤3:创建索引
-- 基本语法 CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name ON tbl_name (col_name [(length)] [ASC|DESC],...) [INDEX_TYPE] [WITH PARSER parser_name] [COMMENT 'string'] -- 实际示例(为status字段创建普通索引) CREATE INDEX idx_status ON orders(status);步骤4:验证索引效果
-- 使用EXPLAIN分析执行计划 EXPLAIN SELECT * FROM orders WHERE status = 'shipped';2.2 组合索引设计黄金法则
法则1:最左前缀原则
-- 对于INDEX(a,b,c): WHERE a=1 AND b=2 -- 能使用索引 WHERE b=2 AND c=3 -- 不能使用索引法则2:基数高的列放左边
-- 用户表有gender(基数2)和age(基数70) -- 应该将age放在更左侧 CREATE INDEX idx_age_gender ON users(age, gender);法则3:覆盖索引优化
-- 设计包含所有查询字段的索引 CREATE INDEX idx_covering ON orders(user_id, status, amount); -- 这样查询就无需回表 SELECT user_id, status FROM orders WHERE user_id=100;2.3 特殊场景索引创建
前缀索引(大文本字段)
-- 为VARCHAR(255)的address字段创建前20字符索引 CREATE INDEX idx_address ON customers(address(20));函数索引(MySQL 8.0+)
-- 为日期格式化查询创建函数索引 CREATE INDEX idx_date_format ON orders((DATE_FORMAT(create_time,'%Y-%m')));降序索引(MySQL 8.0+)
-- 针对时间倒序查询场景 CREATE INDEX idx_time_desc ON logs(create_time DESC);3. 索引优化与避坑指南
3.1 索引失效的七大陷阱
- 隐式类型转换
-- user_id是VARCHAR但传入数字 SELECT * FROM users WHERE user_id = 100; -- 索引失效- 使用否定操作符
SELECT * FROM products WHERE status != 'active'; -- 无法走索引- 前导通配符
SELECT * FROM articles WHERE title LIKE '%mysql%'; -- 全表扫描- 索引列参与运算
SELECT * FROM orders WHERE YEAR(create_time) = 2023; -- 函数导致失效- OR条件使用不当
-- 解决方案:改为UNION ALL SELECT * FROM users WHERE age=18 OR name='张三';- 违反最左前缀原则
-- 对于INDEX(a,b,c): SELECT * FROM table WHERE b=1 AND c=2; -- 无法使用索引- 统计信息过时
-- 定期执行ANALYZE TABLE更新统计信息 ANALYZE TABLE orders;3.2 索引维护最佳实践
监控索引使用率
-- 查看未使用的索引 SELECT * FROM sys.schema_unused_indexes;定期重建碎片化索引
-- InnoDB表的索引重建 ALTER TABLE orders ENGINE=InnoDB;控制索引数量
-- 单个表索引建议不超过5-6个 -- 可通过以下查询评估现有索引 SELECT table_name, index_name, stat_value FROM mysql.innodb_index_stats WHERE stat_name='size' AND database_name='your_db';4. 高级索引应用场景
4.1 分页查询优化方案
低效写法(深分页问题)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;优化方案1:延迟关联
SELECT t.* FROM orders t JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10) tmp ON t.id = tmp.id;优化方案2:基于上次ID
-- 假设上次获取的最大ID是999990 SELECT * FROM orders WHERE id > 999990 ORDER BY id LIMIT 10;4.2 索引合并优化
当查询条件涉及多个独立索引时,MySQL可能使用Index Merge:
-- 需要分别在last_name和first_name上有索引 EXPLAIN SELECT * FROM employees WHERE last_name='Smith' OR first_name='John';可通过optimizer_switch控制此行为:
SET optimizer_switch='index_merge=off'; -- 禁用索引合并4.3 自适应哈希索引
InnoDB会自动为频繁访问的索引页建立哈希索引:
-- 查看AHI使用情况 SHOW ENGINE INNODB STATUS\G -- 在Buffer Pool部分查找"Hash table size"可通过参数调整:
innodb_adaptive_hash_index=ON -- 默认开启 innodb_adaptive_hash_index_parts=8 -- 分区数(减少锁争用)5. 生产环境索引管理策略
5.1 在线DDL操作
MySQL 5.6+的Online DDL
-- 添加索引而不锁表(INPLACE算法) ALTER TABLE orders ADD INDEX idx_amount (amount), ALGORITHM=INPLACE, LOCK=NONE;pt-online-schema-change工具
pt-online-schema-change \ --alter="ADD INDEX idx_email (email)" \ D=your_db,t=users \ --execute5.2 索引变更风险评估
使用EXPLAIN验证
-- 先获取当前执行计划作为基准 EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE status='completed';使用optimizer_trace分析
SET optimizer_trace="enabled=on"; SELECT * FROM orders WHERE amount > 1000; SELECT * FROM information_schema.optimizer_trace; SET optimizer_trace="enabled=off";5.3 索引生命周期管理
索引版本控制
-- 使用注释记录索引创建目的 CREATE INDEX idx_phone ON customers(phone) COMMENT '用于登录验证 创建于2023-08 by 张工程师';定期审查脚本
-- 生成索引审查报告 SELECT table_name, index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS columns, index_type, CASE non_unique WHEN 1 THEN 'YES' ELSE 'NO' END AS is_unique, index_comment FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY table_name, index_name;在多年的MySQL优化实践中,我发现最容易被忽视的是索引的"维护成本"——每个索引都会降低INSERT/UPDATE/DELETE性能。建议每月进行一次索引使用率审查,对于三个月内未被使用的索引,应该考虑删除。同时,组合索引的字段顺序需要根据实际查询模式不断调整,这是一个持续优化的过程。