前言
数据库是大多数Web应用的性能瓶颈所在。当你发现:
- 网站随着数据量增长越来越慢
- 高并发时数据库连接数爆满
- 某些页面偶尔需要等待5秒以上
- 服务器CPU在业务高峰期持续100%
这些问题,80%以上都与数据库性能有关。
本文提供一套系统的MySQL调优方法,从服务器配置参数、慢查询分析到索引设计,每个步骤都配有可直接执行的SQL或命令,帮你把数据库性能提升数倍。
一、调优前:先建立性能基准
调优之前,先记录当前的性能数据,调优后才能量化效果。
sql
-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
-- 查看慢查询数量
SHOW STATUS LIKE 'Slow_queries';
-- 查看QPS(每秒查询数)
SHOW STATUS LIKE 'Questions';
-- 查看InnoDB缓冲池命中率(越接近1越好)
SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW STATUS LIKE 'Innodb_buffer_pool_reads';
-- 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
二、MySQL服务器配置参数优化
这是调优收益最大的环节,修改配置文件即可生效,无需改动代码。
在宝塔面板 → MySQL → 配置修改,或直接编辑 /etc/my.cnf:
核心配置参数
ini
[mysqld]
# ============================
# InnoDB缓冲池配置(最重要)
# ============================
# 设为服务器可用内存的50%~70%
# 2G内存服务器:512M~1G
# 4G内存服务器:2G~3G
# 8G内存服务器:4G~6G
innodb_buffer_pool_size = 1G
# 缓冲池实例数(>1G时建议设为CPU核心数)
innodb_buffer_pool_instances = 4
# 缓冲池预热(重启后快速恢复性能)
innodb_buffer_pool_dump_at_shutdown = 1
innodb_buffer_pool_load_at_startup = 1
# ============================
# InnoDB日志配置
# ============================
# 日志文件大小(越大写性能越好,崩溃恢复越慢)
innodb_log_file_size = 256M
innodb_log_buffer_size = 64M
# 日志刷新策略(2=每秒刷新,性能最好;1=每次提交刷新,最安全)
innodb_flush_log_at_trx_commit = 1 # 生产环境用1保障数据安全
# ============================
# 连接配置
# ============================
max_connections = 300
max_connect_errors = 1000
# 连接等待超时(避免空连接占用资源)
wait_timeout = 600
interactive_timeout = 600
# ============================
# 查询缓存(MySQL 5.7)
# ============================
# MySQL 8.0已移除查询缓存,5.7版本可开启
# query_cache_type = 1
# query_cache_size = 128M
# ============================
# 临时表配置
# ============================
# 内存临时表大小(超过此大小转为磁盘临时表)
tmp_table_size = 64M
max_heap_table_size = 64M
# ============================
# 排序和连接缓冲
# ============================
sort_buffer_size = 4M
join_buffer_size = 4M
read_buffer_size = 4M
read_rnd_buffer_size = 8M
# ============================
# 慢查询日志
# ============================
slow_query_log = 1
slow_query_log_file = /tmp/mysql-slow.log
long_query_time = 1 # 超过1秒的查询记录为慢查询
log_queries_not_using_indexes = 1 # 记录未使用索引的查询
修改后重启MySQL:
bash
systemctl restart mysql # Ubuntu
systemctl restart mysqld # CentOS
三、慢查询分析:找出性能杀手
配置慢查询日志后,等待一段时间让数据积累,再分析慢查询日志:
bash
# 使用mysqldumpslow分析慢查询日志
mysqldumpslow -s t -t 20 /tmp/mysql-slow.log | head -100
# -s t:按查询总时间排序
# -t 20:显示前20条
# 输出示例:
# Count: 245 Time=3.50s (857s) Lock=0.00s (0s)
# SELECT * FROM orders WHERE status='pending' ORDER BY created_at DESC
关注指标:
Count:执行次数最多的查询(高频优化收益大)Time:单次执行时间最长的查询(直接影响用户体验)Lock:锁等待时间长的查询(并发时影响其他请求)
四、索引优化:最高收益的调优手段
索引是数据库性能优化中收益最大的手段。 一个正确的索引,可以让查询速度从几秒降到几毫秒。
使用EXPLAIN分析查询执行计划
sql
-- 在任何SELECT语句前加EXPLAIN
EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;
关键字段解读:
| 字段 | 重点关注值 | 说明 |
|---|---|---|
| type | ALL | 全表扫描,最慢,必须优化 |
| type | ref/range/eq_ref | 使用了索引,良好 |
| type | const/system | 最优,主键或唯一索引查询 |
| rows | 大数值(如100000) | 扫描行数越少越好 |
| Extra | Using filesort | 需要额外排序,可用索引优化 |
| Extra | Using temporary | 使用了临时表,需要优化 |
| key | NULL | 没有使用任何索引!必须添加 |
常见索引优化场景
场景一:WHERE条件列缺少索引
sql
-- 优化前:全表扫描
SELECT * FROM orders WHERE user_id = 12345;
-- 添加索引
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 验证效果
EXPLAIN SELECT * FROM orders WHERE user_id = 12345;
-- type应从ALL变为ref
场景二:多列查询使用复合索引
sql
-- 查询条件包含多列
SELECT * FROM orders
WHERE user_id = 12345 AND status = 'paid';
-- 创建复合索引(遵循最左前缀原则)
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
-- 如果还需要排序
ALTER TABLE orders ADD INDEX idx_user_status_time
(user_id, status, created_at);
场景三:覆盖索引(避免回表)
sql
-- 查询只需要索引中的列,无需访问原始数据行
SELECT user_id, status, created_at
FROM orders
WHERE user_id = 12345;
-- 创建包含查询所需全部列的复合索引
ALTER TABLE orders ADD INDEX idx_cover (user_id, status, created_at);
-- EXPLAIN中Extra应显示"Using index"(覆盖索引)
场景四:避免索引失效
以下情况会导致索引不生效:
sql
-- 失效:在索引列上使用函数
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- 优化:
SELECT * FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
-- 失效:隐式类型转换(user_id是INT但传了字符串)
SELECT * FROM orders WHERE user_id = '12345';
-- 优化:传正确类型
SELECT * FROM orders WHERE user_id = 12345;
-- 失效:LIKE以通配符开头
SELECT