数据库性能优化
概述
数据库是游戏服务器的容量瓶颈之一:玩家数据、排行榜、流水都依赖它。性能优化的思路是别让数据库成为瓶颈:SQL 层面优化(索引/覆盖索引)、连接池调优(HikariCP)、Redis 批量操作(Pipeline)、大 Key 拆分。本文给出完整的数据库性能优化清单。
一、MySQL 慢查询优化
1.1 慢查询定位
慢查询排查:
开启慢查询日志(long_query_time=1s)
按耗时/频率排序
用 EXPLAIN 分析执行计划
分析重点:
是否走索引(type 字段)
扫描行数(rows)
是否文件排序(filesort)/临时表// EXPLAIN 关键列
EXPLAIN SELECT * FROM player_currency WHERE player_id = 123;
// type: const/ref(好);ALL(全表,需加索引)1.2 索引优化
索引设计:
覆盖索引:查询字段都在索引中(避免回表)
复合索引:最左前缀原则(列顺序)
索引选择性:区分度高的列在前
避免索引失效(函数/隐式转换/前导模糊)
// 覆盖索引示例(查询只需 player_id/amount)
CREATE INDEX idx_cur_player_amount
ON player_currency(player_id, amount);SQL 改写:
避免 SELECT *(按需取列)
深分页优化(游标/子查询,见排行榜)
批量操作(IN 列表/批量 insert)
大事务拆分(分批提交)二、连接池调优
2.1 HikariCP 参数
HikariCP 关键参数:
maximumPoolSize:最大连接数
minimumIdle:最小空闲
connectionTimeout:获取连接超时(默认 30s)
maxLifetime:连接最大生命周期(防超时回收)
poolName:池名(监控)
配置依据:
并发量 × 单请求耗时 → 连接数估算
连接数 = 业务并发峰值所需(不宜过大)
过大连接数 → 数据库连接开销反而大// HikariCP 示例配置
spring.datasource.hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 3000
max-lifetime: 1800000池相关调优:
连接池耗尽排查(等待时间增长)
获取连接慢 → 池太小/连接被占用久
长事务占用连接 → 拆分/缩短事务2.2 连接与事务
连接使用原则:
事务内只做必要操作(锁内最小化,见并发章节)
避免连接泄漏(try-with-resources/超时释放)
读写分离(读走从库,见存储章节)三、Redis Pipeline 批量操作
3.1 Pipeline 原理
Pipeline:
多条命令一次发送(减少 RTT)
服务端批量执行,一次返回
对比:
逐条命令:N 次网络往返
Pipeline:1 次网络往返(批量执行)
适用:
批量写入(排行榜/流水)
批量读取(好友在线状态,见社交章节)
高频计数器合并// Pipeline 批量写入示例
Jedis jedis = ...;
Pipeline p = jedis.pipelined();
for (PlayerScore s : scores) {
p.zadd("rank:power:daily", s.score, s.playerId);
}
p.sync(); // 一次提交Pipeline 注意:
一次性命令过多 → 拆批(如 1000 条一批)
不保证原子性(逐条执行)
需要原子 → Lua 脚本(见商城限购)3.2 批量读取
批量读取场景:
玩家信息批量补全(榜单查询)
好友在线状态(MGET)
// 批量读取
List<String> onlineFlags = jedis.mget(keys); // 一次返回四、大 Key 拆分
4.1 大 Key 问题
大 Key 危害:
单次操作耗时长(阻塞 Redis 其他命令)
内存占用不均(节点倾斜)
删除大 Key 阻塞(DEL 大集合)
识别:
大 String(>10KB)
大集合(Hash/Set/ZSet > 万级)
定期扫描(redis-cli --bigkeys)常见大 Key:
全服排行榜单 Key(百万成员)
大 Hash 玩家全量数据
大 Set 全服在线集合4.2 拆分策略
拆分方案:
排行榜 → 分段(分数区间分 Key,见排行榜)
大 Hash → 按字段分组(多 Key 或 Hash 分片)
大 Set → 按前缀分 Key(会员列表按字母/ID 段)
删除大 Key:
用 SCAN 分批删(避免阻塞)
或 rename + 异步删
Redis 4.0+ UNLINK(异步释放)// 大 Key 分批删除示例
String cursor = "0";
do {
ScanResult<String> r = jedis.scan(cursor, new ScanParams().count(100));
for (String key : r.getResult()) {
jedis.unlink(key); // 异步删除
}
cursor = r.getCursor();
} while (!cursor.equals("0"));五、Redis 其他优化
热点 Key:
热 Key 单点瓶颈(如爆款商品)
对策:本地缓存副本 / 拆 Key 分摊
过期策略:
集中过期 → 大量删除阻塞
对策:过期时间加随机抖动
持久化:
RDB/AOF 对性能的影响(见存储章节)
从库分担读压力六、实现要点
优化清单:
慢查询 → EXPLAIN + 索引/覆盖索引
深分页 → 游标优化
连接池 → 按并发配置 + 监控
Redis 批量 → Pipeline(拆批)
大 Key → 分段拆分 + 异步删除
热 Key → 本地缓存副本
优化流程:
1. 压测/监控找瓶颈(慢 SQL、连接池等待、大 Key)
2. 针对性优化
3. 复测对比
常见坑:
索引滥用(写放大)→ 按查询设计
连接池过大 → 数据库连接开销
Pipeline 无批 → 阻塞 Redis
大 Key 漏拆 → 定期扫描与其他系统衔接:
分表 → MySQL 存储章节
Redis 缓存 → Redis 章节
压测 → 压测章节
监控 → 监控章节