关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

2026 MySQL慢查询服务器怎么查:索引、执行计划与IOPS的真实取舍

发布时间:2026-09-29

订单列表页偶发 3 秒,多数人的第一动作是给 where 条件里的字段加个索引。加上之后查询确实快了一点,写入却慢了一截,凌晨的批量任务从 20 分钟拖到 40 分钟。这种交换到底值不值,得先看清瓶颈落在哪一层。

先把结论摆出来:索引只是四个变量里的一个。扫描行数、回表次数、内存里装不装得下热数据、磁盘能扛多少随机 IO,这四样共同决定了一条 SQL 的耗时。一律归因于索引,结果就是索引越加越多,写入变慢、空间翻倍、执行计划开始抖动。

慢查询的第一反应是加索引,这个习惯坑了不少人

加索引能把全表扫描变成索引定位,逻辑上没毛病。问题在于,索引解决的是「找到行的位置」,它不保证「把行取出来」这件事便宜。

订单列表页那 3 秒,可能不是扫描慢,是回表慢

假设 orders 表 2000 万行,列表页走 (shop_id, status, create_time) 索引,按分页取 20 条。EXPLAIN 显示 type 是 ref,rows 只有几百,看着很健康。但接口照样 3 秒。真跑去 handler 状态里看,Handler_read_next 一秒几万次,iostat 里磁盘 await 冲到几十毫秒——这时候慢的不是扫描,是回表:索引叶子节点里只有主键值,取其余十几个字段还得回主键树再读一次,而这些主键在磁盘上大概率不连续。

这种情形下加索引几乎没用,因为你已经用上索引了。真正有用的是把查询需要的列塞进索引变成覆盖索引,或者干脆接受回表、把磁盘换成随机读更强的介质。两条路成本完全不同,判断错了就是白忙一周。

慢的来源只有三类,先分类再动手

一条 SQL 慢,逃不出这三种:一是扫描了太多行,索引没选对或者压根没用上;二是扫描行数不多但每行都要回表取数据,随机 IO 累积;三是既不扫得多也不回表多,时间耗在等——等锁、等刷脏、等磁盘排队。

三类的处置路径不同,甚至互相冲突。第一类缺索引,第二类缺内存或缺 IOPS,第三类要去看长事务和刷盘策略。混在一起处理,常见后果就是:加索引缓解了第一类,却因写入放大加剧了第三类。

先把阈值和采样口径调对,否则看到的是假象

慢查询日志是唯一能拿到完整 SQL 文本的地方,但默认配置下的慢日志,价值很有限。

避坑一:long_query_time 设成 1 秒,把真正的慢查询淹在噪音里

问题:多数线上库沿用 long_query_time = 1 的默认值或者 1 秒,慢日志里只躺着少量超时级查询,看起来风平浪静,业务却天天抱怨卡。

为什么会发生:1 秒这个数字和数据库无关,它是给人看日志的舒适度定的。而 OLTP 接口的耗时预算通常是 100 到 300 毫秒——一个 600 毫秒的查询,单次不足以触发告警,一天跑 5 万次,累积起来就是数据库 CPU 的常驻压力,也足以让列表页在高峰期排队。

怎么判断:把接口的 P99 目标倒推回数据库,单条 SQL 的预算大致是接口预算的 30% 到 50%。接口要求 500 毫秒返回,数据库就该盯 150 到 250 毫秒这条线。

怎么规避:long_query_time 先按业务预算设到 0.2 到 0.5 秒,跑一周看日志量;再配 min_examined_row_limit(比如 100 或 1000)把小表全扫这类无害语句滤掉;log_queries_not_using_indexes 只在排查期开,长期开会灌满磁盘;log_slow_admin_statements 建议打开,否则 ALTER 和备份造成的抖动你永远看不到;log_throttle_queries_not_using_indexes 限制每分钟同类语句记录条数,避免一条烂 SQL 刷爆日志。

全量记录还是采样,得看写入压力

