MySQL 性能优化
MySQL 是全球使用最广泛的开源关系型数据库之一,本文聚焦性能优化,从索引设计、查询优化、配置调优到监控诊断,帮你把慢查询变快、把数据库压力降下来。
核心知识点
| 知识点 | 说明 |
|---|
| 最新版本 | MySQL 8.4 LTS / MySQL 9.x Innovation |
| 存储引擎 | InnoDB(默认,支持事务和行锁) |
| 优化方向 | 索引优化、查询优化、配置调优、架构优化 |
| 核心工具 | EXPLAIN、慢查询日志、Performance Schema |
| 黄金法则 | 90% 的性能问题都是索引和查询写法的问题 |
安装配置
Docker 快速启动
# 用 Docker 运行 MySQL 8
docker run -d \
--name mysql \
-p 3306:3306 \ # 端口映射
-e MYSQL_ROOT_PASSWORD=rootpass \ # root 密码
-e MYSQL_DATABASE=testdb \ # 默认数据库
-v mysql_data:/var/lib/mysql \ # 数据持久化
mysql:8.4 # MySQL 8.4 LTS
# 连接
mysql -h 127.0.0.1 -u root -prootpass # 命令行连接
性能相关配置(my.cnf)
[mysqld]
# === 内存配置(根据服务器内存调整)===
innodb_buffer_pool_size = 4G # InnoDB 缓冲池,建议设为总内存的 60-70%
innodb_buffer_pool_instances = 4 # 缓冲池实例数(每个至少1GB)
innodb_log_file_size = 512M # redo log 大小
innodb_log_buffer_size = 64M # log 缓冲区
# === 连接配置 ===
max_connections = 200 # 最大连接数
thread_cache_size = 50 # 线程缓存
# === 查询缓存(MySQL 8.0 已移除)===
# query_cache_type = 0 # 8.0+ 不需要此配置
# === 慢查询日志 ===
slow_query_log = 1 # 开启慢查询日志
slow_query_log_file = /var/log/mysql/slow.log # 日志文件路径
long_query_time = 1 # 超过 1 秒的查询记录
# === 临时表 ===
tmp_table_size = 256M # 内存临时表大小
max_heap_table_size = 256M # HEAP 表最大大小
# === 排序和连接缓冲 ===
sort_buffer_size = 4M # 排序缓冲区
join_buffer_size = 4M # JOIN 缓冲区
read_buffer_size = 2M # 顺序读缓冲
read_rnd_buffer_size = 8M # 随机读缓冲
基本使用(性能诊断)
1. EXPLAIN 分析查询
-- EXPLAIN 是性能优化最重要的工具
EXPLAIN SELECT * FROM samples WHERE diagnosis = 'T2D';
-- EXPLAIN 输出关键列含义:
-- type: 访问类型(从好到坏:const > eq_ref > ref > range > index > ALL)
-- key: 实际使用的索引
-- rows: 预计扫描的行数(越少越好)
-- Extra: 额外信息(Using index = 好,Using filesort = 需要优化)
-- 更详细的分析
EXPLAIN ANALYZE SELECT * FROM samples WHERE diagnosis = 'T2D';
-- 会实际执行并显示真实耗时
2. 慢查询日志分析
# 查看慢查询日志
cat /var/log/mysql/slow.log
# 用 mysqldumpslow 分析(MySQL 自带工具)
mysqldumpslow -s t /var/log/mysql/slow.log # 按时间排序
mysqldumpslow -s c /var/log/mysql/slow.log # 按次数排序
mysqldumpslow -t 10 /var/log/mysql/slow.log # 只看前 10 条
3. 查看当前连接和查询
-- 查看当前正在执行的查询
SHOW PROCESSLIST; -- 查看所有连接
-- 查看 InnoDB 状态
SHOW ENGINE INNODB STATUS; -- InnoDB 引擎详细状态
-- 查看表状态
SHOW TABLE STATUS LIKE 'samples'; -- 表的行数、大小等信息
高级用法(优化策略)
1. 索引优化
-- === 创建合适的索引 ===
-- 单列索引
CREATE INDEX idx_diagnosis ON samples(diagnosis); -- 经常查询的字段
-- 复合索引(遵循最左前缀原则)
CREATE INDEX idx_diag_bmi ON samples(diagnosis, bmi);
-- 这个索引能加速:
-- WHERE diagnosis = 'T2D' ✓ 用到
-- WHERE diagnosis = 'T2D' AND bmi > 25 ✓ 用到
-- WHERE bmi > 25 ✗ 用不到(不满足最左前缀)
-- 覆盖索引(查询的字段全在索引里,不需要回表)
CREATE INDEX idx_cover ON samples(diagnosis, sample_id, bmi);
-- SELECT sample_id, bmi FROM samples WHERE diagnosis = 'T2D';
-- EXPLAIN 会显示 Using index(全从索引取,不回表)
-- === 索引使用注意事项 ===
-- 1. 不要在低基数字段(如 gender: M/F)上建索引
-- 2. 不要建太多索引(影响写入性能)
-- 3. 定期用 ANALYZE TABLE 更新统计信息
ANALYZE TABLE samples; -- 更新表统计信息
2. 查询优化技巧
-- === 避免全表扫描 ===
-- 坏:函数包裹索引列,索引失效
SELECT * FROM samples WHERE YEAR(created_at) = 2025;
-- 好:用范围查询
SELECT * FROM samples
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';
-- 坏:LIKE 以 % 开头,索引失效
SELECT * FROM samples WHERE sample_id LIKE '%001';
-- 好:LIKE 以 % 结尾,可以用索引
SELECT * FROM samples WHERE sample_id LIKE 'T2D_%';
-- 坏:隐式类型转换
SELECT * FROM samples WHERE sample_id = 123; -- sample_id 是 VARCHAR
-- 好:类型匹配
SELECT * FROM samples WHERE sample_id = '123';
-- === 分页优化 ===
-- 坏:大偏移量分页
SELECT * FROM samples ORDER BY id LIMIT 10 OFFSET 100000; -- 扫描 100010 行
-- 好:用游标分页
SELECT * FROM samples WHERE id > 100000 ORDER BY id LIMIT 10; -- 只扫描 10 行
-- === JOIN 优化 ===
-- 确保 JOIN 条件列上有索引
-- 小表驱动大表(小表放 FROM,大表放 JOIN)
SELECT s.sample_id, r.result
FROM samples s -- 小表放前面
JOIN results r ON s.id = r.sample_id -- 确保 r.sample_id 有索引
WHERE s.diagnosis = 'T2D';
3. 批量操作优化
-- 坏:逐条插入
INSERT INTO samples VALUES (...);
INSERT INTO samples VALUES (...);
INSERT INTO samples VALUES (...);
-- 好:批量插入
INSERT INTO samples VALUES
(...), (...), (...), (...), (...); -- 一次插入多行
-- 更好:关闭自动提交,批量提交
SET autocommit = 0; -- 关闭自动提交
INSERT INTO samples VALUES (...); -- 多条 INSERT
INSERT INTO samples VALUES (...);
COMMIT; -- 一次提交
-- 大量数据导入用 LOAD DATA
LOAD DATA INFILE '/path/data.csv'
INTO TABLE samples
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
IGNORE 1 LINES; -- 跳过表头
4. 监控脚本
#!/bin/bash
# mysql_monitor.sh - MySQL 性能监控
# 连接信息
MYSQL="mysql -u root -prootpass"
echo "=== MySQL 性能监控 ==="
# 查看连接数
echo "--- 当前连接数 ---"
$MYSQL -e "SHOW STATUS LIKE 'Threads_connected';"
# 查看慢查询数
echo "--- 慢查询统计 ---"
$MYSQL -e "SHOW STATUS LIKE 'Slow_queries';"
# 查看缓冲池命中率
echo "--- InnoDB 缓冲池命中率 ---"
$MYSQL -e "
SELECT
(1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')
) * 100 AS hit_rate_percent;
"
# 命中率应 > 99%,否则需要增大 innodb_buffer_pool_size
常见报错与解决
| 报错信息 | 原因 | 解决方法 |
|---|
Too many connections | 连接数超限 | 增大 max_connections 或用连接池 |
Lock wait timeout exceeded | 锁等待超时 | 优化事务,减少锁定时间 |
Table is full | 表空间或临时表满 | 增大 tmp_table_size 或磁盘空间 |
Deadlock found | 死锁 | 优化事务顺序,重试失败事务 |
| 查询很慢但没走索引 | 索引失效 | 用 EXPLAIN 检查,避免函数/类型转换 |
Disk full | 磁盘满 | 清理 binlog: PURGE BINARY LOGS BEFORE '2025-01-01' |
速查表
-- ===== MySQL 性能优化速查表 =====
-- 诊断工具
EXPLAIN SELECT ...; -- 查看执行计划
EXPLAIN ANALYZE SELECT ...; -- 实际执行并分析
SHOW PROCESSLIST; -- 当前连接
SHOW ENGINE INNODB STATUS; -- InnoDB 状态
-- 索引管理
CREATE INDEX idx ON t(col); -- 创建索引
DROP INDEX idx ON t; -- 删除索引
SHOW INDEX FROM t; -- 查看索引
ANALYZE TABLE t; -- 更新统计
-- 索引失效的常见原因
-- 1. WHERE 中对索引列用函数
-- 2. LIKE '%xxx'(前缀通配符)
-- 3. 隐式类型转换
-- 4. OR 条件(部分情况)
-- 5. 不满足最左前缀原则
-- 配置关键参数
-- innodb_buffer_pool_size = 总内存*60-70%
-- slow_query_log = 1
-- long_query_time = 1
-- 慢查询分析
-- mysqldumpslow -s t -t 10 slow.log
-- 优化口诀
-- 1. 先看 EXPLAIN,再动手优化
-- 2. 索引不是越多越好
-- 3. 小表驱动大表
-- 4. 避免 SELECT *
-- 5. 批量操作代替逐条