
单表卡到一两千万行的时候很多团队的第一反应就是上分表分库。这种心情我特别理解当年我们订单表刚过三千万一个带条件的 count 查询能把主库 CPU 打到 80%DBA 半夜打电话把我叫醒张嘴就是“分了吧”。但真到了选分片策略的时候才发现最难的其实不是中间件怎么配而是面对 Hash、Range、一致性哈希、映射表这一堆方案你根本不知道自己的业务该抄哪一份作业。这篇文章就是一份可以直接拿来对照的“分表分库分片策略选型清单”我尽量把每个策略的适用场景、坑点、选型参数都讲透适合正在做库表拆分方案预研的架构师也适合被领导派去“调研一下分库分表方案”的后端开发。内容不会涉及具体中间件的安装教程重点放在策略本身的权衡逻辑上——因为不管你最后用 ShardingSphere、MyCat 还是自研路由底层选的还是同样的分片策略。先说个反常识的结论分库分表解决的是“数据量大导致的操作慢”但它同时会把“简单查询变复杂”“事务范围变窄”“扩容变重”。所以选分片策略之前先得有清单确认自己是不是真的走到了那一步。1. 先别急着分库分表动手前的评估清单1.1 什么信号说明“必须分了”不是所有慢查询都需要分库分表。我见过最典型的误判是团队把一条没走索引的 SQL 当成“数据量太大”结果分完库之后该慢还是慢反而多了一堆分布式事务的麻烦。真正需要拆分的信号通常同时出现两到三个单表行数超过千万级且业务增长曲线看不到头。这个阈值不是拍脑袋机械硬盘时代单表超过两千万行B 树高度到四层随机 IO 成本明显上升现在 SSD 稍微好一点但超过一亿行之后很多聚合操作依然会明显变慢。主库写入成为瓶颈。单库的写入能力受限于磁盘 IO 和 binlog 复制延迟读写分离只能缓解读压力解决不了写入单点。如果你的写入 TPS 长期超过单库承受能力或者主从延迟在工作日高峰期持续拉高这时候单纯加缓存已经没用了。数据归档做了还是慢。很多团队先试了按月归档、按用户归档发现业务查询范围太宽归档后的数据依然常被访问冷热分离扛不住热数据的体量那这一步就该考虑分片了。有一个原则很重要分库分表解决的是“量大”的问题不是“SQL 写得烂”的问题。如果慢查询日志里全是全表扫描和文件排序先花两周优化索引和 SQL可能省掉半年拆分的成本。1.2 分库分表能解决什么、解决不了什么列一张表会清楚很多。这也是选型清单的第一张表建议直接贴到方案评审的文档里。问题类型分库分表能不能解决说明单表数据量过大导致读写性能下降能数据分散到多个分片单分片数据量下降单库写入TPS瓶颈能多库并行写入前提是分片键分散合理单次查询过大大范围扫描部分能Range分片可优化Hash分片对大范围查询不友好跨分片 join / 聚合不能需要应用层组装或预聚合复杂度大增分布式事务不能跨分片事务成本极高最好从业务设计上规避深分页 order by limit不能需要全局排序分片越多性能越差热点数据集中访问部分能取决于分片键设计热点分片依然会被打爆非分片键查询不能需要映射表或搜索引擎辅助看明白这张表你就知道为什么很多大厂最后走的是“分库分表 异构数据同步”的路子分片只解决主链路问题非分片键查询丢给搜索引擎或宽表去扛。1.3 先做低成本优化再考虑分片还有一个容易忽略的问题成本。从单库到分库分表代码改动不只是一层 DAO 层的路由替换——SQL 改写、分页重写、事务边界重构、数据迁移脚本、灰度发布方案、回滚预案每一项都是工期。按照我见过的团队节奏一个核心业务立项到平稳上线最少得一个季度这还不算后续半年里陆续冒出来的边界 bug。所以在评估阶段建议按顺序排除以下方案慢 SQL 治理和索引优化缓存兜底热点读Redis 等归档冷数据到历史库读写分离扛读压力垂直拆分按业务域拆库先不搞水平分片这些方案成本低、见效快而且会为以后的分库分表打下基础——比如你先把订单和商品拆到不同库后续再做水平拆分时至少不用同时解决跨业务域的问题。2. 核心选型清单五大经典分片策略逐个拆解选分片策略本质上是回答三个问题数据怎么均匀散开查询怎么定位到具体分片扩容的时候数据怎么迁围绕这三个问题业界沉淀出了几种经典方案下面逐个讲。2.1 Hash取模分片最直接但扩容是硬伤Hash 分片是默认选项也是最容易理解的一种。选一个分片键比如用户 ID计算哈希值后对分片总数取模路由公式大致长这样// 分片总数 库数 * 每库表数 int dbIndex Math.abs(hash(userId)) % dbCount; int tableIndex Math.abs(hash(userId) / dbCount) % tableCount;这种两层路由的做法很常见第一层用userId % dbCount选库第二层用userId / dbCount再取模选表能保证同一个用户的数据落在一个库的一张表里不会出现“同一个用户订单散落在多个库”的尴尬情况。Hash 分片最大的优点是数据分布均匀。只要分片键的取值离散度够——比如自增 ID、随机串——每个分片的数据量和写入压力都差不多不会有明显短板。它的缺点是范围查询和批量操作基本报废。你想查某个用户最近三个月的订单前提是 SQL 里必须带上用户 ID如果只按时间查中间件就得对所有分片广播查询再把结果合并排序性能可能比不分片还差。更要命的是扩容问题。原来的分片总数是 8 个库现在要扩到 16 个库取模的模数变了几乎所有的数据映射关系都要变意味着你要全量迁移数据而且迁移过程中还得保证业务不停。这就是很多团队在 Hash 分片后死活不敢扩容的原因。实操心得如果你确定未来一两年内数据量不会翻倍Hash 取模是最省心的方案维护成本低、读写均衡适合大多数 ToC 业务的用户主链路。2.2 Range范围分片公认最懂业务的方案Range 分片的思路是告诉中间件“哪一段数据去哪张表”通常按时间或 ID 区间划分。比如订单表按月份分表order_202401、order_202402时间条件明确时可以精准路由到某一张表范围查询天然友好。Range 分片最典型的场景就是时序类数据交易流水、日志、操作记录、消息历史。这类数据有两个特点查询总是带时间范围而且旧数据访问频率越来越低。这时可以结合归档策略把一个季度前的分片自动降级到冷存储查询路径和存储成本都好看。但 Range 分片有个致命软肋热点集中在最新分片。所有新订单都写进当月的表月底最后一天那张表可能扛着整个系统的写入压力其他分片却很闲。层主库的“月底大促”场景里你要是按天分表峰值那天照样会被打爆。折中的做法是“时间 业务维度”的组合分片。比如先用卖家 ID 做 Hash 分库再用月份做 Range 分表这样写入压力被卖家维度打散查询又保留了按时间收敛的能力。// 组合分片示意库按卖家Hash表按月份 int dbIndex Math.abs(hash(sellerId)) % dbCount; String tableName order_ yyyyMM;这种方案的代价是路由规则稍微复杂但对大部分电商、支付类业务来说性价比远高于纯 Hash 或纯 Range。2.3 一致性Hash与虚拟节点扩容友好的选项一致性哈希值得认真理解因为它解决的是 Hash 分片最痛的问题——扩容/缩容时的数据迁移量。原理不复杂把整个哈希值域组织成一个环比如 0 到 2^32每个分片节点在环上占据一个或多个位置。要定位一个分片键就算它的哈希值然后沿环顺时针找第一个节点。这样加节点时只有环上该节点位置到前一个节点之间的数据需要迁移其他数据保持不动。但是朴素的一致性哈希有个问题节点少的时候分布严重不均运气不好可能出现“一个节点扛了 80% 数据”的情况。解法是引入虚拟节点——每个物理节点在环上放几十个甚至上百个虚拟位置让数据分布更均匀同时也能让不同性能的机器承担不同权重。维度Hash取模分片一致性Hash分片数据分布均匀性好依赖虚拟节点设置配置合理则均匀扩容迁移量近似全量只迁移环上局部数据路由复杂度极低中等范围查询支持差差运维心智负担低中高一致性哈希在 DDoS 防护、缓存集群、负载均衡领域用得多数据库分片里也有团队采用。它的核心价值是“随时扩缩容”如果你的业务流量季节性波动很明显需要定期加节点扛峰值、砍节点降成本那一致性哈希比固定取模合适得多。注意数据库分片用一致性哈希意味着中间件要自己维护虚拟节点和路由表。有些团队为了这个能力专门自研了分库分表组件复杂度不低。小团队还是建议用成熟中间件的内置一致性哈希实现别自己去造轮子。2.4 映射表与目录服务灵活但多一跳映射表模式比较另类不直接根据分片键计算路由而是维护一张“分片键 - 实际存储位置”的映射关系。比如用户有 1000 个订单这 1000 个订单分别存在哪些库哪些表在映射表里查一下就知道了。这种模式的优点是路由规则可以随时改数据迁移时映射表同步更新就行业务代码无感知。多租户场景特别喜欢——每个租户的数据量差异可能非常大按租户 ID 取模会导致大数据租户占用整个分片而映射表可以手动把大租户的库表分配调匀。有些 ToB 系统的“企业版专属存储”就是拿映射表做出来的。代价也很明显查询多了一跳。原来的路径是“SQL 直连路由”现在变成“查询映射表 - 拿到物理位置 - 再查实际分片”延迟多了几毫秒而且映射表本身成了新的单点和性能瓶颈。解决思路是把映射关系缓存到 Redis量大时甚至可以本地缓存 版本号更新。我的建议是映射表不要全局一张最好做成分片键维度的分片映射表比如按租户 ID 分表存映射关系避免单表过大。还有一种轻量用法——只把映射表用于“热点用户”的手动调度普通用户走 Hash 取模等于给分库分表加了一个人工干预的旋钮灵活性和性能都能兼顾。2.5 各策略适用场景对照与选型建议把主流策略放在一张表里对比选型时直接对着看。策略推荐场景优势劣势优先级建议Hash取模用户/订单等按ID等值查询为主的业务均匀、实现简单扩容难、范围查询差刚起步的团队首选一致性Hash流量波动大、需频繁扩容扩容迁移量小路由复杂、依赖虚拟节点运维能力强可考虑Range范围日志、流水、时序数据范围查询友好、可分层归档热点集中、月末峰值风险时序场景必选组合分片电商订单、交易流水均衡与查询兼顾路由规则复杂数据量大的核心业务推荐映射表多租户、数据倾斜严重灵活、可人为干预多一跳、映射表可能变瓶颈配套方案一般不单独用这里多说一句很多团队喜欢抄头部大厂的方案但大厂的“一致性哈希 映射表 冷热分离”整套体系是为千万级 QPS 和上万台机器设计的。如果你的数据总量在几十亿以内峰值写入不超过几千 TPS用“Hash 取模 时间归档”就完全够了简单方案能扛住 80% 的业务剩下 20% 的问题遇到了再升级也不迟。3. 实操阶段要做对的三件事分片键、路由设计与容量规划选了策略只是第一步真正动手设计时分片键的选择、路由的落地方案、容量的预估这三个环节才是决定成败的关键。这一章我按实操顺序一点点说。3.1 分片键选型三问选错了后面全是坑分片键是整个分库分表方案的心脏一旦定下来后面几乎没法改。选型的时候问自己三个问题第一这个键能不能唯一定位核心业务主体。订单表的核心业务主体是买家还是卖家如果是买家就选buyer_id如果买卖双方都是主角就得考虑“买家维度为主、卖家维度走辅助索引”的折衷。选错了后续最常见的尴尬是订单表按买家 ID 分片结果运营后台全是按卖家 ID 查订单每条 SQL 都得广播全库——那种痛苦用过的人都懂。第二这个键的取值够不够分散。性别、状态、渠道这种枚举字段千万不能当分片键因为值就那么几个数据根本散不开会导致几个分片极不均匀。用户 ID、订单号、设备 ID、企业统一信用代码这类高基数字段才是候选。第三查询是不是总会带这个条件。分片键的价值在于路由定位如果业务里大量查询根本不带这个字段那分片对查询的加速作用就大打折扣甚至变成负担。说一个我踩过的坑早年我们把订单表按order_id做 Hash 分片逻辑上很完美——订单号唯一且均匀。结果上线两周发现后台运营所有查询都是按商家维度拉的因为订单号对运营来说毫无意义。每条 SQL 都要广播到 64 张表再在应用层做内存合并原本 50ms 的查询变成 2 秒运营同事直接投诉。后来只能加了一张“商家-订单”的映射表才算把问题缓解。这就是典型的分片键只考虑“技术均匀性”而忽略“业务查询维度”的教训。3.2 路由与SQL改写中间件的活儿自己得懂关系型数据库的分片路由现在主流是两大类一类是应用层框架比如 ShardingSphere 这种嵌入到业务代码里做 SQL 解析和路由优点是轻量、部署简单另一类是独立部署的中间件比如 MyCat 这类代理层对应用透明但多了一层网络转发和协议转换性能损耗和运维成本都要考虑。选哪种我的看法是小团队优先选应用层框架因为代理层自身的性能瓶颈、高可用配置就能让一个团队忙活好几周。但不管用哪种有几个底层机制你必须懂精确路由PreciseRouting和范围路由RangeRouting。中间件拿到 SQL 后会从 where 条件里提取分片键的值或范围然后通过分片算法精确计算目标分片。如果 SQL 里没有分片键就会走广播路由给所有分片都发一份再合并结果。这也是为什么“查询不带分片键”会变成性能灾难。分片键加函数运算要小心。有些 SQL 喜欢写WHERE user_id 1 100绝大多数中间件无法从这种表达式里提取分片键只能广播查询。类似的还有DATE(create_time) 2024-01-01如果 create_time 不是分片键还好一旦分片键是它建议把 SQL 改成范围条件或者设计时就避免对分片键做函数包裹。补充一个通用的路由计算公式示例方便你评估中间件日志里的分片结果// 假设规划10个库每个库100张表共1000个分片 // 分片键sellerId 9527 int shardCount 1000; int shardId Math.abs(String.valueOf(sellerId).hashCode()) % shardCount; int dbIndex shardId / 100; // 决定落在哪个库 int tableIndex shardId % 100; // 决定落在哪张表3.3 容量规划先算清楚再定分片数量分片数量不是越多越好。我见过一个团队把流水表分了 1024 张结果单张表数据量才 20 万行查询动不动就要跨 256 张表合并路由效率低到令人崩溃。容量规划的核心平衡点是单分片的数据量要降到“单机舒服区间”但分片总数又要控制在一个能接受的范围。经验公式大致是这样预估未来 2~3 年的数据总量单位是行。假设订单每年增长 5 亿行三年就是 15 亿。确定单分片舒适数据量。对于 InnoDB单表控制在 1000 万到 3000 万行之间比较稳妥超过 5000 万就要考虑性能预警。用总量除以单分片目标量得到最小分片数。15 亿 / 2000 万 75 个分片预留 20% 缓冲取整到 128 个分片8 库 × 16 表会比较从容。分片总数尽量保持“库数 × 每库表数”的结构这样后续扩容可以按“库数量翻倍”或“每库表数翻倍”两个方向演进不至于把底层路由表做得太零散。3.4 扩容方案预设计现在不想后面更痛苦很多人觉得扩容是几年后的事到时候再说。实际上分片方案上线那一刻扩容路径就已经被锁死了——所以设计方案时就要提前想好未来怎么扩。如果是 Hash 取模分片未来的扩容无非两条路。一条是翻倍扩容库和表都翻倍迁移时用双写 校验 切流的方式平滑过渡另一条是提前在分片键上做二次取模预先拆出“虚拟分片”将来把若干虚拟分片归并到一个物理分片上。第二条路设计复杂但迁移量小适合数据量增长不可控的业务。一致性哈希方案的扩容相对轻松环上每个节点拆成两个虚拟节点组一半数据平滑迁移到新节点不过要提前做好虚拟节点的数量规划不然迁移后热点依然存在。实操心得不管哪种方案扩容都要准备一份回滚预案。最常见的手法是把数据迁移分为“同步阶段”和“切换阶段”同步阶段全量增量持续追平切换阶段停写片刻、确认两边数据一致后改路由。如果切换后发现问题路由改回去就行前提是你保留了旧库的只读权限和数据快照。4. 分片上线后的坑与周边系统联动分库分表上线不是终点它只是把单库时代的“大问题”换成了分布式时代的“一堆小问题”。下面是我们在实践中反复踩到的坑直接列出来当速查表用。4.1 常见问题速查表现象根因排查思路解决方向某个分片数据量明显大于其他分片分片键有热点值比如超大商家统计各分片行数和写入TPS热点分片再次拆分或映射表手工引流查询偶发超时慢 SQL 出现在所有分片SQL 没带分片键走广播路由查看中间件路由日志SQL 改造带分片键或加映射表分页深翻页卡死全局排序取 limit 100000,20分析合并排序的开销改成“游标分页 每分片取更多再归并”跨分片 join 返回数据错乱或超时join 键与分片键不一致查看执行计划冗余字段 应用层组装或宽表数据迁移后出现主键冲突全局ID生成方案缺失或重复对比迁移日志使用Snowflake/Leaf/自研全局发号器定时任务重复扫描同一数据每个分片独立跑job边界未切分检查任务分片逻辑按分片维度传入任务参数而不是各自全扫4.2 数据倾斜与热点处理数据倾斜是分库分表上线后最让人头疼的问题。即便分片键整体均匀也拦不住业务本身的头部效应——比如电商平台的大卖家、社交产品里的超级大 V。处理思路分三层。第一层是分片键设计时就预埋“二级拆分”比如商家维度再叠加商品维度让超级大商家的数据进一步散开第二层是动态识别热点分片把热点数据复制到专门的热点库查询命中时先走热点库第三层才是事后的重新分片成本很高。很多团队会忽略一个要点热点处理不是纯技术问题先要知道热点怎么定义。是单分片行数超过阈值还是单分片 QPS 超过阈值这两个维度的处理方式完全不同。行数大意味着要拆数据QPS 高意味着要加副本或缓存别搞混。4.3 非分片键查询的兜底方案不带分片键的查询是无解的也不是。兜底方案按成本从低到高排列映射表/索引表。在映射表里记录“业务键 - 分片键”的关联比如商家和订单的关系查询订单时先查映射表拿到买家 ID再走分片路由。适合低频后台查询。搜索引擎/宽表。把全量数据同步到 ES 或 ClickHouse 之类的分析引擎非分片键查询组合筛选、聚合统计直接走分析引擎。适合运营后台和 BI 报表。异构索引。同步一份只包含“查询字段 分片定位字段”的轻量索引数据体积小、查询快但只能解决单点定位类查询不适合聚合统计。我接触过的业务几乎都是“分片主链路 ES 辅助查询”的组合拳。这里多说一句全文检索和聚合统计用 ES 不是要同步全字段只需要同步查询、过滤和展示必要的字段即可尽量控制索引体积降低同步延迟。4.4 分布式事务与全局ID的配套设计分库分表之后单库事务变成了跨库“伪事务”。常用的方案是本地消息表、事务消息、TCC、Saga。没有一种是完美的都拿“最终一致性”作为代价。我最想提醒的是分布式事务应该靠业务建模规避而不是靠框架解决。在设计分片键时就把“需要在同一个事务里的操作”尽量放到同一个分片内——比如下单、扣库存、锁优惠券都围绕同一个用户 ID 展开那它们天然落在同一个分片里普通事务就能覆盖根本不用引入分布式事务框架。我见过太多团队把订单和库存两个库拆开了然后为了扣减库存和创建订单的一致性折腾了半年最后发现业务上完全可以通过“预占 异步对账”来兜底根本不需要强一致。全局 ID 的坑也不小。分库分表后不能用自增主键否则多个分片的主键必然冲突。主流的替代方案是 Snowflake 算法、美团 Leaf、滴滴 TinyID或者直接上 UUID注意存储优化和索引性能。设计时要注意全局 ID 不仅是主键最好还能蕴含分片信息比如把分片号编码进 ID 的中间段。这样反查 ID 时不用算哈希也能直接定位分片排查问题的时候能省很多事。4.5 灰度迁移与双写校验最后讲数据迁移这是整个分库分表项目最容易出事故的环节。很多团队栽在迁移上不是分片策略选错而是迁移过程没设计好。标准做法是“双写 校验 切读”。双写阶段旧库和新库同时写新库的写入先通过改造后的 DAO 层路由过去校验阶段写一个对账任务定期比对新旧库的数据差异主键维度逐条比对切读阶段读流量按比例灰度先放 10% 到新链路观察错误率和耗时逐步放大到 100%。切读有个容易被忽略的细节接口超时配置要分开。新链路首次承担大量请求时冷数据加载、连接池预热都可能导致比旧链路更慢如果你直接把超时时间调成和旧链路一样灰度阶段可能会收到一堆超时误报。建议灰度开始时把新链路超时放宽 50%稳定后再逐步收紧。回滚方案必须提前定义清楚“回滚点”。比如灰度到 30% 的时候发现数据错乱回滚操作是什么路由切回旧库双写继续保留新库数据停更等修复后重新同步——这套流程要写成剧本并且排练一遍而不是出了问题再开会讨论。5. 我的一些总结性体会分表分库分片策略的选型本质上是在“查询灵活度、写入均衡度、未来扩展成本”这三个维度之间找一个能接受的平衡点不存在一套方案通吃所有业务。我能给出的最具体建议是先把业务查询维度清单列出来按频率和重要性排个序用这些查询去反推分片键和分片策略而不是先定框架再让业务适配。顺序反了后面全是返工。最后再分享一个小技巧不管选了哪个策略上线之前一定要留一套“全量数据导出到单表”的工具。分库分表跑一阵子以后你会发现所有的排查、对账、临时报表需求最后都得靠这套工具把分散的数据重新拉回去分析。那时候回头看这个不起眼的导出工具可能是整个分片方案里性价比最高的一个组件。