慢日志是同步写文件的,每条都要走一次 IO。低 QPS 场景下这点开销可以忽略;QPS 上到几千、慢查询本身又很多的时候,日志写入会反过来拖慢数据库。这时候有两条路:一是按会话采样(只在排查窗口对部分连接开启),二是改用 performance_schema 的 events_statements_summary_by_digest 做聚合,它按 SQL 指纹归并,拿不到每条语句的完整参数,但对找 Top N 足够用。

log_output 也有讲究:写 FILE 便于离线分析,写 TABLE(mysql.slow_log)便于 SQL 化查询,但它是 CSV 引擎,量大以后查询本身很慢,生产一般用 FILE 配 pt-query-digest。

一条慢日志里真正要看的字段

Query_time 只是结果。Lock_time 大说明在等锁,Row_examined 大说明扫描多,Row_sent 小而行扫描多说明过滤效率低,Rows_affected 大说明是写入。把 Row_examined 和 Row_sent 摆在一起看,比值超过 100 的语句,优先怀疑索引和过滤条件,而不是怀疑硬件。

聚合之后看什么:次数、总耗时还是单次最大值

拿到几百兆慢日志,逐条读没有意义,得先聚合。pt-query-digest 或 mysqldumpslow 会把相同 fingerprint 的语句并成一组,给出 count、avg、min、max、95% 这些统计。问题来了:该按哪个排序?

避坑二:只看平均耗时,不看 P99

问题:报表里平均响应 80 毫秒,看着很健康,用户端却时不时转圈。

为什么会发生:平均值会被大量高频简单查询拉低。一条查询 99 次是 20 毫秒,1 次是 3 秒,平均只有 50 毫秒,但那 1 次正好撞上用户的列表页。

怎么判断:同时看三条线——总耗时(count × avg)决定这条 SQL 吃掉多少数据库资源,max 和 P99 决定它会不会触发超时重试,count 决定它值不值得投入改造。慢日志本身只有 min/max/avg,P95 和 P99 要由 pt-query-digest 的分位数输出,或者靠应用层埋点补齐。

怎么规避:建立两张清单。资源清单按总耗时排序,治的是成本;体验清单按 P99 和 max 排序,治的是投诉。两条线的优化对象经常不是同一条 SQL,混在一张表里排优先级,就容易把力气花在没人感知的地方。

一个对照:谁该先治

语句 A:单次 100 毫秒,一天 5 万次,总耗时 5000 秒。语句 B:单次 3 秒,一天 20 次,总耗时 60 秒。从资源占用看,A 是 B 的八十多倍,把 A 降到 30 毫秒,等于每天省下 3500 秒的数据库时间,CPU 可能直接从 70% 掉到 40%。从体验看,B 才是被投诉的那条,它大概率撞上网关超时、触发前端重试,重试又加重数据库负担。

所以 A 先治,但它未必是本周的救火对象;B 量不大,却要在接口层收敛超时与重试,避免雪崩。两件事可以同时推进,动的是不同层面。

执行计划里真正值钱的三行:type、rows、Extra

EXPLAIN 输出十几列,日常排查真正需要先看的是 type、rows、Extra,配合 key 和 key_len 确认索引到底用上了几列。

type:访问类型决定了数量级

从好到差大致是 system、const、eq_ref、ref、range、index、ALL。const 和 eq_ref 出现在主键或唯一索引等值匹配上,基本不会慢;ref 是普通二级索引等值匹配,健康;range 要看范围多大;index 是全索引扫描,只是扫描的东西比全表小一点;ALL 在千万行表上等于几秒起步。

这里有个常见误解:看到 ALL 就一定要消灭。几千行、常年驻留在 buffer pool 里的小表,全扫成本是微秒级,为它建索引纯属负担。全扫该不该治,取决于这张表会不会长大、这条语句跑得多频繁。

rows 是预估,不是实测

rows 来自统计信息和代价模型,可能和实际差一个数量级。判断预估准不准,用 SHOW STATUS 的 Handler_read_* 系列或者慢日志里的 Row_examined 去对。EXPLAIN ANALYZE(MySQL 8.0.18 起)会把真实执行时间和实际行数打出来,排查时比普通 EXPLAIN 可靠得多,注意它会真的执行这条语句,别在生产的 UPDATE 上跑。

