news 2026/7/15 1:08:15

分库分表核心原理揭秘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分库分表核心原理揭秘

分库分表本质就是在一次 SQL 执行前,动态决定:

  • 哪个数据库连接(DataSource)

  • 哪张真实表(table_xx)

MyBatis / MyBatis-Plus 本身并不具备分库分表能力,真正做到“动态切换”的,是拦截器 + 路由规则 + ThreadLocal 上下文

在 SQL 真正发送到数据库之前,通过拦截器计算路由规则,动态替换 DataSource 和表名。

ORM 框架

https://gitee.com/laomaodu/orm-framework

分库

分库并不是运行时创建数据库连接,而是系统启动时初始化多个 DataSource,执行 SQL 时通过 AbstractRoutingDataSource 根据 ThreadLocal 中的路由 key 动态选择目标 DataSource,从对应的连接池中获取连接。

1️⃣ 多数据源准备(前提)

spring:
datasource:
db0: ...
db1: ...
db2: ...

系统启动时:

  • 所有 DataSource 都初始化

  • 放入一个 Map 中

Map<String, DataSource> dataSourceMap;

public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContext.get(); } }
  • 每次 SQL 执行前

  • Spring 会调用determineCurrentLookupKey()

  • 返回值决定使用哪个 DataSource

ThreadLocal 保存“当前库”

public class DataSourceContext { private static final ThreadLocal<String> HOLDER = new ThreadLocal<>(); public static void set(String dbKey) { HOLDER.set(dbKey); } public static String get() { return HOLDER.get(); } }

4️⃣ 在执行前设置库

String dbKey = "db" + (userId % 2);
DataSourceContext.set(dbKey);

@Bean public DataSource dataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("db0", dataSource0()); targetDataSources.put("db1", dataSource1()); DynamicDataSource ds = new DynamicDataSource(); ds.setDefaultTargetDataSource(dataSource0()); ds.setTargetDataSources(targetDataSources); return ds; }
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContext.get(); } } public class DataSourceContext { private static final ThreadLocal<String> HOLDER = new ThreadLocal<>(); public static void set(String key) { HOLDER.set(key); } public static String get() { return HOLDER.get(); } public static void clear() { HOLDER.remove(); } }
// 2. 分库规则 String dbKey = "db" + (userId % 2); DataSourceContext.set(dbKey);
MyBatis ↓ DynamicDataSource.getConnection() ↓ determineCurrentLookupKey() ↓ DataSourceContext.get() → "db1" ↓ targetDataSources.get("db1") ↓ db1DataSource.getConnection() ↓ 从 db1 的连接池拿 Connection

分表

分表是如何“动态切换表名”的

select * from order where id = ?

MyBatis 最终会生成BoundSql

String sql = boundSql.getSql();

@Intercepts({ @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) }) public class ShardingInterceptor implements Interceptor { }
long userId = getUserId(param); String table = "order_" + (userId % 16);
///方法2 SQLParser.parse(sql).replaceTable();

完整一次执行流程(串起来)

1. Mapper 方法调用 2. 分库分表拦截器触发 3. 从参数中取分片键(userId / orderId) 4. 计算: - dbKey = userId % 2 - table = order_ (userId % 16) 5. ThreadLocal 设置 dbKey 6. SQL 中 order → order_xx 7. Executor 使用正确 DataSource 8. JDBC 执行最终 SQL

为什么必须用 ThreadLocal

  • 一个请求 = 一个线程

  • 同一线程内:

    • 多次 SQL

    • 必须走同一个库

  • ThreadLocal:

    • 无侵入

    • 自动隔离

👉这是分库分表的线程级上下文基础

ShardingSphere

  • 它内置了事务传播和多数据源管理。

  • 手动实现容易错。

ShardingSphere JDBC 本质上是一个增强版 DataSource,在 SQL 执行前通过解析 SQL 和分片算法计算路由结果,动态选择目标数据源并重写 SQL,这与手写 AbstractRoutingDataSource 的原理完全一致,只是做了工程级封装。

MyBatis

ShardingSphereDataSource ←(等价于你的 DynamicDataSource)

真实 DataSource(db0 / db1)

MySQL

刚刚手写的ShardingSphere 对应
DynamicDataSourceShardingSphereDataSource
ThreadLocalSQL Hint / 内部上下文
分库算法ShardingAlgorithm
SQL replaceSQL Rewrite Engine

<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.5.0</version>
</dependency>
⚠️ 不要再引 dynamic-datasource

数据库

ds0.order_0
ds0.order_1

ds1.order_0
ds1.order_1

  1. datasource→ 配置所有物理库(分库)

  2. actual-data-nodes→ 分库分表映射关系,逻辑表对应哪些物理表

  3. database-strategy→ 分库规则

  4. table-strategy→ 分表规则

  5. sharding-algorithms→ 定义具体的分库/分表算法表达式

  6. sql-show→ 打印 SQL,观察路由结果

