☰
从单表到分库分表:MySQL + ShardingSphere 核心机制与实战避坑指南
2026/10/1 4:02:54 网站建设 项目流程

1. 项目概述:我们为什么需要对MySQL做分库分表

先说说我自己的经历。前几年做电商交易系统,订单表、流水表、商品库存表这些核心数据,单表数据量到达两三千万条之后,MySQL的B+树索引深度开始增加,写入并发一旦上来,主从延迟、锁等待、磁盘IO飙升这些问题接踵而至。最直观的表现就是,一个简单的按用户ID查询订单的接口,本来几十毫秒能返回,后来越来越慢,有时候能拖到秒级。DBA排查下来,单表数据量太大、写入瓶颈集中在单一主库上,是根因。当时摆在我面前的路无非两条:一条是继续垂直扩容,换更强的机器,但成本高而且天花板明显;另一条就是分库分表,把数据从物理上拆开,让每个库、每张表的数据量都降下来。我选择了后者,技术选型最终定了MySQL加ShardingSphere这套组合。

ShardingSphere是目前国内用得最广的分库分表中间件之一,它不是一个单点组件,而是一套生态,包含JDBC、Proxy两种接入形态,以及Sidecar(这个在生产里用得少)。它解决的问题很聚焦:把分库分表之后的路由、改写、归并、分布式主键、分布式事务这些复杂逻辑从业务代码里抽出来,让开发人员仍然像操作单库单表一样写SQL,剩下的交给中间件去处理。这句话听起来轻巧,但实际落地时,分片键怎么选、分片算法怎么定、扩容怎么设计、跨库查询怎么办,每一步都有不少坑。这篇文章我会结合自己实际项目的落地过程,把这些核心细节和判断逻辑拆开讲清楚。

这篇内容适合谁看?如果你正在做系统设计,发现单表数据量增长快,MySQL性能开始吃紧,或者你面试被问到分库分表但只是背过八股文,想了解真实落地过程,那这篇文章都能给你一些参考。我会先讲整体设计思路,再深入配置细节,最后把实操过程中踩过的坑和排查方法整理成清单。

2. 分库分表整体设计思路:动刀之前先想清楚四件事

2.1 判断分库分表的触发条件

很多人一看到数据量超过几百万就喊着要分库分表,其实这是个误区。MySQL单表能扛的数据量没有绝对上限,但有几个指标可以作为预警信号:单表行数超过2000万到5000万区间,索引层数增加导致的随机IO成本上升;写入TPS超过单库瓶颈,比如单库写入超过3000到5000 TPS时,磁盘、日志、锁竞争都会成为瓶颈;单表容量超过磁盘合理规划,导致备份和恢复时间过长;业务字段过多,单表字段超过几十个,部分大字段频繁查询拖累整体性能。

我这里补充一个相对可操作的经验判断法。你可以通过监控看三个指标:磁盘IO的utilization是否长期超过70%,慢查询数量是否随着数据量增长呈超线性增长,主从复制延迟是否越来越难追平。如果这三个指标中有两个持续恶化,并且排除了索引缺失、SQL写法不合理、硬件配置过低这些基础因素,那才真正到了需要考虑分库分表的阶段。

需要注意的是,分库分表是系统复杂度的一次显著跃升,它引入的问题比它解决的问题往往更多。如果当前数据量和并发还在可控制的范围内,优先使用缓存、读写分离、冷热数据归档、索引优化这些更低成本的方案。我自己见过不少团队,数据量才几百万就兴冲冲上了分库分表,结果分布式事务、跨节点聚合查询、扩容迁移这些问题把团队拖得筋疲力尽。动手之前,先问自己一句:分库分表真的能解决我的核心瓶颈吗?

2.2 垂直拆分与水平拆分,两种思路如何取舍

分库分表在方向上分成垂直拆分和水平拆分两大类,这个区分很多文章讲得含糊,我在这里把它们的适用场景说透。