filtered 这一列也常被忽略,它表示存储引擎返回的行里预计有多少比例能通过 where 条件。rows 不高但 filtered 很低,说明索引只帮你筛掉了第一层,剩下的过滤还是在 Server 层做,这种时候联合索引少了一列是很常见的原因。

Extra 里的关键词

Using index 是好事,代表覆盖索引,不用回表。Using index condition 代表索引条件下推(ICP),where 的一部分在存储引擎层就过滤掉了,能减少回表次数,但不等于不回表。Using where 表示 Server 层还要再过滤一次。Using filesort 和 Using temporary 是排序与临时表,中小结果集没事,大结果集会同时吃掉内存和磁盘。Using join buffer 说明关联字段没索引,走块嵌套循环,表一大就是灾难。

key_len 能告诉你的事

联合索引 (shop_id, status, create_time),where 只给 shop_id 和 create_time,key_len 会显示只用到 shop_id 那一段,status 一断,create_time 就接不上。看到 key_len 比预期短,先别急着加索引,先看条件写法是不是漏了中间那一列。

回表与覆盖索引:为什么索引建了还要回磁盘

InnoDB 的主键索引是聚簇索引,叶子节点存整行数据;二级索引的叶子节点只存索引列加主键值。通过二级索引定位到主键之后,如果查询还要别的列,就得拿着主键回聚簇索引再读一次,这一步叫回表。

回表的代价是随机 IO

二级索引里的主键值是有序的(按索引列排),但主键对应的聚簇索引位置未必连续。一页 16KB,一次回表最坏就是一次独立的页读取。扫描 1 万行就要 1 万次随机读,落在机械盘上按百级 IOPS 算,光读取就要几十秒;SATA SSD 数万 IOPS,NVMe 可以到数十万 IOPS,这时才降到毫秒级。

这也解释了那个经典现象:EXPLAIN 明明走了索引,rows 只有几百,语句还是慢。rows 少只代表定位便宜,不代表回表便宜。真正的账要按「回表次数 × 单次随机读延迟」算。

覆盖索引怎么建,代价是什么

把查询需要的列全部放进索引,EXPLAIN 的 Extra 出现 Using index,回表次数归零。列表页这种固定返回十几个字段的场景,收益非常直接。

代价也很实在。索引变宽,体积可能从数据的 20% 涨到 60% 甚至更高;buffer pool 能装的索引页变少,等于用内存换 IO,内存不够时反而更糟;每一列变更都要维护所有包含它的索引,写放大跟着涨。

所以覆盖索引适合读多写少、返回列固定的场景,比如订单列表、账单查询。写密集的流水表、日志表,宽索引基本是亏的。

主键的选择会放大或缓解这个问题

自增主键插入是顺序写,页分裂少,聚簇索引的物理顺序与业务时间顺序一致,范围扫描接近顺序 IO。换成 UUID 或随机业务键,插入位置随机,页分裂频繁,碎片率高,聚簇索引体积可能比自增方案大出 30% 到 50%,buffer pool 的有效容量被碎片吃掉,随机写也更多。很多团队 UUID 用了一年之后发现数据库变慢,升级 CPU 完全无效,根子就在这里。

索引选择性不够时,优化器为什么不理你

给 status 字段建了索引,EXPLAIN 依然走 ALL,很多人会以为是索引失效。其实多数情况是优化器算完账,认为全表扫描更便宜。

选择性是一个比值

选择性 = 不同值数量 / 总行数。主键接近 1,手机号、订单号也很高;status、is_deleted、gender、type 这种枚举字段,可能只有万分之几。选择性低的字段做前导列,索引定位出来一大片行,还得逐个回表。

优化器的代价模型里,全表扫描是顺序 IO,一次读一大块;二级索引回表是随机 IO,一次读一页。当回表行数占到总行数的某个比例(业界常用的经验区间是 20% 到 30%,具体阈值随版本和代价常量变化),优化器就会放弃索引。这不是 bug,是它算出来的结果。只不过这个结果建立在统计信息准确的前提上。

统计信息不准,执行计划就会抖

