一、识别慢查询:开启慢查询日志
首先需要准确定位哪些 SQL 是 "慢查询",MySQL 通过慢查询日志记录执行时间超过阈值的 SQL。
1. 配置慢查询参数
通过
my.cnf(或my.ini)配置:# 开启慢查询日志(默认关闭)
slow_query_log = 1
# 慢查询日志文件路径
slow_query_log_file = /var/log/mysql/mysql-slow.log
# 执行时间阈值(单位:秒,默认10秒,建议设为1-2秒)
long_query_time = 1
# 记录未使用索引的查询(即使执行时间未达标)
log_queries_not_using_indexes = 1
配置后重启 MySQL 生效,也可通过 SQL 动态设置(重启后失效):
set global slow_query_log = 1;
set global long_query_time = 1;
二、分析慢查询:使用 Explain 工具
找到慢查询后,通过
EXPLAIN分析 SQL 执行计划,定位性能瓶颈。1. Explain 用法
在 SQL 前加
EXPLAIN:EXPLAIN SELECT * FROM user WHERE age > 30 AND name LIKE '张%';
2. 关键字段解读
| 字段 | 含义与优化点 |
|---|---|
type |
访问类型,性能从好到差:system > const > eq_ref > ref > range > index > ALL。目标:至少达到 range,避免ALL(全表扫描)。 |
key |
实际使用的索引,若为NULL则未使用索引,需优化索引。 |
rows |
估计扫描的行数,值越小越好(需结合实际数据量判断)。 |
Extra |
额外信息,常见关键值: - Using filesort:需优化排序(如加索引);- Using temporary:使用临时表(性能差,需避免);- Using index:覆盖索引(理想状态)。 |
三、优化核心方法
1. 索引优化(最关键)
索引是提升查询速度的核心,需避免 "无索引" 或 "索引失效"。
(1)添加合适的索引
- 单字段索引:where 子句中频繁出现的字段(如
age、status)。 - 联合索引:多条件查询时,按 "字段区分度高→低" 排序创建(遵循最左前缀原则)。
例:WHERE a=1 AND b=2 AND c=3,联合索引应设为(a,b,c)。 - 覆盖索引:索引包含查询所需所有字段(避免回表查询)。
例:SELECT id,name FROM user WHERE age=30,创建(age,name)索引可覆盖查询。
(2)避免索引失效
以下情况会导致索引失效,需特别注意:
- 使用函数 / 运算:
WHERE SUBSTR(name,1,1)='张'(可改为name LIKE '张%')。 - 隐式类型转换:
WHERE phone=13800138000(若phone是 varchar,需加引号)。 LIKE以%开头:WHERE name LIKE '%张'(无法使用索引,可考虑全文索引)。- 使用
NOT IN、!=、<>等操作(可能导致全表扫描)。 - 联合索引不满足最左前缀:
(a,b,c)索引,仅用b=2查询会失效。
2. SQL 语句优化
(1)简化查询范围
- 避免
SELECT *:只查询需要的字段(减少数据传输,利于覆盖索引)。 - 限制返回行数:用
LIMIT分页,避免一次性返回大量数据。
(2)优化关联查询
- 小表驱动大表:
JOIN时,将数据量小的表作为驱动表(减少外层循环次数)。 - 避免跨库 / 跨表 join:尽量在同一库内关联,减少网络开销。
(3)优化子查询
子查询效率较低,可改为
JOIN:-- 低效子查询
SELECT * FROM user WHERE id IN (SELECT user_id FROM order WHERE amount > 1000);
-- 优化为JOIN
SELECT u.* FROM user u JOIN order o ON u.id = o.user_id WHERE o.amount > 1000;
(4)优化分页查询
大偏移量分页(如
LIMIT 100000, 10)会扫描大量数据,优化方案:-- 低效:扫描前100010行
SELECT * FROM user ORDER BY id LIMIT 100000, 10;
-- 优化:利用索引定位起点(需id有序)
SELECT * FROM user WHERE id > (SELECT id FROM user ORDER BY id LIMIT 100000, 1) LIMIT 10;
3. 表结构优化
(1)选择合适的数据类型
- 用更小的类型:
TINYINT(1 字节)代替INT(4 字节)存储小范围数字。 - 避免大字段:
TEXT/BLOB等大字段单独分表(减少主表 IO)。 - 日期用
DATE/DATETIME:而非VARCHAR(便于排序和计算)。
(2)分表分库(数据量大时)
当单表数据量超过 1000 万行,查询性能会明显下降,需拆分:
- 水平分表:按规则拆分数据(如按用户 ID 哈希、按时间范围),每个表结构相同。
例:订单表order_2023、order_2024按年份拆分。 - 垂直分表:按字段冷热拆分,将高频访问字段和低频字段分开存储。
例:用户表拆分为user_base(基本信息)和user_detail(详细信息)。
4. 数据库配置优化
通过调整 MySQL 配置提升性能(
my.cnf):- 缓存优化:
innodb_buffer_pool_size设为服务器内存的 50%-70%(缓存数据和索引,减少磁盘 IO)。 - 连接优化:
max_connections根据并发量调整(避免连接数不足),thread_cache_size缓存线程(减少创建线程开销)。 - 日志优化:关闭不必要的日志(如 general log),慢查询日志仅在排查问题时开启。
四、日常维护
- 定期执行
ANALYZE TABLE:更新表统计信息,帮助 MySQL 生成更优执行计划。 - 监控慢查询日志:通过工具(如 pt-query-digest)分析慢查询趋势,提前优化。
- 避免大事务:长事务会占用锁资源,导致其他查询阻塞(拆分为小事务)。
通过以上步骤,可有效定位并解决 MySQL 慢查询问题,核心原则是:减少扫描行数、高效利用索引、优化数据访问方式。