垂直分库指的是按业务域拆分,比如把用户相关表放一个库,订单相关表放一个库,商品相关表放一个库。这种拆法本质上做的是服务化拆分,好处是不同业务之间的IO、CPU资源物理隔离,互不干扰,也方便独立扩容。它解决的是单一数据库承载过多业务、混合负载互相拖累的问题。

垂直分表指的是按字段拆分,把一张宽表拆成多张窄表。比如商品表有基础信息字段、详情描述字段、库存字段、价格字段,其中详情描述是典型的大字段,每次查询和修改都会拖累整张表的性能。拆成商品主表和商品详情表之后,高频查询走主表,低频大字段查询单独走详情表。这种拆法相对直观,但要注意主键的一致性维护,以及事务范围的收窄。

水平分库分表,这才是大部分人说的狭义分库分表。水平分表是把同一张表的数据按一定规则分散到多张结构相同的表中,比如订单表按用户ID的哈希值分成32张表;水平分库则是把这些表再分布到多个数据库中。实际项目中,水平分库加水平分表往往是组合使用的,例如8个库乘以16张表,总共128个物理表,数据分散度足够高,单个分片的数据量也得到控制。

我的建议是:先做垂直拆分,把业务域边界理清楚;再做水平拆分,解决单表数据量和并发吞吐的瓶颈。不要一上来就跨过垂直拆分直接做水平拆分,那样会导致业务边界混乱,后续维护成本极高。

2.3 ShardingSphere-JDBC与Proxy两种形态怎么选

ShardingSphere的两种接入形态,官网叫ShardingSphere-JDBC和ShardingSphere-Proxy,两者的核心差异在于运行位置和接入方式。

ShardingSphere-JDBC是以JAR包形式嵌入业务应用中的,应用直连底层数据库,中间件只是应用内的一个数据源层。优势是性能损耗低,因为不需要经过额外的网络代理,SQL分发逻辑在本地完成;部署简单,应用打包后直接运行,不需要单独维护中间件服务;与Spring Boot、MyBatis等框架集成非常顺手。缺点是只支持Java应用,运维层面无法做到对业务透明,每接入一个业务应用都要配置一次。

ShardingSphere-Proxy是一个独立的服务进程,对外暴露MySQL协议端口,业务应用把它当作一个普通的MySQL数据库来连接。这样带来的好处是语言无关,任何语言的MySQL客户端都能接入;对业务侵入极低,不需要修改应用代码;适合多系统统一接入分库分表能力。缺点是性能上多了一层网络转发,单跳延迟大约增加零点几毫秒到一两毫秒,并且需要运维单独部署和监控这个Proxy服务。

实际选型时,我大致会按这个逻辑判断:项目是Java技术栈,并且只涉及少数几个业务系统,优先选ShardingSphere-JDBC,性能更好,运维成本低;项目由多个技术栈的系统组成,比如既有Java又有Go、Python,并且希望分库分表对业务完全透明,选Proxy更合适;还要考虑团队是否有独立的中间件运维能力。

我自己的项目当时是Java单体加微服务拆分的中途阶段,核心业务都是Java,所以直接上了ShardingSphere-JDBC,配合Spring Boot的自动配置,接入过程非常顺滑。

3. 核心机制拆解:分片键、分片算法与分布式主键

3.1 分片键的选择,是分库分表成功与否的关键

分片键是决定一条SQL请求路由到哪个分片的依据,它的选择直接影响系统的路由均衡度、查询效率和未来的扩展能力。我看到过太多案例,因为分片键选得不好,导致部分分片数据量严重倾斜,部分分片几乎空转。