InnoDB 的统计信息默认持久化(innodb_stats_persistent = ON),cardinality 靠随机采样若干索引页估算,采样页数由 innodb_stats_persistent_sample_pages 控制,默认值不算高。表越大、数据分布越倾斜,估算与真实值偏离越远。表现就是:同一条 SQL 周一走索引,周三走全扫,周四又走回索引。

处置办法很直接:调大采样页数,对倾斜明显的列用 8.0 的直方图(ANALYZE TABLE tbl UPDATE HISTOGRAM ON col WITH N BUCKETS),批量导入或大批量删除之后手动跑一次 ANALYZE TABLE。注意它会触发统计信息重算,别在业务高峰对大表跑。

那些真会让索引失效的写法

  • 列上套函数:where date(create_time) = '2026-09-28' 用不上 create_time 索引,改成范围条件。
  • 隐式类型转换:列是 varchar,条件是数字,会走类型转换导致索引失效,反过来(列是数字、条件是字符串)通常还能用上。
  • 字符集或排序规则不一致:关联的两张表字段字符集不同,会触发转换。
  • 前导通配:like '%关键字' 无法用 B+ 树定位,like '关键字%' 可以。
  • or 连接不同列:没有各自索引时会退化为全扫,可考虑改写为 union all。
  • 联合索引缺前导列:where 里没有最左列,后面的列基本用不上。

buffer pool 命中率掉下来时,磁盘 IOPS 才是主角

MySQL 的性能分界线,很大程度上是「热数据装不装得进内存」。装得进,绝大多数查询是内存操作;装不进,每一次未命中都是一次磁盘随机读。

命中率怎么算,掉到多少要警觉

命中率 ≈ 1 − Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests。这两个值取两次快照做增量再算,直接拿累计值算会被开机以来的历史稀释。

经验区间:99% 以上算健康;掉到 95% 到 99% 之间,说明热集已经接近内存上限,数据再涨就会恶化;低于 95%,磁盘基本在承担主要读压力,加 CPU 没有意义。要注意的是命中率高也不绝对安全——热集只有 2GB、内存给了 64GB,命中率自然接近 100%,这说明配置过剩,而不是健康。

命中率为什么会周期性下滑

最常见的原因是冷数据污染。一次运营后台的全表导出、一次不带索引的统计任务、一次备份期间的批量读,会把大量冷页读进 buffer pool,把真正的热页挤到 LRU 尾部。任务结束之后,业务查询要重新把热页从磁盘读回来,这一两个小时里所有接口都慢。InnoDB 有 LRU 分代(young/old 区)和 innodb_old_blocks_time 来缓解,但只能减轻,不能消除。

第二个原因是数据自然增长。热集随业务量线性上涨,涨过内存那一天,性能曲线不是缓慢下降,而是某个阈值之后陡降。

CPU 瓶颈和 IO 瓶颈的分辨办法

CPU 高、磁盘利用率低:时间花在 Server 层的排序、聚合、函数计算、大量行的逐行过滤上,常见于大结果集 ORDER BY、GROUP BY、子查询展开。这种情况下升主频和核数有效,加内存没用。

CPU 不高但查询慢、磁盘 %util 接近 100、await 明显上升:这是典型的 IO 等待,CPU 在等磁盘返回。这种情况加内存(让热集进 buffer pool)或者换更强随机读的磁盘有效,加 CPU 没用。

iostat -x 1 里重点看 r/s、w/s、await、%util、以及请求平均大小。NVMe 设备要多看队列深度和饱和度指标,%util 在多队列设备上已经不是可靠信号。

