上周帮一个电商客户排查数据库问题,他们的订单表才两千多万行,每次大促期间查询慢得像蜗牛爬。我上去一看,好家伙,全表扫描,索引一个没建。这种场景我见得太多了,MySQL性能问题从来不是单一原因造成的,而是从硬件到SQL、从配置到架构层层叠加的结果。今天我就把这套从瓶颈定位到极致优化的实战方法摊开来讲。

先说说怎么定位瓶颈。很多人一上来就开慢查询日志,这没错,但慢查询日志只是线索,不是结论。我习惯先用看几个关键计数器:、、。如果长期超过CPU核数,说明有查询在死磕;如果飙升,那大概率是锁竞争。这时候再用抓现场,看哪些查询卡了多久。记住,定位瓶颈要快准狠,别在日志里翻半天,先看核心指标,再顺藤摸瓜。
慢SQL的优化,我的经验是先把过滤条件拆开看。比如,如果status的区分度极低,只有1和2两种值,那建在status上的索引就是摆设。真正该建的是这样的复合索引,让date字段先过滤掉大部分数据。还有个大坑是函数包裹列,这种写法,索引直接失效,得改成范围查询。这些细节,每个都能让查询从秒级降到毫秒级。
索引不是越多越好,这是新手最容易犯的错。我见过一张表建了十几个索引,结果每次写入要更新所有索引,写性能直接崩掉。合理的索引策略是:单表索引控制在5个以内,复合索引优先覆盖高频查询,区分度低的字段别单独建索引。还有个技巧叫覆盖索引,比如查询只需要和两个字段,那建的复合索引就能让查询直接走索引,不用回表。这一步优化,有时候比加缓存还管用。
再往深一层,是配置参数的调优。这个参数,我见过太多人设成默认值128M,白白浪费内存。一台16G内存的机器,至少要分8G给buffer pool,让热数据尽量待在内存里。设为1是安全模式,每次提交都刷盘,但如果业务能接受最多丢1秒数据,改成2能显著提升写入吞吐。也别盲目调大,连接数上来后线程切换开销反而拖垮性能,配合和合理设置,比单纯堆连接数靠谱。
分区表是个让人又爱又恨的功能。用好了,比如按时间分区的日志表,查询时能直接跳过无关分区,性能提升立竿见影。但用不好就是灾难,比如分区键选了个区分度极差的字段,或者分区数量设太多,反而增加了查询时的元数据开销。我的建议是:数据量过亿、有明显时间维度的表才考虑分区,否则老老实实做归档或者用分库分表,别把分区当万能药。
缓存策略也是性能调优的重要一环。MySQL自带的query cache在8.0已经废弃了,别再用。真正靠谱的是Redis这种外部缓存,把热点数据、频繁查询的结果缓存起来,能挡住80%的读流量。但要注意缓存一致性,别让数据更新了缓存还留着旧的。我常用的模式是Cache Aside,先更新数据库,再删除缓存,下次查询时重新加载。这个模式虽然简单,但能避免脏读。
说说架构层面的优化。读写分离是经典方案,一主多从,主库负责写入,从库分担读压力。但要注意主从延迟的问题,半同步复制或者并行复制能缓解,但别指望完全消除。分库分表则是终极手段,比如按用户ID取模分到4个库,每个库的表结构一样,但数据量只有原来的四分之一。这招虽然能解决单表瓶颈,但会引入分布式事务和跨库join的问题,非到万不得已别用。
回过头来看整个调优流程,我总结成一句话:先定位,再优化,验证。定位要快,优化要准,验证要狠。别一上来就调参数、改架构,那是无头苍蝇。从SQL到索引,从配置到架构,一层层往下走,每步都要有数据支撑。MySQL性能调优不是玄学,是门手艺活,靠的是对原理的理解和实战的积累。