分片键的挑选要遵循几个原则。第一,它必须是业务查询中最常见的等值查询条件。比如订单表,用户查询自己的订单列表是最核心的请求,那么用户ID就是天然的分片候选。第二,分片键的值要具备足够的离散性。像状态字段这种取值范围很窄、分布不均匀的字段,绝对不能当分片键。第三,分片键本身最好是个稳定不变的字段。比如手机号、用户ID、订单ID,这些业务标识基本一生不变;而如果选了一个会随业务状态变化的字段,比如用户的归属城市,一旦用户迁移城市,数据迁移的逻辑会让人崩溃。

一个很典型的矛盾是:订单表按订单ID分片,用户查询订单列表时,如果没有带上订单ID作为查询条件,路由就会变成全分片扫描。这是一个非常高频的问题。解决思路一般有两种:一是订单表除了按订单ID分片,还建立用户ID到订单ID的映射关系;二是用用户ID作为分片键,订单创建时以用户ID维度写入,这样用户查询订单天然命中单个分片,代价是订单跨用户的全局查询能力变弱,比如后台运营需要按订单号精确查询时,得先知道订单号的归属用户。实际项目中,我见过比较稳妥的做法是订单表按用户ID分片,同时额外再建一张订单号与用户ID的映射索引表,或者直接引入Elasticsearch承接运营侧的多维查询。

还有一个容易忽略的点:分片键要能支持范围查询场景的裁剪。比如订单表按创建时间分片,时间字段天然具备范围裁剪能力,但要警惕时间维度上的数据倾斜,比如双十一当天的订单量可能超过平时一个月的量。所以实际项目里按时间分片往往不是单独使用,而是配合用户ID或者订单ID做二级分片。

3.2 分片算法的选择逻辑与参数计算

ShardingSphere支持多种分片算法,但从底层原理看,大致可以分成三类:取模与哈希、范围分片、时间分片。下面我把它们放在一个表格里对比。

分片算法核心原理优点缺点典型场景
取模/哈希对分片键做哈希后对分片总数取模数据分布均匀,实现简单分片数量固定,扩容需要数据迁移用户ID、订单ID这类等值查询密集的场景
范围分片按分片键的数值区间划分,如1到1000万进表0支持高效的范围扫描,扩容时可以按区间新增分片数据倾斜风险高,热点区间可能压垮单个分片订单按金额或序号区间分片
时间分片按年、月、日等时间粒度分片归档简单,冷热数据天然分离热点集中在新分片,冷分片闲置日志、交易流水等时间序列数据
一致性哈希将分片键哈希到哈希环上,只影响环上相邻节点扩容时数据迁移量大幅降低实现复杂,部分版本需要自定义数据规模增长不确定、扩容频繁的系统

在ShardingSphere里,取模算法最常用也最容易理解,它的计算过程就是hash(分片键值) % 分片总数,得到一个索引值,这个索引对应的物理表就是数据要写入和读取的位置。我举个例子,假设我们规划了4个分片,用户ID为10086的请求,通过哈希函数得到某个整数值,对4取模后得到的结果可能是2,那么这次请求就会定向路由到分片2对应的数据节点上。

这里有个很重要的问题:分片总数到底怎么定。很多团队拍脑袋定了一个数,后面扩容时吃尽苦头。我的经验是综合考虑三个因素。第一是数据增长预期,预估三年内的数据总量,从而确定分片数量,公式大致是:分片数 = 三年预估总数据量 除以 单分片的目标数据量上限。第二是并发吞吐目标,单分片能抗的写入TPS大约是1000到3000(视硬件情况而定),用目标总TPS除以单分片能力可以推出分片数下限。第三是扩容策略,如果一开始选择取模分片,那么分片数尽量选取2的幂次方,比如4、8、16、32,这样将来借助一致性哈希或者镜像迁移,处理起来会稍微灵活一点。

3.3 分布式主键:雪花算法为何成为首选

分库分表之后,数据库自增主键马上就失效了,因为多个分片各自生成自增ID必然冲突。分布式主键的要求很简单:全局唯一,趋势递增,高性能生成。