现象常见误判真实瓶颈对应硬件变量验证方式
走了索引,rows 只有几百,语句仍然慢以为索引没生效,继续加索引回表次数多,随机读累积磁盘随机读 IOPS、单次读延迟对比 Handler_read_next 增速与 iostat 的 await
查询时快时慢,周期性发作以为是网络或应用抖动冷数据挤占导致 buffer pool 命中率下滑内存容量、buffer pool 大小取 Innodb_buffer_pool_reads 增量算命中率
平均耗时不高,CPU 却长期打满以为 CPU 核数不够高频小查询累积,扫描行数乘次数总量巨大CPU 核数与主频、总扫描行数按总耗时排序的聚合报表
加了索引后写入明显变慢以为磁盘性能下降索引维护造成写放大与刷脏压力磁盘随机写 IOPS、fsync 延迟对比变更前后的写 IO 与检查点抖动
执行计划忽好忽坏以为是优化器缺陷统计信息采样不足,cardinality 偏差大统计信息采样页数、分析频率对比 SHOW INDEX 的基数与真实去重值
全表扫描反而比走索引快以为索引失效了优化器算过回表成本,顺序读更划算顺序读带宽与随机 IOPS 的比值强制 USE INDEX 后实测耗时对比

按数据量和 QPS 倒推内存与磁盘配置

配置不是拍脑袋定的,可以从数据量和访问模式倒推。下面的数字是量级估算,实际还要按行格式、碎片率、填充因子修正。

避坑四:buffer pool 给太小却去升级 CPU

问题:数据库慢,看监控 CPU 也高,于是把 8 核换成 16 核,慢查询一点没少。

为什么会发生:CPU 高很多时候是等 IO 时的上下文切换和中断开销造成的假象,真正的等待在磁盘。另一方面,buffer pool 只有 4GB、热集有 30GB 时,每条查询都要产生磁盘读,CPU 再强也只能干等。

怎么判断:先算命中率,再算热集。热集 = 高频访问的索引页 + 高频访问的数据页,粗略可以用「主键索引大小 × 活跃比例 + 二级索引大小」估算,大小能从 information_schema 或 sys schema 的容量视图里读出来。

怎么规避:内存的优先级高于 CPU。举例:单表 2000 万行、平均行 500 字节,主键树按 1.5 倍膨胀系数算大约 15GB,三个二级索引合计 3 到 4GB;如果活跃数据集中在近三个月,热集可能在 6 到 8GB,配 16GB 的 buffer pool 就够;如果查询分散在全部历史数据上,热集接近 20GB,就该配 32GB 以上的 buffer pool,整机内存留 64GB 更从容。innodb_buffer_pool_size 通常给到物理内存的 60% 到 75%,剩下的留给连接缓冲、临时表、操作系统页缓存。

避坑五:把云盘的标称 IOPS 当成实际随机写能力

问题:选型时看到标称 3 万 IOPS,上线后压测只有几千,批量写入一跑就抖。

为什么会发生:标称值通常是在特定队列深度、特定块大小、纯读或纯写条件下测出来的峰值;很多云盘的 IOPS 与容量挂钩,容量小则配额低;还有突发配额机制,短时间能冲高,长时间跑会被限制在基线水平;网络存储还会叠加网络延迟。而数据库典型负载是小块、高并发、读写混合。

怎么判断:用 fio 按自己的块大小(数据库通常是 16KB)和读写比例压测,跑满 30 分钟以上看稳态值,别看 1 分钟的峰值。同时看延迟分布,平均延迟好看但 P99 抖动大的盘,数据库用起来照样卡。

怎么规避:数据库的随机读能力优先看实测稳态 IOPS 和 P99 延迟;redo log 和刷脏路径对顺序写和 fsync 延迟敏感,要单独看 fsync 的表现。容量规划时留出 30% 以上的余量给备份、批量任务和异常高峰。

硬件选型的确定性从哪来

数据库对硬件的要求其实很朴素:足够大的内存装热集、足够强的随机读 IOPS 扛回表、稳定的 fsync 延迟扛写入。虚拟化和网络存储在前两项上会引入不确定性——邻居的负载会体现在你的 IO 延迟抖动里,而抖动是随机的,排查时最难复现。

一万网络(朗玥科技旗下)深耕 IDC 19 年(成立于 2007 年),深圳南山总部,自营机柜最快 1 分钟上架。对排查慢查询这种需要稳定基线的工作来说,裸金属的价值在于没有虚拟化层的干扰:你压测出来的 IOPS 就是这张盘的 IOPS,你看到的 await 就是真实排队,不会因为宿主机上另一个租户跑批处理而失真。大内存加 NVMe 的组合,是这类场景常见的选型方向。

