在业务规模持续增长时,单机MySQL很难支撑海量数据和高并发请求,利用MySQL构建分布式存储体系成为不少团队的现实选择。这类项目并不是简单部署多个实例,而是要在分片规则、数据路由、事务一致性和运维监控上做通盘设计。下面从实际落地角度,梳理一套可复用的开发经验。

一、为什么要用MySQL做分布式存储
很多系统初期采用单库单表,随着订单、日志或用户行为数据膨胀,磁盘IO和连接数很快成为瓶颈。直接引入专用分布式数据库固然可行,但团队对MySQL的运维和调优更熟悉,迁移成本更低。在保留MySQL稳定生态的前提下,通过分库分表把数据打散到多个物理节点,就能获得近似线性的扩展能力。
从项目经验看,MySQL做分布式存储的优势在于生态成熟:主从复制、备份工具、监控方案都很完善。缺点是原生不支持跨库事务和全局索引,需要自己在应用层或中间件层补齐。明确这一边界,才能合理设计架构,不盲目期待数据库替你解决所有问题。
二、分库分表的核心策略
2.1 哈希分片实践
最常用的做法是取业务主键(如用户ID)做哈希,再对分片数取模,将数据均匀落到不同库表。这样同一用户的数据集中存储,避免跨节点查询。我们在用户中心项目中,将uid作为分片键,分为16个库、每库64张表,整体支持十亿级用户档案。
哈希分片代码逻辑可简化为如下示例,注意所有比较和取模运算都应在应用内完成,再由数据源路由到对应节点:
// 根据用户ID计算分库分表位置
public static final int DB_COUNT = 16;
public static final int TABLE_COUNT = 64;
public static String route(long uid) {
int dbIndex = (int)(uid % DB_COUNT);
int tableIndex = (int)(uid / DB_COUNT % TABLE_COUNT);
return "db_" + dbIndex + ".user_" + tableIndex;
}
// 调用示例
String target = route(100023L);
System.out.println(target); // 输出 db_7.user_39
该方式的优点是数据分布均衡、单用户查询高效;缺点是扩容时需重新哈希,迁移量较大。因此我们预留了翻倍分片的能力,并在深夜低峰用脚本做数据搬运。
2.2 时间区间分片
对于日志、流水类数据,按天或按月分表更自然。新数据写入当前表,历史表转为只读并归档到廉价存储。这种冷热分离减少了热表体积,备份也更灵活。我们在交易流水系统采用按月分表,配合定时任务自动建表。
时间分片无需复杂路由算法,但容易出现单月数据爆量导致热点。实践中会结合哈希做二级拆分,例如202401月内再按商户ID哈希分16表,兼顾时序与自然扩展。
三、分布式事务与一致性保障
3.1 避开两阶段提交
跨分片写操作若使用XA两阶段提交,锁资源时间长,吞吐量下降明显。项目里我们改用本地消息表加补偿任务,实现最终一致性。即主库写业务数据同时记消息,异步线程轮询消息表投递到其余分片,失败则重试。
下面是一段本地消息表写入的简化代码:
-- 订单主库事务内执行
BEGIN;
INSERT INTO orders (oid, uid, amount) VALUES (1001, 2001, 50);
INSERT INTO local_msg (msg_id, topic, payload, status) VALUES (9001, 'shard_sync', '{"oid":1001}', 0);
COMMIT;
-- 异步任务扫描未发送消息
SELECT msg_id, payload FROM local_msg WHERE status = 0 LIMIT 100;
-- 发送至目标分片后更新 status=1
UPDATE local_msg SET status = 1 WHERE msg_id = 9001;
该方案牺牲了强一致,但系统可用性大幅提升。对金额类核心链路,会增加对账批处理,每天校准分片间差异,确保资损可控。
3.2 全局字典表处理
有些配置或维度数据需被所有分片共用,如城市编码表。我们将其作为全局表,在每个库都全量冗余一份。更新时通过广播SQL同步,读取时本地即可完成,避免跨库关联。
全局表不宜过大,且更新频率要低。若频繁变更,广播延迟会引发短暂不一致,因此只用于近乎静态的参考数据。
四、中间件与读写分离
4.1 借助中间件隐藏路由
应用直接感知分片规则会导致代码侵入严重。我们在项目中使用数据访问中间件,配置分片算法后,业务代码像操作单库一样写SQL。中间件解析语句中的分片键,改写并路由到后端真实节点,对开发透明。
中间件还统一处理了读写分离:写走主库,读可根据权重分散到多个从库。如下配置片段展示了读写数据源定义:
<dataSource name="master" url="jdbc:mysql://192.168.0.1:3306/db_0"/> <dataSource name="slave1" url="jdbc:mysql://192.168.0.2:3306/db_0"/> <rule readWriteSplitting="true" writeTo="master" readTo="slave1"/>
引入中间件后,运维同学可通过统一控制台观察慢查询和各分片负载,快速定位热点库。但中间件本身需高可用部署,防止成为新单点。
4.2 多副本与故障切换
每个分片采用一主两从,主库宕机时从库接管。配合健康检查脚本,三十秒内完成切换并通知中间件刷新路由。项目演练证明,该架构在单节点失效时整体服务无感知。
此外,定期用逻辑备份加binlog增量,保证误删数据可回滚到五分钟内状态。备份文件加密后传至对象存储,隔离于生产网络。
五、监控与扩容经验
分布式MySQL上线后,监控重点从单机指标转为分片均衡度。我们采集各库QPS、磁盘使用、慢日志数,绘制热力图。一旦发现某分片水位超阈值,便启动数据再平衡,将部分哈希区间迁至新购节点。
扩容时采用先建新分片、双写观察、再切读的灰度方式,避免一次性大迁移引发抖动。整个过程业务无停机,仅内部监控看到流量渐变。经过三轮大促考验,该MySQL分布式存储方案稳定支撑了核心链路。