ShardingSphere默认提供了雪花算法,这也是目前绝大多数分布式系统的首选。雪花算法生成的ID是一个64位Long型整数,它的组成结构是:1位符号位(固定0),41位毫秒时间戳,10位工作机器ID,12位序列号。换算一下,同一毫秒内同一台机器上可以生成4096个不重复ID,足够支持高并发场景。它还具备时间上的趋势递增特性,对数据库B+树索引的写入非常友好,可以减少页分裂。

在ShardingSphere中配置雪花算法时,有几个参数需要关注。工作机器ID即workerId,如果配置不当,比如多个应用实例使用了相同的workerId,就会在极端情况下生成重复ID。ShardingSphere-JDBC默认会根据应用进程的IP与端口自动生成一个workerId,但在容器化部署环境下,每个Pod的IP与端口可能在启动时相同,容易冲突。我踩过这个坑,后来统一在配置里显式指定了每个实例的workerId,并配合ZooKeeper或者Nacos做一个workerId的动态分配。

分布式ID的另一个替代方案是号段模式,即从数据库里申请一批ID到本地内存,应用在本地依次分配。它的优势是ID数值规律性强,方便DBA排查问题;劣势是依赖额外的发号服务,且存在号段耗尽时短暂阻塞的风险。对比下来,在没有强制严格递增需求的前提下,雪花算法是性价比最高的选择。

4. 实操配置:基于ShardingSphere-JDBC落地分库分表

4.1 Maven依赖与配置文件的基础搭建

我这里以Spring Boot项目为例,把核心配置拆解一遍。首先在pom.xml中加入ShardingSphere-JDBC的依赖,要注意版本与Spring Boot的兼容性。目前的版本体系中,ShardingSphere-JDBC已经独立成了单独的artifact,artifactId是sharding-jdbc-spring-boot-starter,我项目里用的是4.1.1版本,配合Spring Boot 2.x,稳定可用。如果是更高版本的ShardingSphere,则使用shardingsphere-jdbc-spring-boot-starter。

依赖引入之后,数据源这块会有一个本质变化:原来我们配置的是直接指向MySQL的DataSource,现在要配置的是一个逻辑数据源,由ShardingSphere接管。这意味着业务代码里的数据源注入不需要改,因为ShardingSphere的starter会自动注册一个Primary DataSource,但这个DataSource背后是由多个物理数据源组成的。

我这里给一个最基础的两库四表配置示例:

spring: shardingsphere: datasource: names: ds0, ds1 ds0: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://192.168.1.10:3306/order_db_0?useSSL=false&serverTimezone=Asia/Shanghai username: root password: xxxxxx ds1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://192.168.1.11:3306/order_db_1?useSSL=false&serverTimezone=Asia/Shanghai username: root password: xxxxxx sharding: tables: t_order: actualDataNodes: ds$->{0..1}.t_order_$->{0..3} tableStrategy: inline: shardingColumn: user_id algorithmExpression: t_order_${user_id % 4} databaseStrategy: inline: shardingColumn: user_id algorithmExpression: ds${user_id % 2} defaultKeyGenerator: type: SNOWFLAKE column: order_id

这个配置乍看有点绕,我来逐行翻译一下。actualDataNodes指的就是t_order这张逻辑表在物理环境中的真实分布,它分布在ds0和ds1两个数据源上,每个数据源里又各建了t_order_0到t_order_3四张表,总共8张物理表。tableStrategy定义了表级别的分片策略,当SQL传入的user_id为5时,user_id 对4取模是1,于是路由到t_order_1。databaseStrategy定义了库级别的分片策略,user_id 对2取模的结果决定走ds0还是ds1。两个策略层级分明,先确定库,再确定表。

有一个特别容易搞错的地方:库和表的分片策略可以不一样。比如我上面的写法,库用的是user_id % 2,表用的是user_id % 4,这两者必须配合好,否则会出现一些分片组合是空的,而另一些分片组合数据爆满。最稳妥的办法是,让库和表的分片键一致,并且分片数量呈倍数关系,比如一个user_id为5的数据,先落到ds1,再落到t_order_1,多次请求的结果都是确定的,不会走错位置。

