先说说我踩过的坑。去年双十一前夜,我负责的电商系统突然响应变慢,用户点个加购按钮要转三秒。监控面板上数据库CPU飙到95%,连接数爆满,日志里全是超时告警。当时我第一反应是加机器,结果加了四台只撑了半小时,重新打回原形。后来翻慢查询日志才发现,罪魁祸首是一条关联了八张表的统计查询,每次用户刷新首页都要跑一遍。这条SQL单次执行要4.7秒,高峰期一秒并发几十次,再多的机器也扛不住。所以排查数据库性能瓶颈,第一步永远不是加资源,而是先搞清楚慢在哪、为什么慢。

慢查询日志是第一个要看的。MySQL里设置longquerytime=1,把超过一秒的SQL全部记录下来。但光看日志不够,还得用EXPLAIN分析执行计划。我见过太多人只看SQL本身,忽略了执行计划里那行“Using filesort”或者“type=ALL”的全表扫描。有一次排查一个订单查询,明明加了索引,但执行计划显示索引没生效,翻来覆去查才发现是字段类型不一致——订单号在表里是varchar,查询参数传的是int,MySQL隐式转换导致索引失效。这种问题不执行EXPLAIN根本看不出来,经验再丰富也白搭。
索引优化是重头戏,但别一上来就乱加索引。我见过最夸张的一张表加了二十多个索引,写入性能惨不忍睹。正确的做法是先看慢查询里高频出现的WHERE条件和ORDER BY字段,然后针对性地建联合索引。记住最左前缀原则,比如查询条件是a=1 and b=2 order by c,那就建(a,b,c)的联合索引,别单独建三个单列索引。还有覆盖索引的技巧,如果查询只需要id和name两个字段,而索引里已经包含这两个字段,那就能避免回表,性能提升立竿见影。但索引也不是万能的,区分度低的字段比如性别、状态,建了索引反而浪费空间。
再说说连接池和连接数的问题。很多人一看到报错“Too many connections”就调大maxconnections,这其实是饮鸩止渴。每个连接都要占用内存和CPU,连接数越大,单连接能分到的资源越少,整体吞吐反而下降。正确的思路是检查应用层的连接池配置,比如HikariCP的maximumPoolSize设置是否合理。我习惯把连接池大小设为CPU核心数乘以2再加1,这个经验值在大多数场景下都够用。还要检查是否有连接泄漏,用SHOW PROCESSLIST看长时间Sleep的连接,排查代码里有没有忘记关闭连接的情况。
缓存是救命稻草,但很多人不会用。数据库瓶颈往往不在数据库本身,而在应用层重复查询。比如用户信息这种热点数据,每次请求都查库,并发一高数据库就顶不住。这时候引入Redis缓存,把用户信息序列化存进去,设置合理的过期时间,数据库压力立刻降下来。但缓存要注意穿透和击穿问题——穿透是查询一个不存在的key,每次都会打到数据库,可以用布隆过滤器或者缓存空值解决;击穿是热点key过期瞬间大量请求打到数据库,可以用互斥锁或者逻辑过期时间处理。我倾向于用逻辑过期时间,设置一个较长的物理过期时间,然后后台异步刷新,简单实用。
再聊聊数据库本身的参数调优。MySQL的innodbbufferpoolsize是重中之重,一般建议设为物理内存的60%-70%。我见过一台128G内存的机器,buffer pool只配了2G,导致大量磁盘IO,性能差得离谱。还有redo log的大小,默认的2个文件每个48M偏小,高并发写入时会频繁刷盘,建议调到每个1G以上。这些参数不用背,关键是理解原理——buffer pool是缓存索引和数据页的,配小了命中率低,配大了浪费内存;redo log是崩溃恢复用的,太小了刷盘频繁,太大了恢复时间变长。要根据实际业务负载去调整,没有一劳永逸的配置。
分区表和分库分表是杀招,但别轻易用。分区表适合按时间范围查询的场景,比如日志表按月分区,查询时只扫对应分区,性能提升明显。但分区表也有坑,比如分区键没选好,查询不走分区裁剪,反而更慢。分库分表更复杂,涉及分布式事务、全局ID生成、跨库join等问题,如果不是数据量真的到了单表几千万甚至上亿的级别,我劝你别碰。我见过一个小公司,数据量才几百万,就急着上分库分表中间件,结果运维成本翻了好几倍,性能没提升多少,还引入了新的故障点。
说一句大实话:数据库优化没有银弹,90%的问题都出在慢SQL上。你花三天时间优化一条慢SQL,可能比加十台服务器更管用。排查时记得先看慢查询日志,再分析执行计划,然后对症下药调整索引和参数。别一上来就动架构,先把手里的牌打好。这套「数据库性能瓶颈排查指南」的核心就是:慢SQL是突破口,索引是主要手段,缓存是辅助,架构调整是万不得已的选择。按这个顺序来,大部分性能问题都能在半天内搞定。