配置项建议规格适用场景参考月租价格性质
验证机E5-2620 / 32G / 1T热集 8GB 以内、QPS 500 以下,用于压测与方案验证¥999/月官网在售价,以官网实时价为准
主力数据库机E5-2698v4×2 / 32G / 1T,配 NVMe热集 16 至 24GB,回表密集、对随机读延迟敏感¥3999/月官网在售价,以官网实时价为准
内存扩容按热集测算后加至 64G / 128G热集超过 32GB 的历史数据表需询价以官方实时报价为准
磁盘选型NVMe 承担数据目录,SATA SSD 用于冷数据与备份随机读密集与顺序写场景分离需询价以官方实时报价为准
数据安全免费系统盘每日 3 份快照,30 秒回滚大表 DDL 前必做的回滚点随服务提供官方服务说明

加索引的代价:写入、空间、统计信息刷新

索引不是免费的加速器,它是一笔用写入性能和空间换读取性能的交易。下单之前先把账单算清楚。

写入放大是怎么产生的

每张二级索引都是一棵独立的 B+ 树。插入一行,主键树要写,每个二级索引也要写;更新索引列时,对应记录要删除再插入;页满触发页分裂,要搬移数据、可能产生多次页写入。索引从 2 个加到 6 个,写入路径的 IO 量大致翻倍还不止。

写入压力体现在几个地方:redo log 生成量上涨,刷脏跟不上时出现检查点抖动,表现为写入周期性卡住;change buffer 能缓冲二级索引变更,但读多写少的表上反而增加合并负担;从库要重放同样的索引维护,复制延迟变大。

避坑三:盲目建联合索引导致写入放大与统计信息抖动

问题:为了覆盖各种查询组合,一口气建了七八个联合索引,写入明显变慢,而且执行计划变得不稳定。

为什么会发生:联合索引存在前缀冗余——有了 (a, b, c),单独的 (a) 和 (a, b) 基本就是重复维护。索引变多之后,优化器可选路径呈组合式增长,代价估算的微小偏差会导致它在几条相近路径之间来回跳,同一条 SQL 的执行计划在不同时间段不一样。

怎么判断:用 pt-duplicate-key-checker 或者 sys schema 的冗余索引视图扫一遍;对比 information_schema 里索引大小和数据大小的比值,索引体积接近甚至超过数据量时,说明已经过度了;观察加索引前后的 Handler_write 与磁盘写 IO 变化。

怎么规避:按查询反推索引,而不是按字段组合穷举。一个原则:等值条件列在前,范围条件列在后,排序分组列接在后面;枚举类低选择性列放在联合索引里做过滤可以,单独给它建索引通常没意义。删索引之前先确认没有其它查询在用,可以用 performance_schema 或者慢日志的语句指纹做交叉验证。

大表加索引会不会锁表

MySQL 5.6 之后支持 Online DDL,8.0 进一步支持 instant 加列。加索引通常可以走 ALGORITHM=INPLACE, LOCK=NONE,不阻塞 DML。但风险点仍在:DDL 首尾需要短暂的元数据锁,前面若有未提交的长事务,DDL 会卡住并连带堵住后续所有查询——这是生产事故的高发点;建索引期间的 IO 和 CPU 开销会拖慢业务;从库串行重放会拉大复制延迟。

稳妥做法:先查 information_schema.innodb_trx 确认没有长事务,选择低峰窗口,用 pt-online-schema-change 或 gh-ost 这类工具做限速可中断的变更,动手前留好快照与回滚方案。

统计信息刷新也是一笔账

索引变更、大批量导入或删除都会触发统计信息变化。持久化统计信息默认在变更行数达到一定比例时自动重算,这个重算本身要采样、要写盘。批量导入之后执行一次 ANALYZE TABLE,让执行计划尽快稳定下来,比等它自己触发更可控。

慢查询治理的推进顺序

把前面几章串起来,治理顺序其实是有先后的。跳步去做,大概率返工。

五步走,每一步都有可验证的产出