4.2 绑定表和广播表的配置

在真实的订单场景里,t_order是主表,t_order_detail是明细表。它们往往有父子关系,需要通过order_id关联查询。如果不做任何额外配置,两个表各自按分片路由,很可能主表落在ds0,明细表落在ds1,跨库join直接变成本地join失败或者效率极低的跨节点笛卡尔积。

ShardingSphere提供了绑定表机制来解决这个问题。绑定表意味着两张逻辑表在路由时必须保持相同的分片结果。配置非常简单:

spring: shardingsphere: sharding: bindingTables: - t_order, t_order_detail

加上这个配置之后,查询t_order join t_order_detail时,中间件会把它们的路由尽量收缩到同一分片内,join操作在本地完成,避免了跨库网络传输。这里有一个隐含前提:t_order和t_order_detail使用的分片键以及分片算法必须一致,否则绑定表机制不会生效。所以设计表结构时,关联表必须沿用主表的分片键和分片算法。

广播表则是面向字典表这类数据量小但几乎所有分片查询都可能用到的表。典型的像订单状态枚举表、商品类目表。广播表配置后,ShardingSphere会自动将数据同步到所有分片中,每次写入要么全量写,要么拒绝写入。查询时从任一随机的本地分片读取,省去了跨库查询。配置方式是:

spring: shardingsphere: sharding: broadcastTables: - t_dict_status

要注意广播表不适合高频写入,因为每次写入都会放大到所有分片,写入放大倍数等于分片数量。如果字典数据频繁更新,建议不要把广播表放在数据库里,而是走缓存配置中心。

4.3 分页查询和排序的归并与归并陷阱

分库分表之后,SQL层面的limit offset语义会发生微妙变化。比如从单表执行limit 10, 20,只需要单纯跳过10条然后取20条。但在分片环境中,每个分片都必须执行limit 10, 20,取出各自的20条结果,然后由ShardingSphere在内存里做归并排序,再整体跳掉10条取出20条。这个逻辑本身没错,但它的代价是,当offset非常深时,比如limit 100000, 20,每个分片都要取出100020条数据,然后归并后再丢弃100000条。这意味着翻页越深,数据库和中间件的内存消耗越大,性能呈线性恶化。

实践中最推荐的方案是采用游标分页,也就是以排序字段的值作为过滤条件。比如客户端传来lastOrderId和lastCreateTime,查询条件写成where create_time < 上一个页的create_time,或者order_id < 上一个页的order_id,配合倒序排序,这样每个分片只需要取一页数据,性能稳定得多。如果业务上非要支持跳页,我建议把全量ID列表提前查询出来或者走搜索引擎分页,让数据库只承担等值查询和深度受限的分页。

ShardingSphere归并还有一个容易被忽视的坑,就是跨分片的count查询结果。count()会被下推到各分片分别统计,然后中间件求和,这个逻辑是正确的。但如果没有带上分片键作为过滤条件,比如查全表的count(),那么每个分片都得扫全量数据。消耗巨大,尤其是分片多、数据量大的时候。所以一定要避免无分片键的全表聚合查询,如果实在躲不开,建议提前在业务侧维护一些汇总统计表。

5. 常见生产问题与排查技巧实录

5.1 分布式事务失败,订单写入不一致怎么办

分库分表之后,一个业务操作很可能涉及多个分片的写入,比如创建订单同时扣减库存,订单落在一个分片,库存可能落在另一个分片。此时单库事务已经管不到这两个分片了,必须引入分布式事务。

