数据库运维7 min read

MySQL性能调优深度解析:从配置到SQL优化

MySQL性能调优深度解析:从配置到SQL优化

前言

MySQL是互联网公司使用最广泛的开源数据库,其性能直接影响业务系统的响应速度。本文将从实例配置、索引设计、SQL优化三个层面,系统地介绍MySQL性能调优的完整方法论。

一、实例级配置优化

1.1 InnoDB核心参数

# my.cnf
[mysqld]
# 缓冲池大小 - 最重要的参数
# 建议设置为物理内存的 50%-70%
innodb_buffer_pool_size = 8G

# 缓冲池实例数 - 减少并发争用
# 当 buffer_pool_size > 1G 时建议设置
innodb_buffer_pool_instances = 8

# 日志文件大小 - 影响写入性能
innodb_log_file_size = 1G
innodb_log_buffer_size = 64M

# 刷新策略
innodb_flush_log_at_trx_commit = 2  # 性能优先
innodb_flush_method = O_DIRECT      # 绕过OS缓存

# IO容量
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

# 线程并发
innodb_thread_concurrency = 0       # 让MySQL自动管理

1.2 连接与线程

max_connections = 1000
thread_cache_size = 100
table_open_cache = 4000
table_definition_cache = 2000

# 临时表
tmp_table_size = 64M
max_heap_table_size = 64M

1.3 查询缓存(MySQL 8.0已移除)

MySQL 8.0 移除了Query Cache,替代方案是使用Redis等外部缓存。

二、索引设计与优化

2.1 索引类型选择

索引类型 适用场景 注意事项
B-Tree(默认) 等值查询、范围查询、排序 最通用
Hash 等值查询(仅MEMORY引擎) 不支持范围查询
FULLTEXT 全文搜索 中文需配置分词器
Spatial 地理位置数据 GIS应用

2.2 索引设计原则

-- ❌ 低选择性列不适合单独建索引
-- gender 只有 M/F 两个值
CREATE INDEX idx_gender ON users(gender);  -- 无效索引

-- ✅ 高选择性的列
CREATE INDEX idx_email ON users(email);    -- 几乎唯一

-- ✅ 复合索引:遵循最左前缀原则
-- where a=? and b=? and c=?
CREATE INDEX idx_a_b_c ON orders(a, b, c);

-- 等值条件放前面,范围条件放后面
-- where status=? and create_time > ?
CREATE INDEX idx_status_time ON orders(status, create_time);

2.3 覆盖索引

-- ❌ 需要回表
SELECT id, name, email FROM users WHERE age > 25;

-- ✅ 覆盖索引避免回表
CREATE INDEX idx_age_cover ON users(age, id, name, email);
-- 直接从索引获取所有数据,无需访问数据行

2.4 索引监控

-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;

-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;

-- 索引使用统计
SELECT
    object_schema, object_name, index_name,
    count_read, count_write
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'mydb'
ORDER BY count_read DESC;

三、SQL优化实战

3.1 执行计划解读

EXPLAIN SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active' AND o.create_time > '2025-01-01';

关键字段解读:

字段 含义 优化目标
type 访问类型 至少达到 range,最好 const/eq_ref
rows 扫描行数 越少越好
Extra 额外信息 避免 Using filesort, Using temporary
key 使用的索引 确保用到合适的索引
filtered 过滤比例 越接近100越好

3.2 常见优化模式

-- ❌ 隐式类型转换导致索引失效
SELECT * FROM orders WHERE phone = 13800138000;
-- phone是varchar,应改为
SELECT * FROM orders WHERE phone = '13800138000';

-- ❌ 对索引列使用函数
SELECT * FROM orders WHERE DATE(create_time) = '2025-06-28';
-- 改为范围查询
SELECT * FROM orders WHERE create_time >= '2025-06-28'
    AND create_time < '2025-06-29';

-- ❌ OR条件可能不走索引
SELECT * FROM orders WHERE status = 1 OR amount > 100;
-- 改为UNION
SELECT * FROM orders WHERE status = 1
UNION
SELECT * FROM orders WHERE amount > 100;

3.3 LIMIT 深度分页优化

-- ❌ 大偏移量分页
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;

-- ✅ 基于主键的延迟关联
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 20
) tmp ON o.id = tmp.id;

-- ✅ 基于上次的结果(适用于分页浏览场景)
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;

3.4 COUNT 优化

-- ❌ COUNT(*) 全表扫描
SELECT COUNT(*) FROM orders;

-- ✅ 使用统计信息(允许误差)
SELECT TABLE_ROWS FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'mydb' AND TABLE_NAME = 'orders';

-- ✅ 使用汇总表
-- 创建汇总表,定时更新
INSERT INTO order_stats (stat_date, total_count)
SELECT CURDATE(), COUNT(*) FROM orders
ON DUPLICATE KEY UPDATE total_count = VALUES(total_count);

四、慢查询分析

4.1 开启慢查询日志

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1                    # 超过1秒记录
log_queries_not_using_indexes = 1      # 记录未使用索引的查询
log_slow_admin_statements = 1
min_examined_row_limit = 1000          # 至少扫描1000行

4.2 pt-query-digest 分析

# 分析慢查询日志
pt-query-digest /var/log/mysql/slow.log > slow_report.txt

# 分析tcpdump抓包
tcpdump -s 65535 -x -nn -q -tttt -i eth0 port 3306 -c 5000 > mysql.tcp
pt-query-digest --type tcpdump mysql.tcp

# 实时分析
pt-query-digest --processlist h=localhost,u=root,p=secret --interval 1

4.3 Performance Schema

-- 找出最耗时的查询
SELECT
    DIGEST_TEXT,
    COUNT_STAR AS exec_count,
    AVG_TIMER_WAIT/1000000000000 AS avg_time_sec,
    SUM_ROWS_EXAMINED/COUNT_STAR AS avg_rows_examined
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 20;

五、实战案例

案例:电商订单查询优化

问题:订单列表页加载超过5秒

排查

EXPLAIN SELECT * FROM orders
WHERE user_id = 12345 AND status IN ('paid','shipped')
ORDER BY create_time DESC LIMIT 20;
-- type: ALL, rows: 5000000 ❌

优化

-- 创建复合索引
CREATE INDEX idx_user_status_time
ON orders(user_id, status, create_time);

-- 优化后
-- type: range, rows: 47 ✅

效果:查询时间从 5.2s → 12ms

总结

MySQL优化是一个系统工程:

  1. 配置先行:先确保实例参数合理
  2. 索引为本:好索引解决80%的性能问题
  3. SQL优化:改写低效查询
  4. 持续监控:建立慢查询追踪机制
  5. 架构兜底:读写分离、分库分表是终极方案
分享:

相关文章