第一步,定口径。把 long_query_time 调到业务预算对应的量级,配好过滤参数,确认慢日志记录的是业务语句而不是管理任务。产出:一份能代表真实情况的慢日志。

第二步,做聚合。按总耗时和 P99 各排一次序,得到资源清单和体验清单。产出:Top 10 语句及其指纹、次数、耗时分布。

第三步,看执行计划。对 Top 10 逐条跑 EXPLAIN,看 type、rows、Extra、key_len,判断属于扫得多、回表多还是等得久。产出:每条语句的瓶颈归类。

第四步,查内存和磁盘。算命中率和热集,用 iostat 看真实 IO 压力与 await。这一步决定要不要动硬件——热集装得下且磁盘很闲,就不要升配置。

第五步,才轮到索引。基于前四步决定加什么索引、能否改成覆盖索引、要不要删冗余索引,并评估写入代价。产出:含回滚预案的变更清单。

变更之后要复测 rows、回表次数、P99、命中率、磁盘 await 五个指标。只看耗时容易漏掉「查询快了但写入慢了」这种转移。

先验证再长期投入

硬件调整不像改 SQL 那样可以秒级回滚。内存从 32G 加到 128G、磁盘从 SATA SSD 换成 NVMe,都需要实际跑一段业务流量才能确认收益。一万网络(朗玥科技旗下)深耕 IDC 19 年(成立于 2007 年),提供华南、华东、华北、中国香港及海外多节点,裸金属与云主机都可以先按月租用,跑一轮真实压测和灰度流量,确认命中率和 IOPS 的改善之后再决定是否长期投入,这样试错成本可控。期间遇到选型判断,7×24 中文工单 5 分钟响应,硬件故障 10 分钟自动迁移。

慢查询治理里最常被问到的七个问题

慢查询阈值设多少合适

不要沿用 1 秒。把接口的 P99 目标拆开,数据库这一段的预算通常是接口预算的 30% 到 50%,接口 500 毫秒返回就把阈值定在 0.15 到 0.25 秒。设完先跑一周看日志量,如果一天几十万条,说明要么阈值依然偏松,要么库里有系统性问题需要分层治理。同时配 min_examined_row_limit 过滤小表全扫,配 log_slow_admin_statements 记录管理语句。阈值不是一次性设定,业务量和硬件变了都需要重新校准。

索引建了为什么还是慢

四种常见原因。一是用上了索引但回表太多,扫描行数少不代表随机读少,这时要看 Extra 有没有 Using index。二是优化器选择性判断后主动放弃索引,低选择性字段上回表成本高于全扫。三是统计信息偏差,cardinality 估算不准导致执行计划抖动,需要调大采样页数或建直方图。四是写法问题,列上套函数、隐式类型转换、前导通配、缺少前导列都会让索引用不上。先跑 EXPLAIN 确认 key 和 key_len,再对号入座。

回表是什么,怎么避免

二级索引叶子节点只存索引列和主键值,取其余字段必须回聚簇索引再读一次,这一步是随机 IO,行多的时候占总耗时的绝大部分。避免的办法是建覆盖索引,把查询返回的列放进索引,EXPLAIN 的 Extra 出现 Using index 就说明不回表了。代价是索引变宽、体积变大、写入维护成本上升,所以只建议用在读多写少、返回列固定的场景。另一个方向是减少扫描行数本身,让回表次数跟着降下来。

数据库服务器内存该给多大

按热集倒推,而不是按数据量倒推。热集是高频访问的索引页加数据页,可以从 information_schema 的容量视图估算主键树和二级索引大小,再乘活跃比例。热集 8GB 的库给 16GB buffer pool 就够,热集 20GB 就该考虑 32GB 以上。innodb_buffer_pool_size 一般占物理内存 60% 到 75%,剩余留给连接缓冲、临时表和系统页缓存。命中率长期低于 99% 时,加内存的收益通常高于加 CPU。

SSD 还是 NVMe,随机读差别有多大

