分库分表——从理论到最佳实践 📅 2026/7/6 21:12:54 目录1、为什么要分库分表2、切分方案有哪些2.1 分库2.1.1 垂直分库2.1.2 水平分库2.2 分表2.2.1 垂直分表2.2.2 水平分表2.3 分库分表3、数据水平分片方法3.1 Hash分片3.2 一致性Hash分片3.3 Range分片4、分库分表的挑战4.1 分布式id4.2 分布式事务4.3 动态扩容4.4 跨库关联问题4.5 服务、数据平滑迁移4.6 数据库基本的增删改功能4.7 子表分库分表5、项目实战5.1 容量规划5.2 迁移整体流程与阶段拆解5.2.1迁移准备5.2.2业务改造上线5.2.3存量数据同步5.2.4数据校验与读切换测试5.2.5开启双写5.2.6双写期间数据校验5.2.7读流量切换5.2.8停止同步与双写5.2.9收尾5.3基于 MyBatis 拦截器的双写插件5.4数据一致性保障1、为什么要分库分表随着业务的不断发展系统数据量不断增加即便正确设置了索引仍然无法掩盖因为数据量过大从而导致的数据库性能下降的事实。对于单台 MySQL 服务器来说硬件资源如存储容量、连接数与处理能力是有限的。当数据量过大或读写操作并发过高时超出了单服务器的性能瓶颈可能会对系统的稳定性产生严重隐患甚或导致系统整体不可用。在仅查询请求量增加的场景中通过主从架构实现读写分离能够满足业务读多写少的需求从而保证系统的可靠性。然而当数据库中单表数据量增大时业务操作的耗时会增加。有时由于业务特点的限制归档或删除操作无法从根本上解决问题。此外进行大表结构变更时需要拷贝数据若表数据量过大会导致无剩余空间进行表结构修改。当单表数据量过大或写操作负荷过高以至于达到存储或性能的上限时必须通过数据切分等方式进行治理。核心思想是通过分割数据确保单表和单机的负荷在机器性能许可的范围内。2、切分方案有哪些切分方案主要解决的问题只分库不分表分库数据库读/写QPS过高数据库连接数不足只分表不分库分表单表数据量过大存储性能遇到瓶颈既分库又分表分库分表以上两种问题2.1 分库分库指的是将数据从单个数据库拆分成多个数据库的过程。拆分后的数据库往往部署在多套集群中这也就意味着降低了单个集群的负载压力提升整体的读写性能。分库通常有两种方式垂直分库和水平分库。2.1.1 垂直分库垂直分库是指根据业务领域划分将不同领域的库表拆分到多个数据库中从而分散数据存储和读取压力降低数据层面的耦合度。2.1.2 水平分库水平分库通常与分表操作一起进行。当单表数据量过大导致存储容量和读写性能出现瓶颈时可以根据一定规则将单表拆分成多个表并将这些表存储在不同的数据库中从而解决存储容量和性能问题。2.2 分表分表指的是将单张数据表拆分成多张表的过程以减少单表容量提升读写效率。如提升数据写入时索引的构建效率、减少锁范围等对于读流量也能较好减少耗时。分表通常有两种方式垂直分表和水平分表。2.2.1 垂直分表垂直分表是指根据实际应用场景将数据表中的部分列拆分出来改变原有表结构。通常可以结合实际查询需求、冷热数据分离和大字段独立存储等方式进行分表。如将db库中的订单表拆分为订单基本信息表和订单详细信息表。2.2.2 水平分表水平分表是指当单表数据量过大且不断增长导致存储备份缓慢和读取效率低下时将单表按一定规则拆分成多张表。这样可以降低单表容量提升数据查询效率。如将db库中的订单表拆分为32个分表order_[0-31]分表还位于同一个库中。2.3 分库分表在处理海量数据时单一的数据库和数据表会面临存储容量和性能瓶颈。通过同时进行分库和分表我们可以将单表的数据水平拆分为多个表并将这些表分散存储在多个数据库中。这样不仅能有效降低单一数据库和数据表的负载压力还能提升数据的读写性能和系统的整体稳定性。3、数据水平分片方法确定了分库分表后就需要考虑如何将数据分到哪个库或哪个表中。分片是一种水平切分数据表的方式可以将数据按特定策略切分成多个数据文件这些文件既可以存储在单节点上也可以存储在分布式的多个节点上从而实现更强大的存储和计算能力。下面介绍几种常见的数据分片方法。3.1 Hash分片Hash分片就是按照数据记录中指定关键字的Hash值将数据记录映射到不同的分片中。如图所示通过一个Hash函数计算用户ID的Hash值而后取模分配到对应的分片。模为4的原因是系统一共有四个节点每个节点作为一个分片。优点这种方法实现简洁可以保证数据非常均匀地分布到多个分片上。缺点直接用节点数作为模如果系统节点数量变动模也随之改变数据就要重新Hash计算从而带来大规模的数据迁移。显然这种方式的扩展性不好。必须找一个方法提升系统的扩展性就是接下来要介绍的一致性Hash。3.2 一致性Hash分片一致性哈希是一种特殊的哈希算法其核心思想是在一个虚拟的环上分布数据和节点不同于传统的哈希算法这种方法在增加或删除节点时能够最小化数据重分布。如图所示首先会引入虚拟节点每个虚拟节点就是一个分片。这个案例中将分片数量设定为16。因为分片数量决定了集群的最大规模所以实践中它通常会远大于初始集群节点数。16个分片构成了整个Hash空间数据记录都要通过Hash函数映射到这个空间我们换一种方式画图可以看得更清楚些。如下图所示Hash空间构成了一个Hash环数据按照顺时针找到最近的节点。当我们新增一台服务器即节点E时受影响的数据仅仅是新服务器到其环空间中前一台服务器即沿着逆时针方向的第一台服务器之间数据。结合我们的示例只有小红分享的消息从节点B被移动到节点E其他节点的数据保持不变。此后节点B只存储Hash值6和7的消息节点E存储Hash值4和5的消息。一致性哈希优点数据均衡能够将数据均衡地分布到多个表中避免某些表过度负载。扩展性强当需要增加新的表时一致性哈希允许新的表平滑地加入系统不会导致大量数据重新分布。部分表失效或移除的情况下一致性哈希能将其数据分布到剩余的表中减小影响范围。3.3 Range分片范围分片是一种根据数据值的范围将数据拆分成多个部分片的分片方法。每个片包含一个特定范围内的连续数据。通常这种方法使用数据的某个关键字段来确定数据的范围例如日期、用户ID、订单号等。假设有一个订单表 orders我们可以按订单日期将其拆分成不同的分片orders_2020: 存储2020年的订单orders_2021: 存储2021年的订单orders_2022: 存储2022年的订单这种方式使用偏少先介绍到这。工作中MySQL分片最常用的还是Hash分片。4、分库分表的挑战4.1 分布式id在分库分表后我们不能再使用mysql的自增主键。因为在插入记录的时候不同的库生成的记录的自增id会出现冲突。因此需要有一个全局的id生成器。4.2 分布式事务原本采用单库通过本地事务就可以保障一致性。分库后因为涉及到了同时更新多个数据库如何保证要么同时成功要么同时失败。分布式事务是分库分表绕不过去的一个坎。柔性事务是目前比较主流的方案柔性事务包括最大努力通知型、可靠消息最终一致性方案以及TCC两阶段提交等。4.3 动态扩容动态扩容指的是增加分库分表的数量。例如原来的user表拆分到2个库的4张表上。现在我们希望将分库的数量变为4个分表的数量变为8个。这种情况下一般要伴随着数据迁移。4.4 跨库关联问题单库下的表连接操作在分库后需要进行改造。常见的方式有冗余存储数据、应用层拼接等4.5 服务、数据平滑迁移单库单表迁移到分库分表后为了保证平稳过渡上层无影响需要采用相应的策略进行服务、数据的迁移4.6 数据库基本的增删改功能对于开发人员而言虽然分库分表但还是希望能和单库单表那样的去简单地操作数据库包括查询与DML操作。通常是通过数据库中间件实现中间件的原理大体分为数据源代理、数据库代理。具体可搜索cobar、zebra、sharding-jdbc等关键字了解其原理4.7 子表分库分表在进行主表分库分表时子表也需要进行相应的处理。需要按照相同的分片规则进行分库分表确保数据的关联性和查询的效率。工作量会翻倍。5、项目实战实践中的大表治理阈值是单表数据量不超过两千万条同时还需要综合考虑索引量和单行容量——如果单行数据量大或索引数量多阈值应该更低。超过这个阈值后查询性能下降、DDL 耗时剧增、备份恢复变慢等问题会集中爆发迁移的难度和风险也会成倍增加。但分片设计只是第一步真正棘手的是迁移过程。线上服务 7×24 小时运行不可能停机切库亿级数据搬迁过程中新的写入还在不断发生切流过程中一旦发现数据不一致需要能够回滚。整个过程就像是边开飞机边换引擎——流量在线上跑着任何操作失误都可能导致线上故障。更棘手的是数据迁移和业务强绑定、侵入业务逻辑很难做到一套方案通用复用。这些问题如果处理不当轻则数据丢失重则系统整体不可用。本节结合多个大表治理项目的实战经验给出一套通用的不停机迁移方案。核心目标是平滑迁移 数据一致性保障 操作可回滚。5.1 容量规划容量规划是分库分表设计中容易被忽视但至关重要的环节。分片数量一旦确定并写入数据后续扩容如从 16 库扩到 32 库需要重新 rehash 和数据迁移成本极高。因此在拆分设计阶段必须充分评估当前和未来的容量需求留足扩展空间。数据量评估。首先需要摸清待拆分表的现状和增长趋势。关键指标包括当前数据行数、单行平均字节数、当前总数据量行数 × 单行字节数、日均增长行数、月均增长数据量。基于这些指标推算未来 2~3 年的数据量。例如当前 5000 万行、日均增长 20 万行三年后将达到约 2.7 亿行。数据量评估不仅看行数还要考虑单行大小——如果表中有 TEXT/BLOB 等大字段实际存储占用可能远大于行数 × 固定字节数的估算。分片数量计算。分片数量需要同时满足两个约束单分片数据量不超过治理阈值如两千万行以及总分片数能覆盖未来 2~3 年的增长。计算公式很简单分片数 预期总数据量 / 单分片阈值。以上面的例子为例2.7 亿行 / 2000 万行 13.5向上取 2 的幂次得到 16。实践中建议分片数取 2 的幂次如 8、16、32、64这样取模路由的扩容可以按倍数翻倍减少 rehash 的工作量。同时要预留 30%~50% 的缓冲空间——不要把分片设计得刚好够用留有余地才能从容应对业务增长超预期的情况。硬件资源规划。分库分表后数据库实例数量成倍增加需要提前规划硬件资源。核心考虑点包括每个分片数据库实例的规格CPU、内存、磁盘通常单分片配置不低于原单库配置的 1/NN 为分片数但要保证单分片能独立承受其分片的读写压力磁盘空间需要预留数据量 2~3 倍的余量考虑索引、临时表、binlog 等开销如果使用云数据库还需要考虑实例规格的可选档位和升级路径。连接池规划。双写阶段是连接数压力最大的时期——应用同时连接老库和所有新库分片数据库连接数翻倍。假设原连接池大小为 20分 16 个库双写期间连接数可能从 20 增长到 20 16×20 340。需要提前评估数据库最大连接数配置MySQL 默认 151通常需要调大到 500~1000同时合理设置应用侧连接池上限避免连接耗尽。建议在双写阶段适当调小单分片连接池大小如从 20 降到 10用较小的连接池配合排队机制度过双写期。性能预算。迁移过程中引入了额外开销需要为每个环节预留性能预算。双写阶段每次写入操作从 1 次 DB 调用变为 2 次写 RT 预估增加 50%~100%取决于新库网络延迟灰度切读阶段部分读流量走新库需要监控新库的查询 RT 是否达标数据校验阶段校验任务会消耗数据库 CPU 和 IO建议在低峰期执行或限制并发度。整体性能预算建议双写期间写 RT 不超过原来的 2 倍切读完成后新库读 RT 不超过老库的 1.5 倍。如果超出预算需要排查新库索引是否完备、分片键是否均匀、是否存在跨分片查询。迁移带宽评估。全量数据迁移阶段会产生大量网络流量需要评估迁移带宽是否充足。计算方式总数据量 / 期望迁移时间 所需带宽。例如500GB 数据期望 4 小时迁完需要约 35MB/s 的带宽。如果带宽不足可以考虑分批迁移、压缩传输等方式。增量同步阶段虽然流量较小但需要持续占用带宽需确保不影响正常业务流量。5.2 迁移整体流程与阶段拆解整套迁移方案分为九个阶段覆盖从前期梳理到最终收尾的完整生命周期。每个阶段的读写策略、具体事项和回滚方案如下表所示阶段读写策略具体事项回滚方案1. 迁移准备读老、写老整理需要迁移的表清单梳理访问迁移表的代码梳理迁移表上下游订阅/校验任务等梳理数仓任务无风险纯梳理阶段2. 业务改造上线读老、写老数据源代码改造上线灰度读、双写控制改造上线代码回滚3. 存量数据同步读老、写老启动迁移任务全量数据迁移增量数据拉取回放增量同步追上源库迁移任务可随时暂停/恢复4. 数据校验与读切换测试读灰度→读老、写老数据校验首轮新老库数据比对按千分比例读切换测试逐步扩大读比例至全部读新测试成功后回滚读策略至老库读流量回老库5. 开启双写读老、写老写新开启双写业务系统控制暂停数据迁移工具增量同步关闭双写开关恢复增量同步6. 双写期间数据校验读老、写老写新数据校验第二轮新老库数据比对不一致数据通过补偿脚本修复修复后重新校验无需回滚7. 读流量切换读灰度→读新、写老写新数据校验成功后手动触发读切换按比例灰度读切新读流量完全切换读写平稳灰度比例调回 0读流量回老库8. 停止同步与双写读新、写新停止迁移任务数据同步关闭停止双写读写均在新库重新开启双写同步数据补偿9. 收尾读新、写新通知下游依赖老库相关资源进行切换切换上下游订阅任务切换数仓任务无风险纯切换阶段数据迁移工具负责在新老库之间同步数据支持全量同步和增量同步两种模式。全量同步将老库的存量数据批量导入新库增量同步则通过解析数据库日志如 MySQL binlog实时追平老库的变更。开源方案有 DataX、Canal 等各云厂商也提供类似的数据传输服务。选型时需关注是否支持分库分表路由能根据分片键将数据写到对应的分表、增量同步延迟、断点续传能力。数据迁移工具同时提供数据校验服务可以在平台上进行实时数据校验。数据一致性校验工具负责比对新老库的数据差异。工具定期扫描两个库中相同范围的数据逐行比较字段值输出不一致的记录列表。开源方案有 pt-table-checksum 等也可以基于业务自研。选型时需关注比对效率百万级数据应在 10 分钟内完成、是否支持分片表比对、比对结果能否直接驱动补偿。5.2.1迁移准备迁移的第一步不是动手搬数据而是全面梳理。这个阶段需要完成四项工作整理需要迁移的表清单明确哪些表需要拆分、哪些不需要梳理所有访问迁移表的代码包括 DAO 层、Service 层以及跨服务的调用链路梳理迁移表的上下游依赖包括下游订阅如 binlog 订阅、消息队列消费和数据校验任务等梳理数仓任务确认离线数仓是否依赖该表的同步链路。这些梳理工作决定了后续改造的范围和风险面必须做到不遗漏。5.2.2业务改造上线梳理完成后进行业务侧的代码改造并上线。改造内容包括两个层面数据源代码改造——为分库分表准备新的数据源配置和分片 Mapper确保应用能够连接新的分片库并正确路由灰度读和双写控制改造——在应用中嵌入灰度读路由逻辑和双写拦截器通过动态配置开关控制此时开关全部关闭线上行为与改造前完全一致。改造上线后系统具备双写和灰度读的能力但尚未实际启用。后续会讲解双写控制方案5.2.3存量数据同步业务改造上线完成后启动数据迁移任务。迁移工具首先进行全量数据迁移将老库的存量数据批量导入新的分库分表。全量同步完成后自动切换到增量同步模式通过解析老库的 binlog 实时拉取增量变更并回放到新库。整个同步过程是旁路进行的老库的正常读写不受影响。当增量同步延迟降到秒级以内时可以认为增量同步已追上源库存量数据就绪。这个阶段需要关注同步延迟和断点续传能力。如果同步延迟持续较大需要排查迁移工具的吞吐瓶颈如并发度、批量大小配置或网络带宽限制。迁移工具应支持断点续传避免因任务异常中断而从头开始。5.2.4数据校验与读切换测试增量同步追上源库后进入第一轮数据校验。使用数据一致性校验工具对新老库的数据进行全量比对确认存量数据和增量数据在新库中的一致性。校验通过后开始读切换测试——将少量读流量切到新库验证新库的查询结果和性能是否正常。读切换测试采用逐步放量的策略先按千分比例切换观察新库查询耗时、错误率等指标确认无异常后逐步扩大读比例直至全部读流量走新库。整个测试过程中写流量始终走老库数据迁移工具的增量同步保持运行新库数据持续追平。读切换测试成功后将读策略回滚至老库为下一阶段开启双写做准备。这一步看似多余——既然测试成功了为什么不直接保持读新库原因是这一步核心目的是验证新库读能力防止最后切读时暴露新库读能力有问题导致前功尽弃。同时读切换测试期间老库仍然是写入主库新库的数据通过迁移工具同步存在秒级延迟。如果此时直接开启双写应用层同步写新老库与迁移工具的增量同步会产生重复写入冲突。因此需要先回滚读策略、暂停迁移工具同步再开启双写确保数据写入链路清晰无冲突。5.2.5开启双写读切换测试成功并回滚读策略后开启双写。操作步骤是先暂停数据迁移工具的增量同步任务再通过动态配置中心开启双写开关。双写开启后应用层的每次写操作INSERT/UPDATE/DELETE会同时写入老库和新库读流量暂时仍走老库。选择在 DAO 层而非 Service 层做双写有几个明显优势对上层业务代码零侵入Service 层无需任何改动双写逻辑集中在一个插件中便于维护和排查可以精确控制哪些表需要双写通过白名单机制实现细粒度管理如果双写出现问题只需关闭开关即可回退到只写老库的状态影响范围可控。双写插件采用三态切换设计通过动态配置中心控制无需重启服务。三种模式对应迁移的不同阶段模式一双写-老库为主先写老库成功后再写新库新库失败仅告警。对应阶段 5~7老库仍是数据权威来源。模式二双写-新库为主先写新库成功后再写老库老库失败仅告警。作为切写时的可选中间态新库成为数据权威来源。模式三关闭双写直接走原有流程。对应阶段 8迁移完成后或紧急回滚时使用。三态切换的设计本质是在可用性Availability和一致性Consistency之间做平衡。双写要做到强一致需要同步等待两个库都写入成功才返回但这会牺牲在线业务的 RT——大多数业务无法接受写耗时翻倍。因此实践中普遍采用尽力而为的策略主库写入成功即返回辅库异步写入失败只告警不阻塞。这种设计牺牲了强一致性但保证了可用性不一致问题交给后续的数据校验和补偿机制来兜底。在迁移场景下可用性优先于一致性是更务实的选择——数据不一致可以修复但服务不可用的代价要大得多。5.2.6双写期间数据校验双写运行一段时间后进行第二轮数据校验。这一轮校验的目标是发现双写过程中可能产生的不一致数据。校验方式与第一轮相同使用数据一致性校验工具定时比对新老库的数据差异同时在数据迁移工具平台上进行实时数据校验。校验过程中如果发现不一致需要分析原因。常见原因包括双写过程中新库写入失败但未及时告警、迁移工具暂停期间的增量数据未完全追平、分片键为空导致路由失败等。对于不一致的数据通过泛化调用或批量修复脚本将老库中存在但新库中缺失的数据重新写入新库。校验和补偿应该在开启双写后、读流量切换前完成确保新库数据完全可信。除了实时校验还需要建立定时的全量数据校验机制。校验频率建议在迁移初期设置较高如每小时一次稳定后逐步降低。双写期间的新老库数据比对应贯穿整个双写周期不是一次性工作。5.2.7读流量切换第二轮数据校验成功后手动触发读流量切换。这次是真正的读切换不是测试——读流量将永久切到新库。切换采用灰度策略基于分片键如 userId取模实现动态配置中心维护一个灰度比例值应用层在读取时判断是否命中灰度/** * 灰度路由判断千分比 */publicbooleanshouldReadFromShard(LonguserId){intgrayPermilleconfigCenter.getGrayReadPermille();// 0~1000if(grayPermille0)returnfalse;if(grayPermille1000)returntrue;returnMath.abs(userId.hashCode())%1000grayPermille;灰度过程遵循千分比 → 百分比 → 全量的阶梯逐步推进每一步观察新库的查询耗时、错误率、数据一致性指标。灰度切读期间双写保持开启如果新库读取发现问题将灰度比例调回 0 即可立即回退因为老库的数据始终在双写中被更新。当读流量完全切换到新库并稳定运行后进入读写平稳状态——读走新库写双写新老库数据迁移工具已暂停。此时可以停止迁移任务进入下一阶段。5.2.8停止同步与双写读写平稳后停止数据迁移任务关闭数据同步。然后停止双写此时读写均在新库完成。这一步标志着迁移的核心工作完成。停止双写有两种策略直接关闭双写模式三读写全部走新库或先切换为新库为主双写模式二观察一段时间后再关闭双写。后者更为保守适合对数据一致性要求极高的场景。这一阶段的回滚需要特别注意停止双写后老库不再更新如果需要回滚到老库属于有损回滚——需要通过数据补偿工具将新库的增量数据回写到老库。因此建议在停止双写后保留老库一段时间如一周作为最终的数据兜底确认无误后再彻底下线。5.2.9收尾迁移完成后通知下游依赖老库相关资源进行切换。这包括将下游的 binlog 订阅、消息队列消费等切到新库将数仓任务的同步链路从老库切换到新库。这些收尾工作确保所有依赖链路都已指向新库老库可以安全下线。5.3基于 MyBatis 拦截器的双写插件这部分留给读者自己研究或者联系博主进行咨询。5.4数据一致性保障双写机制虽然保证了写入的双份但并不能完全保证数据一致性。以下几个场景可能导致不一致需要额外的保障机制。双写失败导致的不一致。当主库写入成功、辅库写入失败时数据就会出现不一致。插件的处理策略是辅库失败只告警不阻塞保证主库写入不受影响但这意味着需要人工介入修复。实践建议为双写失败配置告警如日志关键词监控、告警群通知发现失败后及时排查。如果失败率持续偏高应考虑关闭双写回退到单写老库状态。迁移工具暂停期间的增量数据。开启双写前需要暂停数据迁移工具的增量同步任务暂停期间的秒级增量数据不会被自动同步到新库。这部分数据需要通过数据校验发现并补偿——通过泛化调用或批量修复脚本将老库中存在但新库中缺失的数据重新写入新库。校验和补偿应该在开启双写后、读流量切换前完成。分片键为空。分库分表要求数据必须携带分片键才能正确路由。如果历史代码中某些写入路径未设置分片键双写时新库写入会失败。双写插件在写入前进行前置校验分片键为空时抛出异常。建议在双写开启前全面排查所有写入路径确保分片键不会为空。数据校验贯穿始终。从存量同步追上源库后的第一轮校验到双写期间的第二轮校验再到灰度切读过程中的持续比对数据校验是贯穿迁移全过程的活动而非某个阶段的独立工作。实时告警 定时全量校验 人工抽检三层保障确保数据一致性。校验频率建议在迁移初期设置较高如每小时一次稳定后逐步降低。写作不易您的鼓励是我不断输出优质文章的动力欢迎评论、转发、收藏。抱拳。