单库瓶颈分析:连接数打满、磁盘IO瓶颈、慢查询拖垮全服

说实话,做游戏后端这些年,我见过太多项目死在「单库」这两个字上。

很多团队一开始觉得,一个MySQL实例够用了。用户量不大,数据量也不大,干嘛要搞分库分表?嗯,这个想法在日活1万以下确实没问题。但一旦用户量上来,单库的瓶颈就会像多米诺骨牌一样,一个接一个倒下来。

今天我就拿一个真实案例复盘,带你看清楚单库是怎么被拖垮的。

1. 连接数打满:第一个倒下的骨牌

先说说连接数的问题。

MySQL默认的最大连接数一般是151。你可能会觉得,151个连接不少了啊?但你要知道,游戏服务器可不是只有一个进程在连数据库。

我经历过的一个项目,上线第三天就出事了。玩家反馈登录超时,充值不到账,公会数据读不出来。我一看监控,MySQL连接数直接飙到150+,新连接全部排队等待。

为什么会这样?

我们当时有8个游戏服务器进程,每个进程维护了一个20连接的连接池。8×20=160,已经超过151了。再加上管理后台、运营工具、日志采集程序……连接数不爆才怪。

关键点:连接数打满的直接后果就是——新的数据库请求被拒绝。玩家登录失败、数据保存失败、排行榜刷新失败。整个游戏体验瞬间崩塌。

我当时紧急处理的方式是:

  • 先把连接池大小从20降到15
  • 把管理后台的连接池从10降到3
  • 临时把max_connections调到300

但这只是治标不治本。你想想看,连接数只是表象,真正的问题是——所有请求都挤在一个数据库上。

2. 磁盘IO瓶颈:读写争抢的噩梦

连接数的问题刚稳住,磁盘IO又出事了。

游戏服务器的数据库,读写比例大概在7:3左右。读多写少,但问题是——写操作往往很重。

比如玩家下线时,要保存背包数据、任务进度、战斗记录、社交关系……这些数据可能分布在十几张表里。一次下线操作,可能要产生几十个写请求。

我记得有一次,晚上8点高峰期,磁盘IO等待时间直接飙到5000毫秒以上。什么意思?就是数据库发一个磁盘读写请求,要等5秒才能拿到结果。

你想想看,玩家点一下「强化装备」,5秒后才看到结果。这游戏还能玩吗?

避坑指南:我曾经犯过一个错误——把所有日志表和数据表放在同一个磁盘上。结果日志写入频繁,把数据读取的IO带宽全抢走了。后来我把日志表迁移到单独的磁盘,IO等待时间直接降了70%。

磁盘IO瓶颈的典型表现:

  • 慢查询日志里全是等待IO的条目
  • iostat显示磁盘利用率接近100%
  • 数据库的QPS(每秒查询数)突然下降

说白了,磁盘IO就是数据库的「咽喉」。一旦这里堵了,整个系统都得跟着慢下来。

3. 慢查询拖垮全服:最后一根稻草

连接数和磁盘IO的问题还没完全解决,慢查询又来了。

慢查询这个东西,平时可能没什么感觉。但一旦并发上来,一个慢查询就能拖垮整个数据库。

我印象最深的一次——某个排行榜查询,SQL写得特别随意:

SELECT * FROM player_score 
ORDER BY score DESC 
LIMIT 100

看起来很简单对吧?但问题是player_score表有500万行数据,而且score字段没有索引。每次执行这个查询,MySQL都要做一次全表扫描,然后排序。

这个查询平时执行要3秒。高峰期并发100个这样的查询,数据库直接卡死。所有其他查询都在排队等这个慢查询释放资源。

真实数据:那次事故中,一个慢查询导致全服所有数据库操作延迟从5毫秒飙升到8秒。整整持续了15分钟,玩家在线率从80%跌到30%。

慢查询的典型特征:

  • 执行时间超过1秒
  • 扫描行数巨大(几十万甚至上百万)
  • 没有使用索引,或者索引选择错误

我当时排查慢查询的方法:

  1. 开启慢查询日志,设置long_query_time=1
  2. 用pt-query-digest分析慢查询日志
  3. 找到执行次数最多、总耗时最长的SQL
  4. 用EXPLAIN分析执行计划

嗯,这里要注意——慢查询日志本身也会消耗IO。高峰期不要开太细的日志级别,否则会雪上加霜。

4. 三个瓶颈的连锁反应

这三个问题不是孤立的。它们会互相放大。

问题 直接后果 连锁反应
连接数打满 新请求被拒绝 玩家重试→更多连接请求→更严重的打满
磁盘IO瓶颈 读写延迟飙升 查询变慢→连接持有时间变长→连接数更容易打满
慢查询 单查询耗时过长 占用连接和IO→其他查询排队→整体延迟爆炸

你看,这三个问题就像三个环,环环相扣。任何一个环节出问题,都会把其他环节拖下水。

我的建议:做数据库容量规划时,不要只看平均负载。要看峰值负载,而且要留50%以上的余量。比如你算出来高峰期需要100个连接,那就配置200个。磁盘IOPS也是同理。

5. 复盘总结:单库的极限在哪里?

经过这次事故,我总结了一个单库的「安全边界」:

  • 连接数:不要超过最大连接数的60%
  • 磁盘IO:利用率不要超过70%
  • 慢查询:单个查询超过500ms就要优化
  • 数据量:单表超过500万行就要考虑分表

当然,这些数字不是绝对的。不同的硬件配置、不同的业务场景,边界值会有差异。但有一点是确定的——

单库不是银弹。它只是一个起点。

当你的游戏用户量增长到一定程度,单库的瓶颈一定会出现。与其等到事故发生了再手忙脚乱,不如提前做好分库分表的规划。

下一章,我会详细讲分库分表的策略和实战方案。到时候你会看到,分库分表不是「要不要做」的问题,而是「什么时候做」的问题。

嗯,今天就先聊到这里。记住这个案例,它可能会帮你避免一次线上事故。