差距主要在队列深度和小块随机读上。SATA SSD 的随机读一般在数万 IOPS 量级,NVMe 可以到数十万,同时单次访问延迟更低、队列更宽。数据库 16KB 小块随机读的典型负载,正好是 NVMe 优势最大的区间。但要注意,标称 IOPS 是在特定条件下测的,实际值受容量配额、读写比例、稳态时长影响,选型时应该用 fio 按自己的块大小和读写比跑满 30 分钟看稳态。回表密集的场景优先 NVMe,冷数据和备份盘用 SATA SSD 更经济。

大表加索引会不会锁表

5.6 之后的 Online DDL 支持 ALGORITHM=INPLACE, LOCK=NONE,加索引过程中一般不阻塞 DML。但风险点仍在:DDL 首尾需要获取元数据锁,前面若有未提交的长事务,DDL 会卡住并连带堵住后续所有查询;建索引期间的 IO 和 CPU 开销会拖慢业务;从库串行重放会拉大复制延迟。规避办法是执行前检查 innodb_trx、选择低峰窗口、用限速可中断的工具做变更,并提前保留快照以便回滚。

慢查询日志要不要一直开

建议一直开,但要控制粒度。慢日志是事后复盘唯一的完整证据,出问题才临时开,往往已经错过了现场。长期开的代价是同步写文件的 IO 开销和磁盘占用,可以通过合理阈值、min_examined_row_limit 过滤、日志轮转与定期清理来控制。QPS 特别高的库可以改为按会话采样,或者主要依赖 performance_schema 的语句摘要聚合。无论如何,不要把 log_queries_not_using_indexes 长期打开,它会在小表全扫上产生海量记录。

慢查询治理的验收标准

治理容易陷入「改了就算完成」。没有验收标准,改动和收益对不上账,下次出问题还是从零排查。以下六条可以直接作为验收清单。

  • 口径可复现:慢日志阈值与业务预算对齐,过滤规则写进配置管理,任何人拉同一时间段的日志都能得到同样的 Top 列表。
  • 分位数改善:目标 SQL 的 P99 与 max 同时下降,而不是只有平均耗时好看。平均值下降但 P99 不变,通常说明只是高峰期被错开了。
  • 扫描效率:Rows_examined / Rows_sent 的比值显著下降,索引相关的优化应当让这个比值收敛到接近结果集量级。
  • 命中率达标:buffer pool 命中率回到 99% 以上,并且在业务高峰和批量任务之后不出现长时间下滑。
  • 磁盘余量:稳态 await 处于可接受区间,%util 或饱和度指标留有余量,突发流量下不会立刻打满。
  • 没有转移代价:查询变快的同时,写入耗时、主从延迟、磁盘占用没有明显恶化。出现转移,说明这次优化只是把瓶颈挪了个位置。

把这六条与治理前的基线对比,才算完成闭环。达不到第三条和第六条,通常说明索引改对了但内存或磁盘没跟上,或者读取收益是用写入代价换来的。下一步不是继续加索引,而是重新确认扫描行数、回表与 IOPS 的关系。

本篇数据来源与口径说明

MySQL 8.0 官方参考手册(慢查询日志参数、EXPLAIN 输出格式、InnoDB 统计信息与持久化采样、Online DDL 算法与锁行为):https://dev.mysql.com/doc/refman/8.0/en/

Percona Toolkit 官方文档(pt-query-digest 聚合口径与分位数输出、pt-duplicate-key-checker 冗余索引检查、pt-online-schema-change 变更机制)。

sysstat 官方手册(iostat 的 await、%util、队列与饱和度指标含义及在多队列设备上的局限)。

一万网络官网价目与节点说明(裸金属配置、多节点布局、快照与防护服务基线):https://www.idc10000.net/

文中容量为量级估算,用于说明倒推方法,实际数值需按行格式、碎片率、填充因子与真实活跃比例重新核算;IOPS 与延迟以现场 fio 实测为准,标称参数不作为选型依据。价格以官网实时报价为准。


上一篇:2026 etcd与ZooKeeper集群服务器怎么配:法定人数、fsync延迟与跨机房的现实

下一篇:2026日志采集链路服务器怎么配:背压、本地缓冲与丢日志的真实根因