MySQL 8.0性能优化完全手册:从慢查询到InnoDB内核调优

一、性能优化的正确顺序

很多人一遇到MySQL慢就想着加缓存或分库,但正确顺序是:

1. 开慢查询日志 → 找到真正的慢SQL
2. EXPLAIN分析 → 理解执行计划
3. 添加/优化索引 → 通常能解决80%问题
4. SQL重写 → 业务逻辑层面优化
5. InnoDB参数调优 → 内存/IO层面优化
6. 读写分离 → 读流量横向扩展
7. 分库分表 → 写流量/数据量突破单机极限

二、慢查询日志配置与分析

-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;           -- 超过1秒记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL log_queries_not_using_indexes = ON;  -- 未用索引的也记录
# 用pt-query-digest分析慢查询日志(Percona工具箱)
pt-query-digest /var/log/mysql/slow.log     --limit 10 \               # 只看Top 10
    --since "2026-05-28 00:00:00"     --until "2026-05-28 23:59:59"     > slow_report.txt

# 报告中重点关注:
# Query_time: 执行时间
# Rows_examined: 扫描行数(越大越慢)
# Rows_sent: 返回行数
# 比率Rows_sent/Rows_examined 越低越糟糕

三、EXPLAIN执行计划精读

EXPLAIN SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2026-01-01'
GROUP BY u.id
ORDER BY order_count DESC
LIMIT 10;
+----+-------------+-------+------+------------------+------+---------+------+--------+------------------------------------------+
| id | select_type | table | type | possible_keys    | key  | key_len | ref  | rows   | Extra                                    |
+----+-------------+-------+------+------------------+------+---------+------+--------+------------------------------------------+
|  1 | SIMPLE      | u     | ALL  | NULL             | NULL | NULL    | NULL | 500000 | Using where; Using temporary; filesort   |
|  1 | SIMPLE      | o     | ref  | idx_user_id      | idx  | 4       | u.id | 5      | NULL                                     |
+----+-------------+-------+------+------------------+------+---------+------+--------+------------------------------------------+

⚠️ 红色警报:
  type=ALL → users表全表扫描50万行!
  Using temporary → 用了临时表(GROUP BY代价)
  Using filesort → 内存中排序(ORDER BY代价)

修复方案:给users表加索引

-- 复合索引(顺序很重要!)
-- 原则:等值查询列在前,范围查询列在后
ALTER TABLE users ADD INDEX idx_created_name (created_at, id, name);

-- 再次EXPLAIN
EXPLAIN SELECT...;
-- type: range → 扫描创建时间范围,不再全表扫描
-- rows: 12000 → 从50万降到1.2万

四、索引设计最佳实践

-- ❌ 错误:区分度低的列放前面
CREATE INDEX idx_bad ON orders (status, user_id);
-- status只有3个值,扫描大量数据后才能用user_id过滤

-- ✅ 正确:高区分度列放前面
CREATE INDEX idx_good ON orders (user_id, status, created_at);
-- user_id区分度高,快速定位到某用户的订单

-- ✅ 覆盖索引:查询字段都在索引中,避免回表
-- 查询:SELECT id, status, amount FROM orders WHERE user_id=123
CREATE INDEX idx_covering ON orders (user_id, status, amount);
-- EXPLAIN Extra: "Using index" → 无需回表

-- 前缀索引:对长字符串字段
-- 先找合适的前缀长度
SELECT COUNT(DISTINCT LEFT(email, 10)) / COUNT(*) FROM users;  -- 0.94
SELECT COUNT(DISTINCT LEFT(email, 8)) / COUNT(*) FROM users;   -- 0.91
-- 前8位区分度已经很高
ALTER TABLE users ADD INDEX idx_email_prefix (email(8));

五、InnoDB关键参数调优

[mysqld]
# 缓冲池:最重要的参数,建议设置为物理内存的60-80%
innodb_buffer_pool_size = 12G  # 16G内存服务器

# 缓冲池实例数:减少锁争用,建议每1-2G一个实例
innodb_buffer_pool_instances = 8

# Redo Log大小:写密集型场景增大,减少checkpoint频率
innodb_log_file_size = 2G
innodb_log_buffer_size = 64M

# 刷盘策略:
# 0 = 每秒刷盘(性能最好,断电丢1秒数据)
# 1 = 每次提交刷盘(最安全,性能最差)
# 2 = 每次提交写OS缓存,每秒刷盘(平衡)
innodb_flush_log_at_trx_commit = 2  # 对大多数业务足够

# 并发线程数:防止过多线程争用导致性能下降
innodb_thread_concurrency = 16  # 通常 = CPU核心数 * 2

# IO能力配置(SSD机器)
innodb_io_capacity = 4000       # SSD的实际IOPS
innodb_io_capacity_max = 8000

六、常见性能问题快速诊断

-- 1. 找当前执行中的慢查询
SELECT * FROM information_schema.PROCESSLIST
WHERE TIME > 5  -- 执行超过5秒
ORDER BY TIME DESC;

-- 2. 找没有索引的表(超小表除外)
SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_db'
  AND TABLE_ROWS > 10000
AND TABLE_NAME NOT IN (
    SELECT DISTINCT TABLE_NAME FROM information_schema.STATISTICS
    WHERE TABLE_SCHEMA = 'your_db'
);

-- 3. 找未使用的索引(长期运行后)
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
  AND COUNT_STAR = 0
  AND OBJECT_SCHEMA = 'your_db'
ORDER BY OBJECT_SCHEMA, OBJECT_NAME;

MySQL优化80%的问题靠索引解决,只有在索引优化到位后,才值得考虑分库分表的复杂度。