spring: shardingsphere: # -------------------------- # 数据源配置(分库用) # -------------------------- datasource: # 定义所有的数据源名称,用逗号分隔 names: ds0, ds1 # 数据源 ds0 的具体配置 ds0: type: com.zaxxer.hikari.HikariDataSource # 使用 HikariCP 连接池 jdbc-url: jdbc:mysql://localhost:3306/db0 # 连接的数据库地址 username: root # 数据库用户名 password: 123456 # 数据库密码 # 数据源 ds1 的具体配置 ds1: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://localhost:3306/db1 username: root password: 123456 # -------------------------- # 分片规则(分库分表策略) # -------------------------- rules: sharding: # 配置具体的分表对象 tables: order: # 表名逻辑名 # 实际物理表的数据源与表名 # ds$->{0..1} -> ds0, ds1 # order_$->{0..1} -> order_0, order_1 actual-data-nodes: ds$->{0..1}.order_$->{0..1} # 分库策略 database-strategy: standard: sharding-column: user_id # 根据哪个字段决定分库 sharding-algorithm-name: db-inline # 使用的分库算法 # 分表策略 table-strategy: standard: sharding-column: user_id sharding-algorithm-name: table-inline # 使用的分表算法 # -------------------------- # 分库和分表算法定义 # -------------------------- sharding-algorithms: # 分库算法 db-inline: type: INLINE # 内联表达式算法 props: algorithm-expression: ds${user_id % 2} # 例如 user_id=3 -> 3%2=1 -> 使用 ds1 数据源 # 分表算法 table-inline: type: INLINE props: algorithm-expression: order_${user_id % 2} # 例如 user_id=3 -> 3%2=1 -> 使用 order_1 表 # -------------------------- # ShardingSphere 全局配置 # -------------------------- props: sql-show: true # 打印最终执行的 SQL,方便调试和验证分库分表是否生效

dbKey = "db" + userId % 2; -》algorithm-expression: ds${user_id % 2}

table = "order_" + userId % 2;-》algorithm-expression: order_${user_id % 2}

ShardingSphere

  • 内部 SQL 路由引擎

  • 自动选择 ds0 / ds1

  • AST 级 SQL Rewrite

  • 支持 join / 子查询

@Select("select * from order where user_id = #{userId}")
Order select(@Param("userId") Long userId);

ShardingSphere 实际执行: select * from order_1 where user_id = ? -- DataSource = ds1
场景结论
单表百万级不需要
分表但不分库可选
分库 + 分表必须
分库 + 事务必须
多表 join必须
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 18:35:11

YOLOE文本提示功能实测,无需训练识别万物

YOLOE文本提示功能实测&#xff0c;无需训练识别万物 你有没有试过——对着一张街景照片&#xff0c;临时起意想让AI标出“外卖骑手”“共享单车”“玻璃幕墙反光区”&#xff0c;却被告知“模型没学过这个词&#xff0c;无法识别”&#xff1f;传统目标检测模型就像背熟了固定…

作者头像 李华
网站建设 2026/7/14 22:48:22

Chandra OCR部署教程:Mac M2/M3芯片适配,MLX后端运行可行性验证

Chandra OCR部署教程&#xff1a;Mac M2/M3芯片适配&#xff0c;MLX后端运行可行性验证 1. 为什么需要在Mac上跑Chandra OCR&#xff1f; 你是不是也遇到过这些场景&#xff1a; 扫描了一堆合同、试卷、手写笔记&#xff0c;想快速转成可编辑的Markdown放进知识库&#xff0…

作者头像 李华
网站建设 2026/6/30 10:51:58

CosyVoice-300M Lite一文详解:从零开始部署高效率TTS服务

CosyVoice-300M Lite一文详解&#xff1a;从零开始部署高效率TTS服务 1. 为什么你需要一个真正轻量又靠谱的TTS服务&#xff1f; 你有没有遇到过这些情况&#xff1f; 想给内部工具加个语音播报功能&#xff0c;结果发现主流TTS模型动辄几个GB&#xff0c;光模型加载就要等半…

作者头像 李华
网站建设 2026/7/11 23:14:20

一文说清AD导出Gerber在PCB制造中的作用

以下是对您提供的博文内容进行 深度润色与专业重构后的终稿 。全文已彻底去除AI痕迹,摒弃模板化结构、空洞套话和机械罗列,转而以一位深耕PCB设计与制造协同十余年的硬件工程师视角,用真实项目经验、踩坑教训、产线反馈和教学逻辑重新组织语言。文章更像是一场面对面的技术…

作者头像 李华
网站建设 2026/7/10 21:56:16

用MinerU构建智能客服知识库:非结构化文档处理实战案例

用MinerU构建智能客服知识库&#xff1a;非结构化文档处理实战案例 1. 为什么客服知识库总在“救火”&#xff1f;——一个被忽视的文档痛点 你有没有遇到过这些场景&#xff1a; 新员工入职三天&#xff0c;还在翻找去年的PDF版产品说明书&#xff0c;而最新版本藏在某个会…

作者头像 李华
网站建设 2026/7/9 8:59:58

小模型大能量!VibeThinker-1.5B在教育场景的应用

小模型大能量&#xff01;VibeThinker-1.5B在教育场景的应用 当教育科技团队还在为部署一个7B模型而反复调试显存、优化量化、权衡响应延迟时&#xff0c;一款仅1.5B参数的开源模型已悄然走进中学数学竞赛集训营和高校算法课实验室——它不生成PPT&#xff0c;不润色作文&…

作者头像 李华