1. MySQL SQL执行规则深度解析
1.1 SQL语句的生命周期
当一条SQL语句进入MySQL服务器时,会经历完整的处理流程:
- 连接器:建立连接并验证权限
- 查询缓存(MySQL 8.0已移除):检查是否命中缓存
- 分析器:语法解析和词法分析
- 优化器:生成执行计划
- 执行器:调用存储引擎接口执行
- 结果返回:将结果集返回客户端
重要提示:MySQL 8.0移除了查询缓存功能,因为在实际生产环境中命中率往往低于预期,且维护缓存带来的开销大于收益。
1.2 执行计划详解
通过EXPLAIN命令可以查看SQL的执行计划,关键列包括:
| 列名 | 说明 | 优化关注点 |
|---|---|---|
| type | 访问类型 | 最好达到range级别 |
| key | 实际使用的索引 | 检查是否使用预期索引 |
| rows | 预估扫描行数 | 数值越小越好 |
| Extra | 额外信息 | 避免出现"Using filesort" |
常见的type类型从优到劣排序:
- system > const > eq_ref > ref > range > index > ALL
1.3 优化器工作原理
MySQL优化器基于成本模型选择执行计划,考虑因素包括:
- 索引选择性
- 表统计信息
- 临时表创建成本
- 排序操作开销
优化器有时会做出错误决策,这时可以通过以下方式干预:
-- 强制使用特定索引 SELECT * FROM table FORCE INDEX(index_name) WHERE ... -- 忽略特定索引 SELECT * FROM table IGNORE INDEX(index_name) WHERE ...2. MySQL索引核心原理
2.1 B+Tree索引结构
MySQL索引采用B+Tree数据结构,具有以下特点:
- 所有数据存储在叶子节点,非叶子节点只存键值
- 叶子节点通过指针连接形成链表
- 树的高度通常为3-4层(可支撑千万级数据)
与B-Tree的对比优势:
- 范围查询效率更高
- 磁盘IO次数更少
- 更适合数据库场景
2.2 索引类型详解
2.2.1 主键索引(Clustered Index)
- 数据按主键物理排序存储
- 每个表必须有且只有一个主键索引
- 建议使用自增整型作为主键
2.2.2 二级索引(Secondary Index)
- 叶子节点存储主键值而非数据
- 查询需要回表操作
- 包括普通索引、唯一索引等
2.2.3 复合索引
- 遵循最左前缀原则
- 索引列顺序影响使用效果
- 示例:INDEX(a,b,c)可以支持a、a,b、a,b,c查询
2.3 索引使用场景
适合创建索引的情况:
- WHERE条件中的列
- JOIN关联列
- ORDER BY排序字段
- 高选择性字段(区分度高)
不建议创建索引的情况:
- 频繁更新的列
- 数据量小的表
- 区分度低的列(如性别)
3. 高级索引优化策略
3.1 索引下推(ICP)
MySQL 5.6引入的优化技术,允许在存储引擎层过滤数据:
-- 传统方式:先通过name索引找到所有'张%'记录,再回表检查age>30 -- 使用ICP:在索引层就过滤name和age,减少回表次数 SELECT * FROM users WHERE name LIKE '张%' AND age > 30;启用条件:
- 需要range、ref、eq_ref或ref_or_null访问方式
- 仅适用于InnoDB和MyISAM引擎
3.2 覆盖索引优化
当查询所需列都包含在索引中时,可以避免回表:
-- 创建复合索引 ALTER TABLE orders ADD INDEX idx_order(date, customer_id, amount); -- 以下查询可以直接使用索引数据 SELECT date, customer_id, amount FROM orders WHERE date BETWEEN '2023-01-01' AND '2023-01-31';3.3 MRR优化
Multi-Range Read优化,通过以下方式提升性能:
- 收集索引扫描得到的主键
- 按主键顺序排序
- 批量回表读取数据
启用设置:
SET optimizer_switch='mrr=on,mrr_cost_based=off';4. 索引失效场景与排查
4.1 常见索引失效情况
违反最左前缀原则
-- 索引:INDEX(a,b,c) WHERE b = 1 AND c = 2 -- 无法使用索引使用函数或运算
WHERE YEAR(create_time) = 2023 -- 索引失效类型转换
-- phone字段是varchar类型 WHERE phone = 13800138000 -- 隐式类型转换导致失效使用OR条件
WHERE a = 1 OR b = 2 -- 除非a和b都有索引
4.2 索引使用情况监控
通过performance_schema监控索引使用:
-- 查看未使用的索引 SELECT * FROM sys.schema_unused_indexes; -- 查看索引统计信息 SELECT * FROM mysql.innodb_index_stats WHERE table_name = 'your_table';5. 实战索引优化案例
5.1 电商订单查询优化
原始查询:
SELECT * FROM orders WHERE user_id = 1001 AND status = 'completed' ORDER BY create_time DESC LIMIT 10;优化方案:
- 创建复合索引:(user_id, status, create_time)
- 使用覆盖索引技巧:
SELECT id FROM orders WHERE user_id = 1001 AND status = 'completed' ORDER BY create_time DESC LIMIT 10; -- 然后通过主键获取完整数据(减少排序数据量) SELECT * FROM orders WHERE id IN (...);5.2 分页查询优化
低效写法:
SELECT * FROM large_table LIMIT 1000000, 10;优化方案:
- 使用索引覆盖+延迟关联
SELECT t.* FROM large_table t JOIN ( SELECT id FROM large_table WHERE some_indexed_column = 'value' ORDER BY id LIMIT 1000000, 10 ) AS tmp ON t.id = tmp.id;- 记录上次查询位置(适合有序数据)
-- 记住上一次查询的最后一个ID SELECT * FROM large_table WHERE id > last_id ORDER BY id LIMIT 10;6. MySQL索引维护与管理
6.1 索引碎片整理
随着数据修改,索引会产生碎片,影响性能:
-- 查看表碎片情况 SELECT table_name, data_free/1024/1024 AS frag_mb FROM information_schema.tables WHERE engine = 'InnoDB'; -- 重建表(会锁表) ALTER TABLE orders ENGINE=InnoDB; -- 在线DDL方式(MySQL 5.6+) ALTER TABLE orders ALGORITHM=INPLACE, LOCK=NONE, REBUILD;6.2 索引统计信息更新
优化器依赖统计信息做决策:
-- 手动更新统计信息 ANALYZE TABLE orders; -- 查看统计信息 SHOW INDEX FROM orders;6.3 索引设计最佳实践
- 单表索引数量建议不超过5-6个
- 复合索引列数不超过5列
- 避免冗余索引(如已有(a,b)索引,再建a索引就是冗余)
- 定期使用pt-index-usage工具分析索引使用情况
实际经验:在大型电商系统中,通过合理索引设计,我们曾将关键查询从2秒优化到50毫秒以下。关键在于理解业务查询模式,而不是盲目添加索引。