尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SpringBoot整合ShardingSphere JDBC实现MySQL分表实战
1. 为什么单表撑不住时分表是绕不开的那道坎做业务系统时间长了总会碰到那个让人头皮发麻的时刻一张订单表或者日志表数据量从几十万飙到几千万查询开始变慢索引像纸糊的一样写入也开始排队。你可能会想加索引、加缓存、读写分离能上的手段都上了但单表的数据量摆在那里物理文件的体积、B树的高度、磁盘IO的随机读这些底层限制不是靠调优能绕过去的。这时候分表就成了一个必须认真考虑的方案。分表的核心逻辑不复杂就是把一张逻辑上的大表按照某个规则拆成多张物理小表让每次查询和写入只落在其中一张或少数几张表上。听起来简单但真到落地的时候问题就来了分片规则怎么定、SQL怎么改写、事务怎么保证、扩容怎么办。如果全靠手写代码去拼SQL和路由逻辑维护成本会高到让人怀疑人生。ShardingSphere JDBC 这个组件就是来解决这个问题的。它以一个轻量级的JAR包形式存在不需要额外部署服务直接嵌在SpringBoot应用里帮你把分片路由、SQL改写、结果归并这些脏活累活全干了。你写SQL的时候还是按逻辑表来写它负责在底层把SQL翻译成对多张物理表的操作。这篇文章我就围绕SpringBoot整合ShardingSphere JDBC做MySQL分表这个主题把整个搭建过程、配置细节、踩过的坑和实际效果完整地梳理一遍。适合谁来参考如果你正在做订单、日志、流水这类数据量增长快、单表已经出现性能瓶颈的业务或者你所在的项目组正在评估分表方案那这篇内容应该能帮你少走不少弯路。我会尽量把每一步的操作意图和背后的原理讲清楚让你不仅知道怎么配还知道为什么这么配。2. 分表方案的整体设计与选型考量2.1 为什么选ShardingSphere JDBC而不是其他方案分表这件事市面上能选的方案其实不少。大致可以分成三类一类是手工分表在代码层根据分片键自己拼表名一类是中间件代理模式比如ShardingSphere Proxy、MyCat这种独立部署一个代理层应用连代理代理再连数据库还有一类就是ShardingSphere JDBC这种客户端模式以JAR包形式嵌入应用。手工分表的缺点很明显SQL拼接、路由逻辑、结果合并全得自己写代码侵入性极强后期改分片规则几乎等于重写。代理模式的好处是对应用透明但多了一层网络跳转性能有损耗而且代理层本身的高可用和运维又是一个新的负担。ShardingSphere JDBC的优势在于它没有额外的网络开销性能接近直连数据库配置集中在YAML或Java Config里改分片规则不需要动业务代码而且它和SpringBoot的整合非常顺滑基本上就是加依赖、写配置、改数据源这几步。我选它的核心理由就一个在保持轻量的前提下把分片的复杂度从业务代码里彻底剥离出来。对于中小规模的系统或者不想引入额外运维负担的团队这个方案是性价比最高的。2.2 分片键和分片算法的选择逻辑分片键的选择直接决定了分表方案能不能真正解决性能问题。选分片键的时候我一般会看三个维度查询频率、数据分布均匀度、业务增长趋势。以订单表为例最常见的查询场景是“按用户查订单”和“按订单号查订单”。如果分片键选用户ID那按用户查的时候能精准路由到一张表但按订单号查就会变成全表扫描。如果分片键选订单号那按订单号查很快但按用户查就麻烦了。这时候就要看哪个查询场景更核心、频率更高。我这次的做法是选用户ID作为分片键因为业务上“查我的订单”是最高频的操作。订单号查询的场景可以通过在订单号里嵌入用户ID的基因法来解决或者单独建一张映射表。这个后面会细说。分片算法这块ShardingSphere内置了好几种常用的有取模分片、哈希取模、范围分片、时间分片。取模分片的好处是数据分布均匀缺点是扩容麻烦从4张表扩到8张表几乎所有数据都要迁移。范围分片扩容方便但容易出现热点比如按时间范围分最近的数据总是落在同一张表上。我这次用的是取模分片因为当前的数据量级和增长趋势4张表撑个两三年没问题暂时不需要考虑在线扩容。如果你们业务增长特别快建议一开始就用一致性哈希或者范围加分片的组合策略给未来留余地。2.3 分表数量的确定与容量估算分多少张表这个不能拍脑袋决定。我的估算逻辑是这样的先看当前单表的数据量和月增长量再预估未来一到两年的数据规模然后根据单表的最佳承载量来反推分表数量。假设订单表当前有800万条数据每月新增约100万条单表在InnoDB下如果索引设计合理2000万到3000万条是一个比较舒适的范围超过5000万条性能下降会很明显。按两年24个月算新增约2400万条加上存量800万总共约3200万条。如果分成4张表每张表约800万条非常轻松分成2张表每张1600万条也能撑住但余量不大。所以我选了4张表给未来留出足够的缓冲空间。这个估算不是精确科学但能帮你避免两个极端分得太少很快又遇到瓶颈分得太多管理复杂度和跨表查询的成本都上去了。3. 核心配置与实操步骤详解3.1 依赖引入与版本选择SpringBoot整合ShardingSphere JDBC第一步是加依赖。这里有个关键点ShardingSphere的版本和SpringBoot的版本要匹配。我这次用的是SpringBoot 2.7.x配合ShardingSphere JDBC 5.3.x这个组合比较稳定社区资料也多。在pom.xml里加这两个依赖dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactId version5.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency注意ShardingSphere 5.x和4.x的配置格式差异很大网上很多教程还是4.x的写法直接抄会报错。5.x统一用了rules作为配置入口数据源配置也变了一定要看清楚版本。另外如果你项目里用了Druid或者HikariCP作为连接池ShardingSphere JDBC是兼容的它内部会管理实际的数据源。我这次用的是HikariCPSpringBoot默认就是它不需要额外配置。3.2 数据源配置的两种方式ShardingSphere JDBC的数据源配置有两种方式YAML配置文件和Java Config。YAML方式更简洁适合配置固定的场景Java Config更灵活适合需要动态计算分片规则的场景。我这次用的是YAML方式配置放在application-sharding.yml里通过spring.profiles.include引入。核心配置结构是这样的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/order_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password rules: sharding: tables: t_order: actual-data-nodes: ds0.t_order_$-{0..3} table-strategy: standard: sharding-column: user_id sharding-algorithm-name: order-table-inline sharding-algorithms: order-table-inline: type: INLINE props: algorithm-expression: t_order_$-{user_id % 4} props: sql-show: true这段配置里几个关键点actual-data-nodes定义了逻辑表t_order对应的物理表$-{0..3}是行表达式展开后就是t_order_0到t_order_3。sharding-column指定了分片键是user_id。algorithm-expression是分片算法表达式user_id % 4决定了数据落在哪张表。sql-show: true会在控制台打印实际执行的SQL调试阶段非常有用上线后建议关掉。实操心得actual-data-nodes里的数据源名称ds0必须和datasource.names里定义的一致大小写敏感。我见过有人写成DS0结果启动直接报数据源找不到。3.3 物理表的创建与初始化配置写好后物理表得先建出来。4张分表的结构必须完全一致包括索引。我一般会写一个初始化SQL脚本在应用启动时自动执行或者手动执行一次。CREATE TABLE IF NOT EXISTS t_order_0 ( order_id BIGINT NOT NULL, user_id BIGINT NOT NULL, order_no VARCHAR(64) NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id), KEY idx_user_id (user_id), KEY idx_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- t_order_1、t_order_2、t_order_3 结构相同省略这里有个细节主键order_id不能是自增的。因为分表之后自增主键会在每张表里独立增长导致ID重复。解决方案是用雪花算法生成全局唯一ID或者用Redis的INCR命令生成。我这次用的是雪花算法在应用层生成ID后再插入。注意如果你用的是MyBatis-Plus它的TableId(type IdType.AUTO)在分表场景下会出问题必须改成IdType.ASSIGN_ID或者自定义ID生成器。3.4 分片算法的深入解析与自定义扩展ShardingSphere内置的INLINE分片算法适合简单的取模场景但实际业务中往往需要更复杂的逻辑。比如你想让最近三个月的数据落在独立的表里历史数据按用户ID取模这时候就需要自定义分片算法。自定义分片算法的步骤实现StandardShardingAlgorithm接口泛型指定分片键的类型。在doSharding方法里写路由逻辑。在配置里把type改成自定义算法的全限定类名。public class OrderShardingAlgorithm implements StandardShardingAlgorithmLong { Override public String doSharding(CollectionString availableTargetNames, PreciseShardingValueLong shardingValue) { Long userId shardingValue.getValue(); long index userId % 4; return t_order_ index; } Override public CollectionString doSharding(CollectionString availableTargetNames, RangeShardingValueLong shardingValue) { // 范围查询时返回所有表由ShardingSphere做结果归并 return availableTargetNames; } Override public void init() { // 初始化逻辑可以读取配置参数 } Override public String getType() { return ORDER_CUSTOM; } }配置里这样引用sharding-algorithms: order-custom: type: ORDER_CUSTOM props: strategy: standard实操心得自定义算法里不要做数据库查询或者远程调用因为分片算法会在每次SQL执行时被调用性能敏感。如果分片逻辑依赖外部数据建议在应用启动时加载到本地缓存。3.5 分布式主键的生成策略分表之后主键必须全局唯一。ShardingSphere内置了雪花算法的主键生成器配置很简单rules: sharding: tables: t_order: key-generate-strategy: column: order_id key-generator-name: snowflake key-generators: snowflake: type: SNOWFLAKE props: worker-id: 1这样配置后插入数据时不需要手动设置order_idShardingSphere会自动生成。但要注意雪花算法生成的ID是Long类型前端JavaScript处理大整数会丢失精度所以返回给前端的时候要转成String。注意worker-id在分布式环境下必须唯一如果多个应用实例共用同一个数据库每个实例的worker-id要不一样否则可能生成重复ID。可以通过环境变量或者配置中心来动态分配。4. 业务代码适配与SQL使用规范4.1 逻辑表与物理表的映射关系ShardingSphere JDBC的核心设计思想是逻辑表和物理表的分离。你在代码里写的SQL操作的是逻辑表t_orderShardingSphere会根据分片规则把它改写成对t_order_0到t_order_3中某一张表的操作。这意味着你的Mapper层代码几乎不需要改动。原来怎么写现在还怎么写Mapper public interface OrderMapper { Insert(INSERT INTO t_order (order_id, user_id, order_no, amount, status) VALUES (#{orderId}, #{userId}, #{orderNo}, #{amount}, #{status})) int insert(Order order); Select(SELECT * FROM t_order WHERE user_id #{userId}) ListOrder selectByUserId(Param(userId) Long userId); }这段代码里的t_order是逻辑表ShardingSphere会自动路由。但有一个前提查询条件里必须带上分片键否则就会全表扫描。4.2 分片键缺失时的全路由问题这是分表后最容易踩的坑。如果你写了一条不带user_id的查询比如SELECT * FROM t_order WHERE order_no xxxShardingSphere不知道数据在哪张表只能把SQL改写成对4张表分别查询然后合并结果。这就是全路由性能比单表查询还差。解决方案有两个方案一基因法。在生成订单号的时候把用户ID的后几位嵌入到订单号里。比如用户ID是12345取后两位45订单号设计成ORD20240101450001。查询的时候从订单号里解析出用户ID的后两位反推出分片位置。这个方案需要对订单号生成规则做设计但查询效率最高。方案二映射表。单独建一张order_no_mapping表记录订单号和用户ID的对应关系。查询时先查映射表拿到用户ID再查订单表。这个方案实现简单但多了一次查询适合订单号查询频率不高的场景。我这次用的是方案一因为订单号查询在业务里是高频操作多一次查询不划算。4.3 分页查询与结果归并的处理分页查询在分表场景下是个难点。假设你要查SELECT * FROM t_order ORDER BY create_time DESC LIMIT 0, 10ShardingSphere会把它改写成对4张表分别查询LIMIT 0, 10然后归并排序再取前10条。这个逻辑是正确的但性能上会有损耗因为每张表都要查10条总共查40条最后只返回10条。如果分页深度很大比如LIMIT 10000, 10性能问题会更严重。优化思路是尽量在查询条件里带上分片键避免全路由。如果必须跨表分页考虑用搜索引擎如Elasticsearch来做查询数据库只负责存储。使用ShardingSphere的LIMIT优化配置开启sql-show观察实际执行的SQL确认归并逻辑是否符合预期。实操心得分页查询的COUNT语句在分表下也会被改写成对多张表的COUNT求和如果数据量大这个操作会很慢。可以考虑用缓存记录总数或者用近似值。4.4 事务管理与分布式事务的边界ShardingSphere JDBC支持本地事务和分布式事务。如果所有分表在同一个数据库实例里本地事务就能保证一致性不需要额外配置。但如果分表分布在不同的数据库实例上就需要分布式事务了。我这次的分表都在同一个MySQL实例里所以直接用Spring的Transactional就行。ShardingSphere会把事务透传给底层的数据库连接。Transactional(rollbackFor Exception.class) public void createOrder(Order order) { orderMapper.insert(order); orderItemMapper.insertBatch(order.getItems()); }注意如果分表跨实例ShardingSphere提供了XA和BASE两种分布式事务模式。XA强一致但性能差BASE最终一致但实现复杂。选哪个要看业务对一致性的要求不要盲目上分布式事务。4.5 读写分离与分表的组合配置如果业务读多写少可以结合读写分离来进一步提升性能。ShardingSphere JDBC支持读写分离和分表的组合配置结构是在rules下同时定义sharding和readwrite-splitting。rules: readwrite-splitting: >// 错误示例 private String userId; // 正确示例 private Long userId;另外如果分片算法表达式里用了user_id % 4但user_id是字符串类型MySQL会做隐式转换结果可能不符合预期。一定要保证类型一致。5.3 查询结果异常数据重复或丢失数据重复通常是因为全路由后结果归并没有去重。比如SELECT * FROM t_order WHERE status 1如果4张表里有相同order_id的数据理论上不应该但主键生成器配置错误时可能发生归并后就会出现重复。数据丢失则可能是分片算法计算出的表名超出了actual-data-nodes的范围。比如user_id % 4的结果是0到3但如果user_id是负数取模结果可能是负数导致路由到不存在的表。注意分片算法里一定要处理边界情况比如负数、null值、超大值。我一般会在算法里加一层校验如果分片键为null直接抛异常避免数据写到错误的表里。5.4 性能问题全路由导致查询变慢全路由是分表后性能下降的主要原因。排查方法很简单打开sql-show看实际执行的SQL是对一张表还是多张表。如果是对多张表就要检查查询条件里是否带了分片键。优化手段包括在查询条件里强制带上分片键比如WHERE user_id ? AND status ?。对于必须跨表的查询考虑用覆盖索引减少回表。如果跨表查询频率很高评估是否应该换分片键或者引入搜索引擎。5.5 扩容时的数据迁移方案取模分片最大的痛点就是扩容。从4张表扩到8张表user_id % 4变成user_id % 8原来在t_order_0的数据可能有一半要迁移到t_order_4。扩容方案一般有两种方案一停机迁移。停服导出所有数据按新规则重新导入改配置重启。简单粗暴但业务中断时间长。方案二双写迁移。新旧规则同时写后台跑迁移任务数据追平后切读最后下线旧规则。这个方案复杂但业务无感知。我这次因为数据量还不大用的是方案一停机窗口控制在30分钟内。如果你们业务不能停建议一开始就用一致性哈希或者预分片比如一开始就分16张表但只用了前4张扩容时直接用后面的表。5.6 常见问题速查表问题现象排查方向解决思路启动报数据源错误配置名称、大小写、profile加载逐项核对YAML配置用最小配置验证插入数据报主键冲突主键生成策略未配置或worker-id重复配置雪花算法确保worker-id唯一查询返回空结果分片键类型不匹配或路由到错误的表检查实体类字段类型和分片算法表达式分页查询结果乱序归并排序未生效确认ORDER BY字段在每张表里都有索引事务不回滚跨实例未启用分布式事务同实例用本地事务跨实例配置XA或BASE性能突然下降全路由或数据倾斜打开sql-show检查是否带分片键6. 实际运行效果与个人经验补充这套方案上线后订单表的查询和写入性能提升很明显。按用户ID查询订单的响应时间从原来的平均200毫秒降到了30毫秒左右写入的TPS也从原来的几百提升到了两千以上。当然这个数据是基于我们特定的硬件和数据量不同环境会有差异但趋势是明确的。我个人在实际操作中的体会是分表这件事配置本身不难难的是分片键的选择和业务代码的适配。分片键选错了后面怎么调都是事倍功半。我的建议是在动手之前先把业务里所有的查询场景列出来按频率排序找出最高频的查询条件用它作为分片键。如果实在找不到一个能覆盖大部分场景的分片键那就接受全路由的存在但要把全路由的查询限制在低频操作上。另外一个小技巧在应用启动时可以加一个健康检查验证分片配置是否正确加载。比如写一个CommandLineRunner往逻辑表里插入一条测试数据再查出来确认路由和归并都正常。这个检查在每次发布时跑一遍能提前发现配置问题避免上线后才发现数据写错了表。这个内容后续还可以这样扩展如果数据量继续增长可以考虑引入ShardingSphere的弹性伸缩功能实现在线扩容或者结合读写分离和分库分表进一步提升整体吞吐量。但那是另一个话题了等真正遇到瓶颈的时候再折腾也不迟。
RELATED

相关推荐

基于CNN的昆虫类别识别:从数据准备到训练调参的完整实战指南

基于CNN的昆虫类别识别:从数据准备到训练调参的完整实战指南

每年到了毕业设计季,总能看到不少同学在“图像识别”这个方向里打转。尤其是“基于CNN的昆虫类别识别”这类题目,几乎是深度学习毕设里最经典、也最稳的一种选题。它不像目标检测那样需要处理复杂的边界框回归,也不像语义分割那样涉及逐像素的…

📅 2026/10/17 16:50:06
2026昌都软件检测第三方公司排行榜TOP5:软件检测+功能性能测试+信息安全测评+软件验收报告 机构地址及联系方式推荐

2026昌都软件检测第三方公司排行榜TOP5:软件检测+功能性能测试+信息安全测评+软件验收报告 机构地址及联系方式推荐

昌都软件检测市场近年日渐繁荣,各类机构鳞次栉比,门面琳琅满目,然其间良莠不齐、泥沙俱下者亦不在少数。为助本地开发者、政务单位及中小企客户择善而从,小编历时三周实地踏勘,走访二十余处办公点位,核查资…

📅 2026/10/17 16:45:04
Flutter圆角边框阴影原理与避坑实战指南

Flutter圆角边框阴影原理与避坑实战指南

1. 项目概述:为什么圆角、边框与阴影是Flutter UI的“呼吸感”关键 在Flutter开发中,一张图片加个圆角、一个Container加条边框、再叠一层柔和阴影——看起来只是UI细节,但实际是用户对产品专业度的第一印象。我带过不少刚转Flutter的前端和A…

📅 2026/10/17 16:45:04
MORE NEWS

更多资讯

📰

网络安全体系方法论:从IPDRR能力框架到企业安全建设落地指南

简介:一份聚焦企业网络安全体系规划的方法论文档,整合安全牛多位资深安全顾问的一线咨询经验,面向企业安全负责人、安全架构师及等保建设人员,解决安全与业务脱节、体系化不足等常见问题。参考NIST Cybersecurity Framework、SABS…

📰

智能名片小程序整站源码部署实操:从PHP后台到微信前端联调

简介:这是“龙兵平台智能名片个人版V8.4.2”整站商业源码,面向毕业设计、微信小程序开发学习者,提供一套小程序前端与PHP后台完整可运行方案,适合需要参考商业级项目来理解前后端协作、完成论文实践的人群。压缩包共2000个文件&am…

📰

flex space-between最后一行错位?四种解法彻底讲透

先说我自己的亲身经历。之前做某个商城活动页,商品列表用的就是flex justify-content: space-between,一行四个卡片。数据接口返回的数量是动态的,有时候 7 个,有时候 9 个,结果每次刷新都能看到最后一行要么孤零零一…

📰

微博自动化运营全攻略:API 调研、开源项目盘点与 AI 可视化图表绘制实战|TaoToken 统一 Key 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Vscode配置C/C++环境喂饭教程:Windows下MinGW与体验优化设置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

PSO-CNN多输入单输出回归预测:MATLAB实现与超参数优化

简介:这份资源面向深度学习与智能优化方向的研究开发者及从事实测预测的从业人员,聚焦多特征输入、单一数值输出的回归任务,通过粒子群算法自动搜索卷积神经网络的学习率、批大小等关键超参数,以提升预测精度,可应用于…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