售前咨询
腾讯云服务器MySQL优化:慢查询日志、执行计划与连接池实战
很多客户找我们做腾讯云服务器MySQL优化,开口第一句都是"加个索引不就行了"。我一般会先让他们把慢查询日志打开,看完再讨论——因为我经手过的排查里,真正死磕"缺索引"的不到一半,更多时候是连接被应用侧打满、云硬盘的 IOPS 到了顶,或者一条 SQL 因为隐式类型转换压根没用上索引。所以这里不讲虚的,把你手上一台跑着 MySQL 的云服务器 CVM,从定位到落地走一遍。
先分层定位:慢在 SQL、连接还是磁盘
数据库一慢,先别急着改 SQL。我习惯按"应用、连接、SQL、存储"四层倒着看一遍,因为每一层的表象都是"页面转圈",但根因完全不同,改错了方向就是白忙。
• 应用层:同一个接口里串行查了十几次数据库,或者压根没设请求超时,前面一条慢就把线程占满。
• 连接层:应用侧连接池的最大连接数比数据库的 max_connections 还大,高峰期直接抛 Too many connections。
• SQL 层:单条语句扫描行数离谱,或者索引失效变成全表扫。
• 存储层:数据文件放在云硬盘 CBS 上,随机 IO 打满盘型 IOPS 上限,SQL 再优也快不起来。
分层的好处是能用一个动作快速缩小范围。先执行 SHOW PROCESSLIST 看是不是一堆 Sending data,再看 SHOW STATUS LIKE 'Threads_connected' 是不是顶到上限;如果线程不多、SQL 也不复杂,基本就落到存储 IO 上了。这个顺序不少同行不会先做,他们上来就 EXPLAIN,改了半天索引,最后发现瓶颈其实在磁盘。
云上定位还有一层便利:云服务器 CVM 配套的云监控能看到 CPU、内存、磁盘读写与网络四条曲线,把应用现象和资源曲线拉到同一时间轴上,很快就能判断是计算瓶颈还是 IO 瓶颈。数据库自身也有工具,performance_schema 和 MySQL 8.0 的 sys 库能按表统计读写,sys.schema_table_statistics 这类视图可以迅速告诉你"到底哪张表在制造 IO",比凭感觉猜高效得多。
慢查询日志与 EXPLAIN:索引失效的四个原因
慢查询日志是治慢的起点,不是可选项,开启时几个参数要一起配。
• slow_query_log 设为 ON,slow_query_log_file 单独指定到一个数据盘目录,别和系统盘抢空间。
• long_query_time 先给 1 秒,治理一段时间后再收紧到 0.1 到 0.2 秒,太早收紧日志量会很吓人。
• log_queries_not_using_indexes 会记录没走索引的语句,线上大表慎开,它可能把日志量放大好几倍。
• min_examined_row_limit 配合使用,过滤掉扫描行数很少的伪慢查询。
解析我一般用 mysqldumpslow 按耗时排序,或者用 pt-query-digest 做聚合,先看总耗时占比最高的那几条,而不是单次最慢的那条——真正拖垮库的是高频次慢,不是偶发的一次慢。
拿到嫌疑 SQL 就上 EXPLAIN,MySQL 8.0 更建议用 EXPLAIN ANALYZE,它会真正执行并给出实际行数。重点看三列:type、key、rows。type 从好到坏是 const、ref、range、index、ALL,出现 ALL 基本就是全表扫;key 为 NULL 说明没用上索引;rows 是预估扫描行数,越接近表总行数越危险。
索引失效我见得最多的四类原因,按出现频率排:
隐式类型转换。字段是 varchar,查询条件却写了数字,MySQL 会把列转成数字比较,索引直接废掉;反过来字段是 int、条件传字符串同样失效。
函数包列。像 WHERE DATE(created_at) = '2026-10-01' 这种对列做了函数运算,索引用不了,改成范围条件 created_at 大于等于 '2026-10-01' 且小于 '2026-10-02'。
前导模糊与最左前缀。LIKE 模式以百分号开头无法走索引;联合索引 (a, b, c) 跳过 a 直接查 b、c 也白搭。
字符集或排序规则不一致。两表 JOIN 时一个 utf8mb4_general_ci、一个 utf8mb4_unicode_ci,关联列上的索引可能失效,这个最隐蔽。
索引不是加得越多越好,一个高频写入的表上堆十几个索引,写入反而会明显变慢。
索引之外,还有两个和写法强相关的慢点值得单独拎出来。第一是深分页:LIMIT 1000000, 20 这种写法,MySQL 要先扫过前一百万行再丢弃,越往后翻越慢;常见优化是记住上一页的最大主键或时间,用 WHERE id 大于上一页末值再 LIMIT 20 来替代 offset 分页,或者直接改成基于游标的翻页。第二是索引设计本身:能给覆盖索引就给覆盖索引,让查询需要的列全落在索引里,省掉回表;联合索引的字段顺序按等值在前、范围在后、区分度高的靠前来排,才能吃到最左前缀;MySQL 8.0 的索引条件下推能减少回表次数,但前提是过滤条件仍然作用在索引列上。日志层面还可以顺手配一下 log_output,临时排查用 TABLE 方便观察,长期留存用 FILE 再配轮转更省事。
再补一条判断标准:优化前后要用同一套基准去比,最好固定样本 SQL 和数据集,记录执行时间、扫描行数、返回行数三个指标,而不是凭"感觉快了"。有时候加索引确实让某条 SQL 变快,却把写入拖慢,整体吞吐不升反降;所以优化要放在整体 QPS 和响应延迟上看,不能只盯单条语句的耗时。
max_connections 与连接池:连接被打满
连接被打满和 SQL 慢是两个病,症状却很像。max_connections 是数据库允许的最大连接数,默认常常只有一百多,而应用侧连接池的最大连接数如果配到 200,高峰一冲就报错。
这里有个反直觉的点:连接数不是越多越好。MySQL 每个连接对应一个线程,连接越多,上下文切换和内存开销越大,整体反而更慢。单实例 QPS 不高的话,连接池给 10 到 30 往往就够,重点是把等待超时配短,让请求快速失败,而不是排队把线程耗死。
连接池要盯三件事:
• 大小。应用池总量乘以应用实例数,必须小于数据库 max_connections,还要留出给运维、备份、复制的余量。
• 泄漏。代码里借了连接没还,异常分支漏了 close,表现为 Threads_connected 只涨不跌,用 SHOW PROCESSLIST 看 Time 很长的 Sleep 连接就能抓出来。
• 超时。connectionTimeout、idleTimeout、maxLifetime 三个都要设,且短于数据库侧的 wait_timeout,避免拿到已经被服务端断掉的死连接。
还有两个容易被忽略的参数要一起看:数据库侧的 wait_timeout 和 interactive_timeout 决定空闲连接被服务端回收的时长,如果应用连接池的 maxLifetime 比它还长,就会出现"拿到死连接"的报错;应用侧的重试策略也得收敛,一超时就疯狂重试,等于把本就不够的连接又放大一倍压力,配上指数退避和熔断才合理。
如果业务是读多写少,读写分离也是常见思路:主库扛写入,只读副本分担查询压力。但要注意复制延迟会带来"刚写完却读不到"的一致性问题,涉及强一致的场景仍要走主库,别为了省资源把一致性赌上去。
云数据库 MySQL 的数据库代理能在前面做一层连接复用,应用侧连接数会被收敛,连接雪崩的场景比自建省心不少。但如果 MySQL 是直接跑在云服务器 CVM 上,这些就得自己管。作为腾讯云代理,我们见到太多客户是在被连接打满之后才回头补这一课。
CBS 的 IOPS、大表 DDL 与轻量实例的天花板
自建 MySQL 跑在 CVM 上,磁盘性能绕不开。数据盘用云硬盘 CBS,不同盘型的 IOPS 上限差很多,高性能云硬盘、SSD 云硬盘、增强型 SSD 的随机 IOPS 是一档档往上走的。数据库是重随机 IO 的业务,IOPS 一打满,SHOW ENGINE INNODB STATUS 里能看到大量等待,SQL 优化此刻几乎没意义。
判断方法很直接:
• 看 iostat -x 1,如果 %util 长期接近 100、await 明显偏高,基本就是盘到瓶颈了。
• 对照云硬盘的 IOPS 上限,粗算 QPS 乘以每次查询的随机读次数,看有没有超标。
• innodb_buffer_pool_size 决定多少热数据能放内存,命中率一低,随机读就全压到盘上,一般给到实例内存的 50% 到 70%。
冷热分离也是缓解磁盘压力的常用手段:把历史数据归档到独立的历史表甚至对象存储 COS,热表只留最近几个月的数据,扫描行数和磁盘占用都会明显下降。分区表同样能帮大表按时间快速裁剪,但要注意分区键的选择,以及跨分区查询并不会因此变快,该扫的行数一行都少不了。
大表 DDL 是另一个隐形杀手。MySQL 5.7 之前很多 ALTER TABLE 会锁表重建,几千万行的表动一下就是几十分钟写不进读不出。在线变更的思路是先做兼容设计,能走 ALGORITHM=INSTANT 的加列就走 INSTANT,否则用 pt-online-schema-change 或 gh-ost 做影子表同步。但即使是在线工具也吃 IO,务必放在低峰窗口,并提前备好回滚方案。
轻量应用服务器 Lighthouse 上跑数据库我会更谨慎。小内存实例缓冲池小、核数少,跑个几十万行的小库没问题,数据量和并发一上来天花板很明显,这时候更该考虑 CVM 或托管云数据库,而不是硬扛。
自建 MySQL 与云数据库 MySQL 的对照
选型时我会把两种方案拉到一张表上比,看客户到底缺的是钱还是人:
维度 自建 MySQL(CVM 自建) 云数据库 MySQL
慢查询治理 自己开慢日志、装解析工具 控制台慢日志分析、直接看 TOP SQL
连接管理 自己配 max_connections 与连接池 数据库代理收敛连接、更可控
存储性能 依赖 CBS 盘型、IOPS 自己算 规格内绑定、按规格扩容
备份恢复 自己写脚本配合 rsync 与 COS 自动备份、按时间点回滚
高可用 自己搭主从、自己切 默认主备、故障自动切换
运维人力 高,需要专人盯 低,平台托管
单机成本 相对低 相对高,但含运维
表看下来结论很清楚:缺人缺运维的团队,托管省下的时间远比差价值钱;有 DBA、要做深度定制的团队,自建更自由。
还要强调一句,托管并不等于不用优化。慢 SQL 在托管实例上照样会拖垮性能,只是慢日志分析、数据库代理这些工具替你完成了"观测"这一步。真正决定响应时间的,依旧是索引设计和 SQL 写法,这两件事无论自建还是托管都躲不开。
FAQ
Q:慢查询日志开着会不会影响性能?
A:会有少量写日志的开销,但相比它帮你定位到的收益完全可以接受。关键是 long_query_time 别一上来就调到极低,先 1 秒起步,日志文件单独放数据盘并配日志轮转。
Q:已经加了索引,EXPLAIN 还是显示全表扫,为什么?
A:优先查隐式类型转换和函数包列这两类。字段类型与参数类型不一致、条件里对索引列做了运算,都会让优化器放弃索引,再检查两表字符集与排序规则是否统一。
Q:CVM 上 MySQL 建议用多大的盘?
A:不看容量看 IOPS 和吞吐。数据库是随机读密集型,优先选随机 IOPS 高的盘型,把 innodb_buffer_pool_size 配足,让热数据尽量留在内存里,减少落到盘上的随机读。
Q:轻量应用服务器能不能跑生产数据库?
A:小库、低并发的内部系统可以,但内存小、IOPS 上