ShardingSphere本身提供了XA分布式事务能力,基于Atomikos或者Narayana实现,可以保证强一致性。XA事务的使用方式很简单,只需要在业务方法上加上@ShardingTransactionType(value = TransactionType.XA),配合常规的@Transactional,中间件就会自动协调所有分片的事务提交或回滚。这套方案在分片数量不多、事务参与方都是关系型数据库时表现尚可,但要注意它对数据库锁的持有时间更长,高并发下容易出现分布式锁等待和死锁。

如果业务允许最终一致性,我更推荐柔性事务方案,比如自研或者引入Seata的AT模式。AT模式通过undo log记录数据变更前镜像,事务提交时先执行业务SQL,再异步删除undo log,如果发生异常,通过undo log逆向补偿。它把分布式事务从两阶段提交的强一致模型切换到了补偿模型,并发性能更好。但代价是需要额外部署Seata服务端,且对SQL的解析有要求。

这里必须说实话,分布式事务永远是分库分表最痛的一环。我自己的经验是:尽量从业务设计上规避,把需要强一致的操作放到同一个分片内,比如一个用户相关的所有业务数据都通过userId分片到同一库,很多事务问题就不存在了。跨分片的强一致事务应当被当成一种极端情况,而不是默认手段。

5.2 跨分片JOIN和子查询带来的性能雪崩

分库分表之后,跨分片JOIN是性能杀手之一。ShardingSphere虽然支持将JOIN下推到各分片执行,然后内存归并,但一旦关联的数据量变大,网络传输和内存消耗会迅速膨胀。绑定表可以解决一部分同分片JOIN问题,但更普遍的情况是:分库分表之后,原本一条关联查询被拆成了多条查询,业务方在应用层做组装。

我在项目里积累了几个实用策略。第一个策略是数据冗余,把关联查询中高频使用的字段直接冗余到主表里。比如订单列表页需要展示用户昵称,那就把昵称冗余到订单表,省去join用户表。第二个策略是字段冗余配合异步更新,当昵称变更时通过MQ发消息,异步更新订单表里的冗余字段。第三个策略是引入搜索引擎或宽表,比如用Elasticsearch或者ClickHouse承载多维分析查询,MySQL只负责事务性强的增删改查。

子查询的问题同样值得警惕。SQL里如果出现了in子查询,并且子查询的结果集依赖于多个分片,中间件处理起来非常吃力。我建议能改写就改写,把子查询的结果先在应用层算出来,再用in条件去查询主表。注意,如果in列表包含的分片键值恰好能确定路由,那还好说;如果in列表不能确定分片键,同样要面临全分片扫描。

5.3 数据倾斜与热点分片问题

分片算法的目标是数据均匀,但现实世界的数据往往并不均匀。拿订单表按用户ID分片举例,头部大客户的订单量可能占全表订单的20%以上,这些大客户的请求全打在同一个分片,会造成热点分片。反观一些小散用户的分片,负载很低。

一种缓解手段是使用一致性哈希,它能让热点数据在哈希环上分散到多个节点,避免单一分片过热。但这种方案在ShardingSphere中需要自定义分片算法,复杂度更高。另一种更常用的手段是给热点用户加盐,就是在分片键的末尾拼接一个随机后缀,让原本集中在同一分片的数据分散到多个分片。加盐之后,查询时必须知道盐值才能定位,所以需要一个映射关系来维护哪些用户加了哪些盐。

我印象比较深的一次线上事故,是双十一大促期间,某个头部商家的订单表写入量是平时的20倍,导致对应分片的主库负载达到90%以上,眼看着就要拖垮整个集群。后来紧急对该商家的订单数据做了加盐处理,把写入压力分散到8个分片,才算稳住。从那以后,凡是涉及促销活动,我都会提前做压测和数据倾斜预估,尽早识别热点业务。

5.4 扩容方案设计与数据迁移

取模分片最大的隐性成本就是扩容。假设原来4个分片,数据量涨上来要扩到8个分片。直接改分片数为8是不行的,因为原来的hash(userId) % 4结果和hash(userId) % 8结果大概率不一致,老数据全部路由错位,必须做全量数据迁移和重新路由。

