WebFlux + R2DBC 场景下的分库分表预研:从架构选型到落地风险
1. 为什么要预研分库分表在业务早期单库单表通常是最简单、最稳定、成本最低的方案。但当系统中出现一些高频写入的大表时单库单表会逐渐暴露问题。例如查询越来越慢。索引越来越大。写入和查询互相影响。大表 DDL 风险变高。历史数据归档困难。慢 SQL 优化空间越来越小。单库资源扩容成本越来越高。典型的大表包括消息流水表。设备事件表。操作日志表。交易明细表。监控采集表。时序统计表。审计记录表。这类表的共同特点是数据持续增长。写入频率高。查询经常带时间范围。数据天然有业务维度例如设备、用户、租户、组织。最近数据访问频繁历史数据访问较少。所以分库分表预研的核心问题不是“怎么拆表”而是系统的核心查询和写入模型是否适合被稳定地路由到少量分片。如果不能精准路由分库分表很可能只是把一个单表慢查询变成多个分片上的广播查询。2. 什么时候需要考虑分库分表分库分表不是越早越好。过早引入会增加开发、测试和运维复杂度。通常可以从以下维度判断是否需要进入预研阶段。2.1 数据量维度可以重点观察单表行数是否已经达到千万级甚至更高。表数据文件和索引文件是否持续膨胀。历史数据是否明显多于热数据。大表备份、恢复、DDL 是否已经变慢。数据量只是一个信号不是唯一标准。有的表几千万行但查询简单、索引合理仍然可以稳定运行有的表几百万行但查询复杂、索引设计差也可能很慢。2.2 查询维度重点关注是否出现大量慢 SQL。是否有大 offset 分页。是否频繁按时间范围查询。是否经常只查最近一段时间数据。是否存在不带高选择性条件的模糊查询。是否存在多字段动态组合查询。如果慢查询主要来自不合理 SQL、缺索引、隐式类型转换、函数导致索引失效那么应该先做 SQL 和索引优化不要直接上分库分表。2.3 写入维度重点关注写入吞吐是否接近单库瓶颈。高频写入是否导致索引维护成本过高。写入是否影响查询。是否有批量写入、异步写入、削峰空间。如果只是瞬时峰值可以先考虑批量写入。队列削峰。异步落库。读写分离。表分区或归档。2.4 运维维度分库分表会引入新的运维问题分片元数据管理。分片扩容。数据迁移。Proxy 高可用。分布式事务。跨分片聚合。监控和告警。如果团队没有足够的运维和排障能力建议先通过更保守的方式优化。3. 分库分表前可以先做哪些优化在正式拆表前可以先做一轮低成本优化补齐必要索引。清理无效索引。消除隐式类型转换。避免函数作用在索引列上。大分页改成游标分页。冷热数据归档。读写分离。报表查询走汇总表。搜索类查询走搜索引擎。高频统计走预聚合表。如果这些手段已经不能满足需求再考虑分库分表。4. 目标表如何选择不是所有表都适合分库分表。优先处理写入量最大的流水表。数据增长最快的历史表。查询压力最集中的核心表。历史数据占比高且可按时间归档的表。不建议优先处理配置表。字典表。状态变化不频繁的主数据表。数据量不大的关联表。查询不在核心链路上的辅助表。一个常见策略是核心大表分库分表 配置字典表保持单表 报表统计汇总表或物化视图 搜索筛选搜索引擎或索引表 历史数据归档或冷热分层5. 分片维度设计分片维度通常要从查询模型倒推。5.1 时间维度高频流水表大多天然适合按时间拆分。常见粒度按天yyyyMMdd 按周yyyy_WW 按月yyyyMM 按季度yyyy_Q按时间拆分的好处历史归档方便。查询最近数据时扫描范围小。按时间删除旧数据成本低。单表大小可控。缺点时间范围过大时会跨多张表。表数量会随着时间增长。需要处理跨表聚合和分页。一般建议从按月或按周开始极高频写入场景再考虑按天。5.2 业务维度除了时间还需要选择一个能稳定路由的业务维度。常见选择device_id user_id tenant_id org_id account_id order_id选择分片键时要看核心查询是否一定带这个字段。字段分布是否均匀。是否容易产生热点。关联写入是否能落到同一分片。后续扩容和迁移成本是否可控。不适合作为分片键的字段状态。类型。布尔值。低基数字段。经常为空的字段。5.3 时间 哈希组合对于高频流水表一个比较常见的设计是t_message_yyyyMM_hashN例如t_message_202601_00 t_message_202601_01 t_message_202602_00 t_message_202602_01路由逻辑先根据时间确定月份表组 再根据业务 ID hash 到具体分片这样可以同时控制单表数据量。单分片写入压力。历史归档范围。6. 分片算法6.1 哈希取模最常见的算法hash(sharding_key) % N优点实现简单。数据分布相对均匀。同一个分片键能稳定落到同一分片。缺点N 变化后大量数据需要迁移。扩容成本较高。适合初期分片数量相对稳定的场景。6.2 一致性哈希一致性哈希适合需要后续扩容的场景。优点扩容时迁移的数据量相对较少。可通过虚拟节点改善分布。缺点实现和运维复杂度更高。对中间件支持情况有要求。6.3 范围分片范围分片常用于时间字段。例如2026-01 - t_message_202601 2026-02 - t_message_202602优点便于历史数据清理。查询时间范围时容易裁剪分片。缺点当前时间分片可能成为写入热点。时间范围过大时会跨很多表。7. 技术方案选型7.1 ShardingSphere-JDBCShardingSphere-JDBC 是应用内嵌式方案。优点功能完整。生态成熟。支持分库分表、读写分离、分布式事务、数据加密等能力。文档和案例较多。不足主要面向 JDBC 生态。对纯 R2DBC 项目不如 Proxy 自然。应用侧引入成本更高。7.2 ShardingSphere-Proxy对于 WebFlux R2DBC 场景更推荐优先评估 ShardingSphere-Proxy。架构形态应用服务 - R2DBC Driver - ShardingSphere-Proxy - 后端数据库应用仍然以数据库方式连接只是连接目标从真实数据库变成了 Proxy。优点对应用侵入较小。对 R2DBC 项目更友好。分片规则集中管理。可以统一处理读写分离和分片路由。后续数据库迁移时应用侧改动相对可控。风险Proxy 本身成为关键链路。需要额外部署和监控。跨分片聚合仍然有成本。复杂 SQL 兼容性需要压测验证。7.3 自研路由自研路由通常是应用内根据业务参数计算物理库表。示例业务参数 - 路由规则 - DataSource / ConnectionFactory - 物理表名优点性能可控。逻辑贴合业务。可以针对特殊查询做深度优化。缺点研发和维护成本高。跨分片查询要自己处理。扩容和迁移要自己处理。事务边界更复杂。除非团队有明确的中间件维护能力否则不建议一开始就完全自研。8. 推荐架构更稳妥的落地路线是ShardingSphere-Proxy 汇总表 索引表 冷热归档职责划分Proxy 负责普通分片路由。汇总表负责报表统计。索引表负责非分片键查询。搜索引擎负责复杂检索。冷热归档负责历史数据生命周期。这样能避免把所有复杂度都压在分库分表中间件上。9. 查询改造重点分库分表后查询模型需要同步改造。9.1 核心查询必须带分片键理想查询WHEREdevice_id?ANDcreate_time?ANDcreate_time?这类查询可以根据设备和时间精准路由。不理想查询WHEREstatus?ORDERBYcreate_timeDESCLIMIT20这种查询没有分片键容易广播到所有分片。9.2 非分片键查询需要辅助结构如果业务确实需要按非分片键查例如状态、手机号、外部编号可以考虑索引表。搜索引擎。汇总表。缓存映射。例如external_no - sharding_key先通过索引表找到分片键再查询真实分片表。9.3 跨分片分页要谨慎传统分页LIMIToffset,size跨分片后可能变成每个分片都取offset size条再聚合排序。offset 越大性能越差。建议改成游标分页WHEREcreate_timelast_create_timeORDERBYcreate_timeDESCLIMIT20或者WHEREidlast_idORDERBYidLIMIT209.4 排序字段要稳定跨分片排序时单独按时间排序可能不稳定。建议使用组合排序ORDERBYcreate_timeDESC,idDESC这样可以避免同一时间点多条数据时分页重复或丢失。10. 写入改造重点10.1 全局唯一 ID分库分表后不建议依赖数据库自增主键。常见方案UUID。雪花算法。号段模式。建议优先考虑雪花算法或号段模式。UUID 虽然简单但作为主键时通常不够友好无序。索引膨胀。写入局部性差。10.2 批量写入批量写入需要注意单批大小。参数数量限制。连接池占用。事务范围。失败重试。如果一批数据会落到多个分片需要评估是否拆批按目标分片分组 - 分批写入 - 单分片事务10.3 幂等写入分库分表后重复写入和补偿写入更常见。建议设计全局唯一业务 ID。唯一索引。幂等插入。可重试的补偿逻辑。11. 事务问题分库分表后单库事务不一定能覆盖完整业务操作。要优先通过设计规避分布式事务同一业务聚合内的数据尽量落到同一分片。跨分片操作尽量拆成异步流程。能最终一致的场景不强求强一致。核心强一致场景再评估分布式事务。如果必须跨分片事务可以评估XA。BASE。Seata。事务消息。本地消息表。需要注意的是分布式事务会带来性能和复杂度成本不应作为默认方案。12. 数据迁移方案在线系统改造分库分表时数据迁移是难点。常见步骤建新分片表。增加双写或变更捕获。全量迁移历史数据。增量同步新数据。校验数据一致性。灰度切读。灰度切写。保留回滚窗口。下线旧表或归档。校验维度包括行数。主键集合。核心字段 hash。时间范围抽样。业务接口比对。不要只看迁移任务成功还要验证业务查询结果是否一致。13. MySQL 切换 PostgreSQL 的注意点如果系统后续可能从 MySQL 切换到 PostgreSQL分库分表设计时要尽量减少数据库方言绑定。13.1 驱动差异R2DBC 驱动不同MySQL: r2dbc-mysql PostgreSQL: r2dbc-postgresql如果使用 ShardingSphere-Proxy应用连接 Proxy后端数据库类型变化主要体现在 Proxy 配置和 SQL 方言兼容性上。13.2 自增主键MySQLAUTO_INCREMENTPostgreSQLSERIALBIGSERIAL GENERATEDASIDENTITY分库分表场景建议使用分布式 ID避免绑定数据库自增能力。13.3 时间函数MySQLNOW()DATE_FORMAT()UNIX_TIMESTAMP()PostgreSQLnow()to_char()extract()date_trunc()建议将时间计算统一封装避免业务 SQL 到处散落数据库函数。13.4 Upsert 语法MySQLINSERT...ONDUPLICATEKEYUPDATEPostgreSQLINSERT...ONCONFLICT(key)DOUPDATE如果业务大量依赖幂等写建议抽象统一写入接口再按数据库方言生成 SQL。13.5 JSON 差异MySQL 和 PostgreSQL 的 JSON 能力差异较大。PostgreSQL 的jsonb、表达式索引、部分索引能力更强但 SQL 写法不同。建议高频查询字段不要长期放在 JSON 中。核心过滤字段拆成普通列。JSON 用于保存扩展属性。13.6 索引能力PostgreSQL 可以重点评估btree index。partial index。expression index。BRIN index。对于按时间递增的大表BRIN 索引在某些范围查询中非常有价值。13.7 事务隔离级别MySQL InnoDB 默认通常是Repeatable ReadPostgreSQL 默认是Read Committed如果业务依赖特定一致性语义迁移前必须验证。14. 压测和验证指标分库分表不是配置完成就结束必须压测。建议至少验证单写吞吐。批量写入吞吐。单分片查询耗时。跨分片查询耗时。分页查询耗时。Proxy 路由耗时。Proxy CPU 和内存。后端连接池使用率。慢 SQL 数量。P95 / P99 响应时间。建议对比三组数据单库单表基线 分片后单分片查询 分片后跨分片查询只有这样才能判断分库分表到底有没有实际收益。15. 监控和运维上线后需要监控Proxy 存活状态。Proxy 路由耗时。Proxy 后端连接数。数据库连接池。分片表数据量。分片热点。慢 SQL。跨分片查询数量。迁移任务状态。数据一致性校验结果。分库分表后的系统问题往往不再只发生在数据库也可能发生在 Proxy、连接池、路由规则、应用 SQL 和数据迁移链路上。16. 落地路线建议比较稳妥的路线梳理核心大表和慢查询。先做索引、SQL、归档和读写分离优化。明确核心查询是否能带分片键。设计时间分片和业务哈希分片。搭建 ShardingSphere-Proxy 验证环境。编写核心 SQL 兼容性测试。做压测和容量评估。设计迁移和回滚方案。小流量灰度。逐步切换读写流量。不要一开始就全量切换。17. 最终 checklist分库分表落地前建议确认是否已经证明单库单表成为瓶颈。是否已经做过低成本优化。分片键是否稳定。核心查询是否都能带分片键。是否有跨分片查询兜底方案。是否有全局 ID 方案。是否避免了大 offset 分页。是否有数据迁移方案。是否有数据校验方案。是否有回滚方案。是否压测过核心链路。是否监控 Proxy 和后端数据库。是否评估过 MySQL / PostgreSQL 方言差异。18. 总结分库分表不是简单地把一张大表拆成多张小表而是一整套数据架构改造。它会影响查询模型。写入模型。主键生成。事务边界。分页排序。数据迁移。运维监控。数据库迁移。对于 WebFlux R2DBC 场景ShardingSphere-Proxy 是一个值得优先评估的方案因为它能把分片能力下沉到代理层降低应用侧侵入。但无论选择 Proxy 还是自研路由最关键的问题仍然是能否通过分片键精准路由。如果核心查询无法携带分片键分库分表可能不会带来预期收益反而会引入更多跨分片聚合、分页、事务和运维问题。一句话总结分库分表的价值不在于“拆”而在于让核心读写能够稳定落到可控范围内。