订单列表页偶发 3 秒,多数人的第一动作是给 where 条件里的字段加个索引。加上之后查询确实快了一点,写入却慢了一截,凌晨的批量任务从 20 分钟拖到 40 分钟。这种交换到底值不值,得先看清瓶颈落在哪一层。
先把结论摆出来:索引只是四个变量里的一个。扫描行数、回表次数、内存里装不装得下热数据、磁盘能扛多少随机 IO,这四样共同决定了一条 SQL 的耗时。一律归因于索引,结果就是索引越加越多,写入变慢、空间翻倍、执行计划开始抖动。
加索引能把全表扫描变成索引定位,逻辑上没毛病。问题在于,索引解决的是「找到行的位置」,它不保证「把行取出来」这件事便宜。
假设 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 的默认值或者 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% 这些统计。问题来了:该按哪个排序?
问题:报表里平均响应 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 量不大,却要在接口层收敛超时与重试,避免雪崩。两件事可以同时推进,动的是不同层面。
EXPLAIN 输出十几列,日常排查真正需要先看的是 type、rows、Extra,配合 key 和 key_len 确认索引到底用上了几列。
从好到差大致是 system、const、eq_ref、ref、range、index、ALL。const 和 eq_ref 出现在主键或唯一索引等值匹配上,基本不会慢;ref 是普通二级索引等值匹配,健康;range 要看范围多大;index 是全索引扫描,只是扫描的东西比全表小一点;ALL 在千万行表上等于几秒起步。
这里有个常见误解:看到 ALL 就一定要消灭。几千行、常年驻留在 buffer pool 里的小表,全扫成本是微秒级,为它建索引纯属负担。全扫该不该治,取决于这张表会不会长大、这条语句跑得多频繁。
rows 来自统计信息和代价模型,可能和实际差一个数量级。判断预估准不准,用 SHOW STATUS 的 Handler_read_* 系列或者慢日志里的 Row_examined 去对。EXPLAIN ANALYZE(MySQL 8.0.18 起)会把真实执行时间和实际行数打出来,排查时比普通 EXPLAIN 可靠得多,注意它会真的执行这条语句,别在生产的 UPDATE 上跑。
filtered 这一列也常被忽略,它表示存储引擎返回的行里预计有多少比例能通过 where 条件。rows 不高但 filtered 很低,说明索引只帮你筛掉了第一层,剩下的过滤还是在 Server 层做,这种时候联合索引少了一列是很常见的原因。
Using index 是好事,代表覆盖索引,不用回表。Using index condition 代表索引条件下推(ICP),where 的一部分在存储引擎层就过滤掉了,能减少回表次数,但不等于不回表。Using where 表示 Server 层还要再过滤一次。Using filesort 和 Using temporary 是排序与临时表,中小结果集没事,大结果集会同时吃掉内存和磁盘。Using join buffer 说明关联字段没索引,走块嵌套循环,表一大就是灾难。
联合索引 (shop_id, status, create_time),where 只给 shop_id 和 create_time,key_len 会显示只用到 shop_id 那一段,status 一断,create_time 就接不上。看到 key_len 比预期短,先别急着加索引,先看条件写法是不是漏了中间那一列。
InnoDB 的主键索引是聚簇索引,叶子节点存整行数据;二级索引的叶子节点只存索引列加主键值。通过二级索引定位到主键之后,如果查询还要别的列,就得拿着主键回聚簇索引再读一次,这一步叫回表。
二级索引里的主键值是有序的(按索引列排),但主键对应的聚簇索引位置未必连续。一页 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。注意它会触发统计信息重算,别在业务高峰对大表跑。
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 高、磁盘利用率低:时间花在 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 后实测耗时对比 |
配置不是拍脑袋定的,可以从数据量和访问模式倒推。下面的数字是量级估算,实际还要按行格式、碎片率、填充因子修正。
问题:数据库慢,看监控 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%,剩下的留给连接缓冲、临时表、操作系统页缓存。
问题:选型时看到标称 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。
差距主要在队列深度和小块随机读上。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 长期打开,它会在小表全扫上产生海量记录。
治理容易陷入「改了就算完成」。没有验收标准,改动和收益对不上账,下次出问题还是从零排查。以下六条可以直接作为验收清单。
把这六条与治理前的基线对比,才算完成闭环。达不到第三条和第六条,通常说明索引改对了但内存或磁盘没跟上,或者读取收益是用写入代价换来的。下一步不是继续加索引,而是重新确认扫描行数、回表与 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 实测为准,标称参数不作为选型依据。价格以官网实时报价为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品