
前面挖过一阵子分库分表也踩了不少坑正好最近梳理面试题时又把这套东西完整过了一遍。网上讲ShardingSphere的文章很多但要么是照搬官方文档的配置示例要么一上来就给你整一堆源码分析对真正想落地的人并不友好。这篇我换个讲法从面试官最关心的几个问题切入结合我实际做过的项目把分库分表从设计到落地再到面试应答完整串一遍。如果你是一个Java后端开发者无论是正在准备面试还是项目中即将要面对数据量暴涨的问题这篇文章都适合你。我会先把分库分表的核心逻辑讲透再带你手把手把ShardingSphere的配置跑起来最后聊几个高频面试题和答题思路。1. 先搞清楚你什么时候才需要分库分表很多人的困惑其实不是怎么分而是要不要分。搞清楚这个问题比学会用ShardingSphere重要得多。我在面试中问候选人分库分表大部分人都能背出水平拆分、垂直拆分几个词但真要问一句你什么场景下才会考虑分库分表不少人就卡住了。1.1 数据量到什么级别就该动手了先说一个我自己的经验值。MySQL单表数据量在千万级别以内索引合理的话读写性能基本还能撑住。但当单表数据量超过2000万到3000万或者单表容量超过50GB左右时B树的层级会加深索引命中后的随机IO开销明显上升即使走索引查询响应时间也可能从个位数毫秒变成几十毫秒甚至上百毫秒。这时候分库分表就该提上日程了。不过你要注意数据量只是触发条件之一更关键的是要看数据增长趋势。有些表虽然现在已经1000万了但业务增长缓慢优化一下索引也许还能用两年。而有些表一年就翻几倍那就算现在只有500万也得趁早规划。分库分表最忌讳的是等性能已经扛不住了再动手那时数据迁移的代价、业务改造的成本都会成倍放大。具体到判断维度我从三个角度看读压力大不大如果单库的QPS已经到几千CPU和IO长期在70%以上加缓存已经救不回来了就需要考虑拆分。写压力大不大如果单库的写入成为了瓶颈比如订单流水这种只增不减的表每天新增百万级数据写入锁竞争、binlog同步延迟都会成为问题。存储容量能不能接受单库存储已经到几个TB备份和恢复时间动辄以小时计这时光靠清理归档数据也不是长久之计。1.2 垂直拆、水平拆到底怎么选垂直拆分是按业务模块拆把一张大表拆成多张结构不同的表或者按业务域拆库。比如把用户表、订单表、支付流水表拆到不同库。这种拆分相对简单业务侵入也小但是解决不了单表数据量过大的核心问题——用户表就算单独一个库它也还是有那么多数据。水平拆分才是按数据行拆把同一张表的数据按某个规则分散到多张结构完全相同的表里每张表只保存一部分数据。比如订单表按用户ID取模拆成32张表。这才是解决单表数据量问题的正统方案。实际项目中两种拆分往往是配合使用的先按业务域垂直拆分落地到不同库再对核心大表做水平拆分。这里必须提醒一句分库分表是解决性能问题的最终手段不是首选手段。动手拆分前先确认缓存、索引优化、读写分离、归档冷数据等手段都已经做到位了。很多团队一遇到性能问题就说我们要分库分表结果分完之后发现瓶颈根本不在数据库白折腾一场。2. ShardingSphere选型为什么是它市面上分库分表中间件有好几个各有各的适用场景。早年用MyCat的不少最近几年新项目选择ShardingSphere的占了大多数Apache基金会顶级项目的光环确实不虚。但选型这件事不能跟风你得知道每个方案解决什么问题才有底气在面试中讲清楚为什么选这种方案。2.1 三种形态JDBC、Proxy、SidecarShardingSphere项目下面其实有三个子产品这个很多人没搞清楚面试时首先就露怯了。ShardingSphere-JDBC以jar包形式存在是增强版的JDBC驱动。你的应用直接引入依赖配置好分片规则它就在底层帮你把SQL改写好、路由到正确的库表。这种模式不需要额外部署服务业务侵入小性能损耗也低适合Java应用。ShardingSphere-Proxy独立部署的代理服务对应用透明。应用连接代理就像连接一个普通的MySQL代理在背后做分片和路由。这种模式适合异构语言、不方便改造的应用也可以作为治理层统一管控。ShardingSphere-Sidecar这个还不太成熟现在很少有团队在线上使用面试中了解一下即可不用深挖。我自己的项目里用的是ShardingSphere-JDBC 5.x版本和Spring Boot集成非常顺畅配置也直观。如果你的场景是多个不同语言的应用统一入口走Proxy更合适。2.2 核心概念一篇文章讲清楚在进入实操之前必须把ShardingSphere的几个核心概念弄明白。面试题考来考去翻来覆去就是这些东西。逻辑表你在代码里操作的表名比如t_order。你在SQL里写的表名就是逻辑表名。实际表真正存储在数据库里的表比如t_order_0、t_order_1、t_order_2。数据节点实际数据分布的位置描述为库名.表名例如ds0.t_order_1。分片键数据库按哪个字段拆分这是分表的关键决策点。订单表按user_id分日志表按id分就是分片键的差别。分片算法决定一条数据落到哪张表的规则比如取模、哈希、区间等。分片策略分片算法 分片键的组合比如user_id取模4就是一条完整的策略。为什么说分片键是最重要的决策因为一旦选定分片键所有查询都必须尽可能带有这个字段。你按user_id分了32张表那查某个用户最近的订单就很方便ShardingSphere能精准定位到一张表。但你要是写一个查最近24小时所有订单的SQL没有分片键它就只能把SQL广播到全部32张表去执行性能反而更差。这个逻辑一定要在面试中讲出来面试官就是想听这种知其所以然的答案。3. 从零开始Spring Boot集成与基础配置这一小节照着做就能跑起来依赖用的是ShardingSphere-JDBC 5.3.2版本Spring Boot 2.7.x。环境上准备一个MySQL即可本机没有装的话用Docker起一个最省事。3.1 引入依赖如果是Spring Boot项目在pom.xml里引入shardingsphere-jdbc-core-spring-boot-starter即可dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactId version5.3.2/version /dependency需要注意一个坑ShardingSphere 5.x使用的是Antlr解析SQL对SQL语法有一定要求项目里如果用了比较特殊的SQL比如基于函数的索引、特殊Hint解析可能会报错。这也是我前面反复强调不要轻易上分库分表的原因之一。3.2 规划库表结构与分片策略这里我用一个最简单的订单场景来做示例。假设我们要把订单表t_order按user_id做取模分表分4张表。第一步创建物理表。注意ShardingSphere不会帮你自动建表表需要你在MySQL里提前建好。在常见的生产实践里这一步通常放在发布脚本中CREATE TABLE IF NOT EXISTS t_order_0 ( id BIGINT NOT NULL, order_no VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, amount DECIMAL(10, 2) NOT NULL, status INT NOT NULL, create_time DATETIME NOT NULL, PRIMARY KEY(id), KEY idx_user_id(user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;后面要建t_order_1、t_order_2、t_order_3一共四张结构完全一样的表。SQL结构可以从t_order_0复制改表名即可。第二步配置数据源。所谓数据源就是告诉ShardingSphere你的库在哪里。这里假设四个分片表都在同一个库ds0里如果你想分成4个库每个库放1张表那就是另一种配置方式了后面会讲。application.yml中配置spring: shardingsphere: datasource: names: ds0 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/sharding_demo?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root rules: sharding: tables: t_order: actual-data-nodes: ds0.t_order_$-{0..3} table-strategy: standard: sharding-column: user_id sharding-algorithm-name: t_order_inline sharding-algorithms: t_order_inline: type: INLINE props: algorithm-expression: t_order_$-{user_id % 4} props: sql-show: true这里的关键点是actual-data-nodes。$-{0..3}是个表达式表示枚举从0到3它告诉ShardingSphere逻辑表t_order对应的实际表有4张。sharding-column指定分片键是user_id。algorithm-expression是分片算法表达式user_id % 4表示对4取模。3.3 配置里的几个细节不要小看这几行配置里面的坑非常多。我把我踩过的、以及别人踩过告诉我的坑一起列出来。第一actual-data-nodes里的表达式写法。很多新手会写成ds0.t_order_0, ds0.t_order_1, ds0.t_order_2, ds0.t_order_3这样也能跑但表一旦多了就很啰嗦而且如果后续要把4张表扩成8张改动也麻烦。官方推荐的$-{0..3}区间表达式是更优的选择。第二algorithm-expression里的取模运算。这个表达式是Groovy语法不是Java。t_order_$-{user_id % 4}注意不要写成user_id%4L如果分片键的类型是LongGroovy里直接写user_id % 4就行写了个长整型后缀反而容易出问题。第三sql-show这个属性在生产环境要关掉。开发阶段打开它每次执行SQL都会把路由信息打出来很方便排查但线上开这个会白白消耗IO并打印大量日志。4. 核心实操分片、读写分离与分布式ID4.1 数据写入与查询的原理验证配置完成后写一段正常的MyBatis-Plus代码插入订单Service public class OrderServiceImpl implements OrderService { Resource private OrderMapper orderMapper; Override public void createOrder(Order order) { orderMapper.insert(order); } Override public Order getByUserAndId(Long userId, Long id) { return orderMapper.selectByIdAndUserId(id, userId); } }这里有个关键点当你有user_id这个分片键时ShardingSphere会精确路由到某一张表去查询这里通过Mapper层传递userId作为查询条件就能触发精确路由。为了看效果配置了sql-show执行时会打印路由日志[INFO ] ShardingSphere-SQL: Logic SQL: select * from t_order where user_id 1 and id 100 [INFO ] ShardingSphere-SQL: Actual SQL: ds0 ::: select * from t_order_1 where user_id 1 and id 100看到这个日志说明逻辑SQL被改写成了目标表上的真实SQL。但如果你不带分片键查询比如select * from t_order where order_no xxx日志里就会变成全路由4张表都打一遍实际SQL会显示4条。这在高并发场景下就是灾难。后面讲面试题的时候会专门聊这个问题的解决思路。4.2 取模分表、哈希分表和Range分表取模分表是我们刚刚演示的方案也是中小团队最常用的。它实现简单、数据分布均匀但最大的问题是扩容麻烦原来是4张表要扩到8张所有数据的分布都会变化历史数据必须全部重新分配。我见过不少团队直接用了取模方案结果三年后要扩容整条链路全部要停服迁移代价非常大。所以如果你的表规模大概率会超过预期的一开始就要考虑Range分表或一致性哈希。Range分表按时间区间或者ID区间拆比如每个月一张表t_order_202401、t_order_202402。它的好处是扩容简单新增一个月就新建一张表不用动老数据。坏处是数据分布不均匀热点月份数据量可能翻倍。这种方案比较适合日志表、流水表这类有时间属性的数据。哈希分表像是取模的优化版它先对分片键做哈希再取模好处是不管分片键是字符串还是数字都能实现相对均匀的分布。ShardingSphere有个HASH_MOD算法配置也很简单sharding-algorithms: t_order_hash: type: HASH_MOD props: sharding-count: 44.3 读写分离平滑落地分库分表通常和读写分离一起出现。原理是一样的主库负责写一个或多个从库负责读ShardingSphere在中间做流量路由。配置上要加一个读写分离规则spring: shardingsphere: rules: readwrite-splitting: >rules: key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1然后在表分片规则里指定主键生成策略tables: t_order: key-generate-strategy: column: id key-generator-name: snowflake这样插入时不传IDShardingSphere会帮你生成雪花ID并写入分表里对应主键。这是官方封装好的能力直接用即可不用自己重复造轮子。5. 分库分表之后那些躲不掉的复杂问题5.1 分布式事务怎么处理分表之后最直接的连锁反应就是分布式事务。没有分表时一个订单流程涉及订单表、库存表、流水表只需要一个本地事务。分表之后订单数据落在t_order_1库存数据也可能在另一台库的另一张表里本地事务管不到那么远必须引入分布式事务的协调机制。ShardingSphere支持两种事务方案XA强一致事务和Seata柔性事务。强一致方案适合对一致性要求极高的场景比如金融交易但性能开销比较大。柔性事务适合大部分互联网业务场景这里推荐Seata的AT模式它本质上用回滚日志实现了最终一致性业务代码无侵入性能也相对可控。实际落地时Spring的Transactional注解依然能用在ShardingSphere上它会自动转换成分布式事务逻辑。我仍要强调的是能通过业务设计规避分布式事务的场景尽量避免使用分布式事务。比如把订单和明细设计成同分片键——按user_id把订单主表和明细表分到同一个分片在一个库内完成操作就不需要分布式事务了。很多面试官问分布式事务其实更期望听到这种从根上避免的思路而不是只会背XA、TCC的概念。5.2 跨分片查询和分页问题跨分片查询是分库分表无法回避的问题。最常见的两类场景不带分片键的查询以及带分页的查询。不带分片键时ShardingSphere会把查询广播到所有分片执行完后在中间件层对结果做归并。数据量小的时候问题不大数据量一大这种全路由查询会消耗大量数据库资源。分页问题更典型LIMIT 10000, 10每个分片都会各捞10010条数据回来在内存中合并排序后再取第10000条之后的10条。数据越深翻内存和计算开销越离谱。业界主流思路是禁止深分页产品上通过上次ID游标代替页码跳转也就是WHERE id %s ORDER BY id LIMIT 10这种。这个方案要海量数据下还能支持深分页就依赖于有序ID比如雪花ID天然支持这种游标方式。在实际项目和面试中我都推荐优先用这种方案。5.3 绑定表与广播表这两个概念也是高频考题。绑定表是指分片规则一致的主子表。例如t_order和t_order_item都按user_id分表ShardingSphere将它们配置为绑定表之后联表查询JOIN可以确保两张表的分片键相同避免笛卡尔积式的跨表JOIN。配置示例rules: sharding: binding-tables: - t_order, t_order_item广播表则是每张分片库里都存在一份相同结构的表比如字典表、配置表这种表在ShardingSphere里配置为广播表后修改数据时会同步广播到所有分片库查询走任意库即可。这两个概念面试中出现频率极高而且在真实项目中的确很常用理解配比记配置重要得多。6. 面试题整理与答题思路6.1 核心高频考点结合最近两年各大厂的Java后端面试题我把分库分表相关的高频考点整理成一个清单什么时候需要分库分表你如何判断一个系统该分库了垂直拆分和水平拆分的区别与应用场景分片键如何选择分片算法有哪些分库分表后分布式ID怎么生成分库分表后跨分片查询和分页怎么处理分库分表后事务怎么做扩容怎么做取模分表怎么平滑扩容ShardingSphere与MyCat的差异对比这些问题环环相扣不要死记答案建议把前面几章的逻辑线串起来就都能答出来。6.2 答题技巧与常见误区我听过很多候选人把分库分表的原理讲得很漂亮但一落到实际问题就露馅。比如面试官问你们当时分片键是怎么选的如果答案只是选了user_id因为查询多面试官大概率会追问那你们订单查询只有按用户查吗客服后台查订单怎么办这就是面试官在验证你有没有真正经历过这个决策过程而不仅是背概念。好的回答一定包含业务查询需求的梳理、备选方案的权衡、分片键不完美时的兜底设计比如通过ES索引配合查询。另外诚实比什么都重要。如果你只是自学过、没真正在项目里落地过分库分表面试时就不要硬说我们项目用了。面试官顺着问你分库分表后每天的监控指标怎么建的、有没有踩过主从延迟的坑、数据迁移做了多久马上就穿帮了。更好的策略是坦诚说自己在项目里调研过、做过Demo然后把原理讲透反而能体现你的学习能力和思考深度。6.3 热点扩展后端开发的学习路线建议结合后端开发学习路线这类高频搜索词我也聊几句除了分库分表Java后端进阶还需要关注的东西。数据层只是后端的一部分完整的知识体系至少要覆盖这几个方向Java基础与并发线程池、JMM、锁、AQS这是面试常量。框架底层Spring Boot自动装配、Spring IOC/AOP源码至少要能讲清楚核心原理。中间件Redis、消息队列Kafka/RocketMQ、Elasticsearch至少掌握两类以上。微服务治理服务注册发现、配置中心、网关、熔断限流。存储技术MySQL索引调优、分库分表、分布式事务这篇讲的就是这一块。系统设计能力高并发秒杀、订单状态机、接口幂等、分布式锁、消息最终一致性。分库分表在整套体系里不是孤立的技术它涉及SQL路由、分布式ID、事务、监控、迁移等一连串问题是能横向拉通底层存储和系统设计的好课题。认真吃透这一块能帮你串起很多本来零散的知识点。7. 实操心得与避坑清单最后写几条我实操总结出来的心得。这些在网上正规文档里都看不到但每一条背后都是折腾了很久的代价。第一分库分表一定要优先保证链路可观测。上线前就把慢SQL日志、分片路由日志、表容量监控全部接好。没有监控就相当于蒙着眼睛开车出问题只能靠猜。第二自动建表永远不要依赖中间件。我见过有团队以为配置了actual-data-nodes中间件就会自动创建表结果上线后新表不存在数据直接写不进。物理表结构要靠运维脚本管理并用CI/CD平台在发版前自动执行。第三配置变更要严格走评审。一次分片算法表达式的修改可能影响所有数据的读写路径。很多事故不是因为代码写错而是改配置时手滑了。ShardingSphere支持部分配置动态刷新但底层表的扩容和缩放仍然需要严谨的方案评审。第四数据库中间件的版本升级要谨慎。保留原版本、灰度验证、逐步放量缺少一步都可能让你一夜回到解放前。我在5.1升级到5.3时就遇到过SQL解析的细微差异导致部分查询路由异常好在小流量验证及时发现没有对线上造成大范围影响。最后再分享一个我踩过最深的坑上线分库分表最难的不是技术而是止损方案。很多团队只想着怎么把数据分出去没想过万一中间件出问题怎么快速切回来。我的建议是一定保证每个分片库里同时保留一份基于分片键、能独立支撑业务的索引结构这样就算ShardingSphere整体不可用你也能通过直连分库的方式临时撑住核心链路。听起来是句废话真出问题的时候这个兜底方案能救命。