MySQL 8 性能优化实战:慢查询、索引与连接池配置一次讲清


数据库往往是网站最先扛不住的环节:文章列表越拉越慢、后台一查询就转圈、并发一上来连接数直接打满。很多人第一反应是升级 CPU 和内存,但如果慢在一条没走索引的 SQL、或者连接池配错了,配置翻倍也只是把 3 秒变成 2 秒。这篇按“先定位、再优化”的顺序,把 MySQL 8 的慢查询、执行计划、索引、分页、连接池和 InnoDB 参数一次讲清。

一、先定位,不要盲目调参数

优化之前先打开慢查询日志,确认时间到底花在哪里。临时开启可以执行:

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;

长期使用建议写进 my.cnf:

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

log_queries_not_using_indexes 可以在测试环境短时间打开,用来找没走索引的 SQL,但生产环境长期开启会产生大量日志,不建议。

拿到慢日志后,用 mysqldumpslow 按耗时排序,或者用 pt-query-digest 做聚合分析:

mysqldumpslow -s t /var/log/mysql/slow.log | head -20

二、用 EXPLAIN 看懂执行计划

找到可疑 SQL 后,先看执行计划:

EXPLAIN SELECT * FROM articles WHERE category_id = 3 ORDER BY created_at DESC LIMIT 20;

MySQL 8.0.18 之后还可以用 EXPLAIN ANALYZE,得到实际执行耗时:

EXPLAIN ANALYZE SELECT ...

重点看四列:type 是否为 ALL(全表扫描)、key 是否真的用上了索引、rows 预估扫描行数、Extra 是否出现 Using filesort 或 Using temporary。type 出现 ALL、Extra 频繁出现 filesort 和临时表,通常就是优化入口。

三、索引怎么建才有效

  • 联合索引遵循最左前缀:WHERE a = 1 AND b = 2 可以用 (a,b) 索引,只查 b 用不上。
  • 区分度高的列放左边:把 status 这种只有几个值的列放最左,过滤效果很差。
  • 尽量用覆盖索引:查询列都在索引里,可以避免回表。
  • 不要在索引列上做函数或运算:WHERE DATE(created_at) = '2026-01-01' 用不上索引,改成范围查询。
  • 注意隐式类型转换:字符串字段用数字去查,可能让索引失效。
  • LIKE '%keyword%' 无法走索引,前缀匹配 LIKE 'keyword%' 才可以。
  • 索引不是越多越好,每个索引都会增加写入和维护成本。
  • MySQL 8 支持降序索引和函数索引,排序方向不一致、必须按表达式查询时可以用上。

四、深分页别再 LIMIT 100000,20

翻到很后面时,LIMIT 100000,20 会先扫描前 10 万行再丢掉,越翻越慢。两种常用改法:

延迟关联,先用索引拿到主键,再回表取数据:

SELECT a.* FROM articles a
JOIN (SELECT id FROM articles ORDER BY id LIMIT 100000, 20) t
ON a.id = t.id;

或者改成游标分页,记住上一页最后一条 id:

SELECT * FROM articles WHERE id > 100000 ORDER BY id LIMIT 20;

五、连接池与连接数

连接数不是越大越好。每个连接都占内存和上下文切换开销,盲目把 max_connections 调到几千,峰值时反而更容易雪崩。应用侧连接池的起点可以按“CPU 核数 × 2 + 磁盘数”估算,再压测调整。HikariCP 的 maximumPoolSize、Druid 的 maxActive 都要和数据库实际承载能力匹配。

同时检查 wait_timeout 和 interactive_timeout,避免大量空闲连接长期占着。应用不要每个请求都新建连接,连接池要复用。

六、InnoDB 关键参数

  • innodb_buffer_pool_size:专用数据库机器可以设到物理内存的 50%–70%,这是命中率的关键。
  • innodb_buffer_pool_instances:内存较大时拆成多个实例,减少锁竞争。
  • innodb_flush_log_at_trx_commit = 1:每次提交都刷盘,保证不丢数据;改成 2 性能更好,但故障时可能丢约 1 秒事务。
  • sync_binlog = 1:和上面一起常说的“双一”配置,安全性优先。
  • innodb_io_capacity:SSD 可以适当调高,机械盘不要照抄高值。

另外,MySQL 8 已经移除查询缓存,网上那些 query_cache_size 的旧教程不要再照抄。

七、排序、临时表与 JOIN

sort_buffer_size 是按连接分配的,调太大容易把内存吃满,不要盲目加到几百 M。tmp_table_size 和 max_heap_table_size 建议保持一致,避免临时表频繁落盘。JOIN 的两张表关联列类型要一致、都要有索引,尽量让小表驱动大表,避免被驱动表全表扫描。

八、监控与上线检查

优化不是一次性的。上线前至少做三件事:用真实数据压测、抽查慢日志、对核心 SQL 跑一遍 EXPLAIN。线上持续观察连接数、QPS、TPS、缓冲池命中率和慢查询数量,可以用 performance_schema、sys 库,或者 Prometheus + mysqld_exporter + Grafana 做面板。

大表增长很快时,提前规划归档、分区或读写分离,比出事后再拆表从容得多。备份和恢复演练也要一起做,性能优化不能拿数据安全换。

结语

MySQL 优化的顺序永远是:先看慢查询定位问题,再看执行计划,最后才动索引和参数。索引解决查询路径,连接池解决并发入口,InnoDB 参数解决资源利用率,三者配合才有稳定效果。每次只改一项、压测验证,再改下一项,比一次性调几十个参数靠谱得多。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

-

Let's Encrypt 免费 SSL 证书申请与自动续期实战:Nginx + Certbot 一次讲清

评 论