我见过最省事的扩容方案是按时间维度分片,比如按月分表,扩容时新增一个月份的表即可,老表数据不动。但这只适用于日志、流水这类时间序列数据。对业务主表这种高频查询的表,按月分表并不现实。更通用的方案有两个:第一个是双写迁移,在扩容窗口期内,新数据同时写入旧分片和新分片,同时用迁移工具将历史数据从旧分片搬迁到新分片,校验一致后切换路由规则,这需要比较完善的迁移平台。第二个是翻倍扩容,从4个分片扩到8个分片时,利用取模运算的数学特性,让部分分片的数据整体平移,减少重新哈希的计算量。比如旧数据在分片0的分片键hash值为0、4、8、12,扩容后这些数据分别落到新分片0和4,可以通过复制加清理的方式完成迁移。

这里要强调一个观念:数据迁移永远比预想的要复杂,务必在系统设计阶段就预留扩容方案。我建议在新系统设计时,优先考虑一致性哈希或者基于目录的映射方式,避免硬编码分片总数量。ShardingSphere支持自定义分片算法,有能力的团队可以实现目录映射分片,把分片键和分片节点的对应关系存储到配置中心,扩容时只需调整映射关系,数据迁移量降到最低。

5.5 配置和SQL常见的坑

实操中经常遇到几个问题,我整理成一张速查表,都是踩过之后才知道的坑。

问题现象根因排查与解决
应用启动时报找不到DataSourceShardingSphere配置没有生效,或者数据源名称不匹配检查spring.shardingsphere.datasource.names里配置的名称是否与后面的数据源key一致,注意拼写和大小写
分片路由结果不符合预期分片算法表达式写错,或者分片键与表结构中的列名不一致在配置中输出路由日志,开启sql打印功能,逐条查看路由结果
批量插入时SQL执行报错批量插入的shardingColumn在配置中没有正确处理,或者分片键值为null检查批量插入时是否包含分片键列,且值不能为空;必要时对批量插入做预处理,按分片键分组后分批执行
跨分片事务回滚不完整事务管理器未正确集成检查是否引入sharding-transaction-xa-core依赖,并且确认@ShardingTransactionType注解没有被忽略
字段值含有特殊字符导致路由失败分片键的值是字符串,哈希不稳定统一使用一致性哈希算法,或者在自定义分片算法中规范字符串哈希方式
主从模式下读写分离失效未配置主从规则,所有请求都走了主库检查是否配置了replicaQuery或者masterSlave规则;在current版本里,读写分离与分片规则是分开配置的

排查这些问题的通用手段是开启SQL日志。ShardingSphere支持通过配置spring.shardingsphere.props.sql.show=true来打印实际执行的SQL和路由结果。每次改动分片规则之后,我都会先开启这个开关,跑几条代表性的SQL,确认路由目标符合预期后再关闭日志,避免生产环境日志量过大。

6. 写在最后的经验心得

从单表到分库分表,这个过程的本质不是技术炫技,而是对数据体量和业务模式的重新审视。很多团队总觉得分库分表是终极方案,上了之后就能一劳永逸。但真实情况是,分库分表把原本集中在一处的复杂度,分散到了路由、事务、查询、运维的各个角落,它需要团队具备更高的架构能力和运维能力。如果当前系统还在增长早期,我诚恳建议优先用好MySQL本身的优化手段,分库分表是手段,不是目的。

最后分享一个我自己的实操习惯:在项目初期就为分库分表设计一些前置抽象,比如所有DAO层查询都必须显式携带分片键,所有核心表都预留一个分布式主键生成器,所有关联查询都提前做数据冗余设计。哪怕当前还没到分库分表的临界点,这些约束也能让未来的改造过程顺滑很多。千万别等到数据量压垮数据库的那一天,再拉着全组人连夜加班做迁移,那滋味真的不好受。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询