分库分表策略:宠友IM源码中的聊天数据水平扩展实践
IM系统一旦跑到千万级消息量,单表设计基本都会触顶。不是“慢一点”,而是索引开始失效、分页查询抖动、写入延迟明显。宠友信息在「宠友IM」源码的演进里,没有一开始就做复杂分布式,而是在数据量上来之后逐步引入分库分表,把压力从单点拆开。
这篇只讲一个主题:MySQL分库分表在IM聊天数据中的落地方式。
一、什么时候必须分表
判断是否需要分表,不看QPS,而是看几个指标:
- 单表数据量超过千万
- 索引命中率下降
- 分页查询出现明显抖动
- 写入出现锁等待
IM系统的特点是:
👉 数据只增不减,增长速度非常快
宠友IM在这个阶段的处理方式不是“优化SQL”,而是直接:
👉拆表
二、为什么优先选择“水平分表”
常见两种拆分方式:
- 垂直拆分(按字段)
- 水平拆分(按数据)
IM场景中,字段变化不大,瓶颈在数据量。
宠友IM选择:
👉按会话做水平分表
原因:
- 查询基本围绕session_id
- 会话之间互不影响
- 天然可以切分
三、分表规则设计
最简单也是最常见的一种:
👉取模分表
示例:
// 根据session_id分表 int tableIndex = Math.abs(sessionId.hashCode()) % 16; String tableName = "message_" + tableIndex;这样可以做到:
- 数据均匀分布
- 避免热点表
表结构类似:
message_0 message_1 ... message_15四、为什么不用“按时间分表”
很多人第一反应是按月份:
- message_202501
- message_202502
这种方式在IM里问题很多:
- 单个会话跨表查询复杂
- 历史消息翻页困难
- 热点数据集中在当前表
宠友IM没有采用时间分表,而是:
👉优先保证查询路径简单
五、查询路由设计
分表后最大的变化是:
👉 查询必须先定位表
宠友IM的做法:
- 所有查询都带session_id
- 通过同样的hash规则定位表
示例:
// 查询某个会话的消息 int index = Math.abs(sessionId.hashCode()) % 16; String table = "message_" + index; String sql = "SELECT * FROM " + table + " WHERE session_id = ? " + " ORDER BY msg_id DESC LIMIT 20";关键点:
- 路由规则必须一致
- 不能出现多种计算方式
六、分页查询在分表下的处理
IM聊天记录基本都是“向上翻页”,分表后依然沿用:
👉基于msg_id的游标分页
查询方式:
- 不使用OFFSET
- 使用
msg_id < lastMsgId
好处:
- 不受数据量影响
- 索引稳定命中
分表不会影响这种分页方式,因为:
👉 每个会话只在一张表里
七、写入路径设计
写入流程:
- 根据session_id计算表
- 插入对应表
不做跨表写入,不做广播写入。
这样可以保证:
- 写入路径简单
- 性能稳定
八、分表后的索引策略
每张子表依然需要索引:
- (session_id, msg_id)
不要因为分表就减少索引。
反而更需要:
👉保证每张表查询路径清晰
否则分表后依然慢。
九、扩容问题
当16张表不够用时怎么办?
常见误区:
- 直接改成32张表
问题:
- 历史数据无法迁移
- 路由规则变化
宠友IM的处理思路:
👉预留扩容空间
比如:
- 初期直接设计为32或64张表
- 实际使用一部分
避免后期迁移成本。
十、跨表问题处理
虽然大部分查询都在单表,但还是会遇到:
- 全局搜索
- 后台统计
宠友IM没有强行在数据库层解决,而是:
👉交给搜索引擎或离线任务
数据库只负责:
- 单会话查询
- 高性能读写
避免:
- 跨表JOIN
- 全表扫描
十一、分表后的事务问题
分表后一个明显变化:
👉 跨表事务很难做
宠友IM的处理方式:
- 避免跨表事务
- 通过业务保证一致性
比如:
- 先写消息
- 再更新会话状态
即使失败:
- 可以补偿
十二、实际踩过的几个坑
1)hash不均匀
使用不合理字段:
- 数据集中到某几张表
解决:
- 使用session_id做hash
2)路由规则变更
不同模块用不同算法:
- 查询不到数据
解决:
- 统一路由工具类
3)调试困难
开发环境只有一张表:
- 上线后问题暴露
处理:
- 本地模拟分表结构
4)SQL拼接错误
动态表名拼接不规范:
- SQL注入风险
- 执行失败
解决:
- 严格控制表名来源
十三、这套分表策略的特点
宠友信息在IM系统里的分表设计有几个很明显的工程思路:
- 不追求复杂分布式
- 不引入中间层路由
- 直接在业务层控制
核心逻辑很简单:
👉 所有数据围绕session_id
👉 所有路由基于同一规则
IM系统的数据增长是线性的,分表只是时间问题。关键不在于“分不分”,而在于:
👉分完之后查询是否依然简单稳定
😎宠友IM源码在市面上已经很成熟啦~https://www.chongyou.info/1/product/im.html
