一、第一步:精准定位"待优化查询"
优化的前提是"找到问题查询",而非盲目优化。需结合日志与工具,筛选出"影响最大"的查询,优先处理高价值目标。
- 发现慢查询:日志与工具双管齐下
(1)日志定位:常规查询日志 vs 慢速查询日志
MySQL通过两种日志记录查询行为,核心关注慢速查询日志(聚焦低效查询):
| 日志类型 | 作用 | 配置与使用 |
| 常规查询日志 | 记录所有SQL请求(SELECT/INSERT/UPDATE等),用于审计或全量查询追踪| 配置:`general_log=ON`,日志路径`general_log_file=/var/log/mysql/general.log`;<br>注意:生产环境慎用(日志量大),仅临时排查时开启。 |
| 慢速查询日志 | 记录执行时间超过`long_query_time`(默认1秒)的查询,是优化核心依据 | 配置:`slow_query_log=ON`,`long_query_time=1`(可设为0.1秒捕捉更多慢查询),日志路径`slow_query_log_file=/var/log/mysql/slow.log`;<br>关键参数:`log_queries_not_using_indexes=ON`(记录未用索引的查询)。 |
(2)实时工具:快速捕捉活跃慢查询
-
SHOW PROCESSLIST`:查看当前活跃连接的SQL执行状态,识别"长期阻塞"或"耗时查询": SHOW PROCESSLIST\G
-
关键字段:`Time`(执行时间,超过10秒需关注)、`State`(状态,如`Sending data`可能是慢查询)、`Info`(具体SQL)。
-
`sys.statement_analysis`:查看"规范化查询"的聚合统计(如执行次数、总耗时),定位高频查询: SELECT query, exec_count, total_latency FROM sys.statement_analysis ORDER BY total_latency DESC LIMIT 10;
-
优势:将相似查询(如`WHERE id=1`和`WHERE id=2`)合并,避免重复统计。
- 选择优化对象:不是"最慢"而是"影响最大"
优化的核心原则:优先优化"执行频繁的查询",而非仅关注"单次最慢的查询"。
二、第二步:用EXPLAIN剖析"查询执行计划"
`EXPLAIN`是MySQL查询优化的"显微镜"------它不执行SQL,仅输出优化器的"执行计划",帮你判断查询是否用对索引、是否有全表扫描等问题。
- EXPLAIN基本用法
支持`SELECT`/`INSERT`/`UPDATE`/`DELETE`语句,语法简单:
-- 分析SELECT语句:EXPLAIN SELECT * FROM employees WHERE emp_no=10001\G
-- 分析UPDATE语句:EXPLAIN UPDATE salaries SET salary=50000 WHERE emp_no=10001\G
- EXPLAIN输出字段详解(核心必看)
`EXPLAIN`输出12个关键字段,每个字段都对应优化的突破口,重点关注标红部分:
| 字段名 | 含义与优化价值 |
|`id`| 查询语句的编号,复杂查询(子查询、JOIN)会有多行不同id,标识表的处理顺序。|
| `select_type` | 查询类型:<br>- `SIMPLE`:无子查询/UNION;<br>- `SUBQUERY`:子查询;<br>- `UNION`:UNION语句;<br>无需优化,仅用于理解查询结构。 |
| `table` | 该计划对应的表名,JOIN查询会有多行(对应不同表)。 |
| `type` | 索引/连接类型,最核心的性能指标,从优到差排序:<br>`const`(主键/唯一键匹配常量)>`eq_ref`(JOIN时主键匹配)>`ref`(非唯一索引匹配)>`range`(索引范围扫描)>`index`(全索引扫描)>`ALL`(全表扫描)。 |
| `possible_keys` | 优化器认为"可能有用"的索引,不代表实际使用。 |
| key` | 优化器"实际使用"的索引,若为`NULL`,表示未用索引(需优化)。 |
| `key_len` | 实际使用的索引长度(字节),越长表示使用的索引列越多(如联合索引用了前2列)。 |
| `ref` | 与索引对比的"参考值":<br>- `const`:常量(如`WHERE id=1`);<br>- `表名.列名`:JOIN时的关联列。 |
| `rows` | 优化器预估的"扫描行数",数值越小越好(全表扫描时等于表总行数)。 |
| `filtered` | 按WHERE条件筛选后的行百分比(%),越高表示筛选效果越好。 |
| `Extra` | 额外信息,高频优化点:<br>- `Using filesort`:需优化(用索引避免排序);<br>- `Using temporary`:需优化(用索引避免临时表);<br>- `Using index`:最优(覆盖索引,无需回表);<br>- `Using where`:需结合索引判断是否全表扫描。 |
三、第三步:索引优化------查询性能的"加速器"
索引是解决"慢查询"的核心手段,但需选对类型、正确创建,避免"无效索引"浪费资源。
- MySQL支持的索引类型
不同场景需用不同索引,避免一概而论:
| 索引类型 | 特点与适用场景 |
| 非唯一索引 | 默认类型,值可重复;适用于`WHERE`条件查询(如`WHERE dept_no='d001'`)。 |
| 唯一索引 | 值唯一(允许NULL);适用于`WHERE`条件+唯一性约束(如`WHERE email='a@xxx.com'`)。 |
| 主键索引 | 特殊的唯一索引,值非空;每个表仅1个,用于唯一标识行(如`emp_no`)。|
| 全文索引 | 针对字符串的全文搜索(如`MATCH(name) AGAINST('apple')`);不适用于精确匹配。 |
| 空间索引 | 针对空间数据类型(如`GEOMETRY`、`POINT`);适用于地理信息查询 |
- 索引的核心作用(3大场景)
索引不是"万能的",但能解决90%的慢查询,核心支持三类操作:
-
直接值匹配:精确查找(如`WHERE id=1`),通过索引直接定位行;
-
存在性检查:判断值是否存在(如`WHERE email='a@xxx.com'`),避免全表扫描;
-
范围扫描:查找区间值(如`WHERE salary BETWEEN 5000 AND 10000`),仅扫描索引区间。
-
索引的创建与删除
(1)创建索引
-
给现有表加索引(`ALTER TABLE`或`CREATE INDEX`)。
-
创建表时直接加索引。
(2)删除索引
-- 方法1:ALTER TABLE(适用于所有索引,包括主键)
ALTER TABLE employees DROP INDEX idx_emp_firstname;
-- 方法2:DROP INDEX(适用于非主键索引)
DROP INDEX idx_title_empno ON titles;
-- 删除主键(需先删主键索引,再重建)
ALTER TABLE dept DROP PRIMARY KEY, ADD PRIMARY KEY (dept_no);
(3)查看索引信息
用`SHOW INDEXES`查看表的所有索引:SHOW INDEXES FROM departments\G
关键字段:`Key_name`(索引名)、`Column_name`(索引列)、`Non_unique`(是否非唯一,0=唯一,1=非唯一)、`Cardinality`(索引基数,越大索引选择性越好)。
四、第四步:维护索引统计------让优化器"判断更准"
MySQL优化器依赖"索引统计信息"(如索引基数、数据分布)选择最优计划,若统计信息过时,可能导致"索引失效",需定期维护。
- 索引统计信息的自动更新
InnoDB默认自动维护统计信息,无需手动干预:
-
触发条件:表中10%的行被修改后(如INSERT/UPDATE/DELETE);
-
持久化存储:统计信息存在`mysql.innodb_index_stats`表,重启后不丢失;
-
采样策略:默认采样20个数据页,可通过`innodb_stats_persistent_sample_pages`调整(越大越精准,但耗时更长)。
- 手动更新统计信息(`ANALYZE TABLE`)
当自动更新不及时(如批量导入数据后),需手动执行`ANALYZE TABLE`:
-- 分析单个表:ANALYZE TABLE salaries;
-- 分析多个表,不写入二进制日志(避免同步到从库):ANALYZE LOCAL TABLE employees, titles;
-
作用:重新计算索引基数、数据分布,让优化器选择正确索引;
-
适用场景:批量插入10万行后、表结构变更后。
- 索引维护进阶操作
(1)重建索引(优化碎片化)
索引长期使用后会产生碎片(如频繁删除导致空页),需重建优化:
-- 方法1:ALTER TABLE FORCE(重建全表索引,适用于InnoDB)
ALTER TABLE employees FORCE;
-- 方法2:OPTIMIZE TABLE(仅重建全文索引,需先设置参数)
SET innodb_optimize_fulltext_only = 1;
OPTIMIZE TABLE titles;
(2)用mysqlcheck工具批量维护
`mysqlcheck`是命令行工具,支持批量分析/优化表,比SQL更便捷:
分析employees库的salaries表
mysqlcheck -u root -p --analyze employees salaries
优化所有库的所有表(谨慎使用)
mysqlcheck --login-path=admin --optimize --all-databases
- 核心选项:`--analyze`(执行ANALYZE TABLE)、`--optimize`(执行OPTIMIZE TABLE)、`--check`(检查表健康)。
(3)不可见索引(安全测试索引作用)
想测试"删除索引是否影响性能",但又怕删错?用"不可见索引"隐藏索引(优化器不使用,但DML仍更新):
-- 创建不可见索引:CREATE INDEX idx_emp_lastname ON employees (last_name) INVISIBLE;
-- 切换为可见:ALTER TABLE employees ALTER INDEX idx_emp_lastname VISIBLE;
- 优势:无需删除重建,测试后若发现索引有用,直接设为可见即可。
(4)直方图(优化非索引列的查询)
对于非索引列,优化器无法知道数据分布(如`c_mktsegment`列的取值频率),此时创建"直方图"可提供额外信息:
-- 给customer表的c_mktsegment列创建直方图(1024个桶)
ANALYZE TABLE customer UPDATE HISTOGRAM ON c_mktsegment WITH 1024 BUCKETS;
- 效果:案例中查询耗时从49.98秒降至6.35秒,因优化器更精准预估筛选后的行数。