news 2026/4/23 9:22:54

分库分表数据源ShardingSphereDataSource的Connection元数据误用问题分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分库分表数据源ShardingSphereDataSource的Connection元数据误用问题分析

背景

对于分库分表应用来说,使用org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource是一个不错的解决方案,你可以通过配置文件编写分库分表规则,从而在编码时透明地使用分表(当然,路由规则的相关字段还是要传的

但是,在一些场景中是需要绕过mybatis直接做一些操作的,特别是和数据库元数据相关的操作(包括表的结构变更)。

比如我遇到的场景:先查询各个分库中有哪些前缀为table_的表,并给这些表加一列col_x

我结合现有代码和大语言模型,先写了一版,线下运行良好,但是线上的某些分库死活找不到对应的分表,没法进行后续的处理。这个问题查了很久,昨天终于解决了,因此分享出来。

存在问题的代码

@Componet public class TableAlterHandler { @Resource private ShardingSphereDataSource dataSource; public List<String> findTablesByPrefix(String prefix, String physicalSchemaName) { if (StringUtils.isBlank(prefix) || StringUtils.isBlank(physicalSchemaName)) { throw new RuntimeException("分表前缀或分库名为空"); } List<String> tableNames = Lists.newArrayList(); try (HintManager hintManager = HintManager.getInstance(); Connection conn = dataSource.getConnection()) { hintManager.setDataSourceName(DBUtil.queryLogicalSchemaName(physicalSchemaName)); DatabaseMetaData metaData = conn.getMetaData(); try (ResultSet rs = metaData.getTables(physicalSchemaName, null, prefix + "%", new String[] {"TABLE"})) { while (rs.next()) { String tableName = rs.getString("TABLE_NAME"); tableNames.add(tableName); } } } catch (SQLException e) { throw new RuntimeException("处理大结果集失败", e); } return tableNames; } }

逻辑库和物理库

在分析问题之前,首先要明确两个概念:物理库名physicalSchemaName和逻辑库名logicalSchemaName,如果用错了,可能会让你没办法发现后续问题的本质原因。上面的代码,hintManager必须用逻辑库名,而metaData.getTables必须用物理库名。

所谓物理库和逻辑库,可以看作是我定义的概念。正如其名,物理库名就是你jdbcUrl里的库名,比如一个典型的阿里云Mysql的JDBC链接jdbc:mysql://``rm-bpxxxx.mysql.rds.aliyuncs.com/bizcenter_1?useSSL=false&autoReconnect=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

其中的bizcenter_1就是物理库名。这个名称也会在MySql元数据中出现,比如

select * from information_schema.tables;

可以看到这个库的所有表,而TABLE_SCHEMA列就是物理库名。

而逻辑库,则是ShardingSphereDataSource对物理库的映射,在application的配置文件(properties或yml)里,spring.shardingsphere.datasource配置下,你需要配置逻辑库名以及对应的JDBC链接。

你可以将多个逻辑库配置为同一个物理库,这是一种实践方式,可以用于后续的扩容。

问题现象

线下的三个逻辑库分别对应一个物理库,而这三个物理库在同一个阿里云RDS实例上,可以找到每个库对应的表。

而线上的仍然是三个逻辑库对应各自的物理库,每个物理库在不同的阿里云RDS实例上,会出现有时候能找到某个库对应的表,而另外两个库一个表都找不到的情况。

排查

遇到问题后,百思不得其解,因为线下环境一切正常,线上却总能复现问题。由于线上环境管控比较严,既不能远程debug,又不能直连线上库,很难定位原因。

第一阶段排查,我反复确认了上面代码中需要传数据库名的地方到底是逻辑库还是物理库。中间某个版本的确搞错了,但是为何在写错的前提下还能运行,没有做记录。

然后,我删掉了connection.setAutoCommit(false)resultSet.setFetchSize(batchSize)这样的用于降低每次查询元数据结果数量的代码,也没效果。

最后,我把代码移到了另一个连接同样数据库的应用中,因为那个应用有我之前类似的代码。移过去以后倒是歪打正着地解决了。

第二阶段的排查,是在一段时间后,我在原先的应用中开发新的功能,对原先代码进行改动,自以为修复好了,但是上线后发现还是和之前一样。

分析

线下线上最大的区别就是线下几个库是同一个MySql实例,而线上分属三个。我的代码里,疑点最大的是查询元数据metaData.getTables()这段。

好巧不巧,在我排查的第一阶段和第二阶段中间,我写了一个迁移表的功能,完全新写了查询表名的代码,并且为了不再犯物理库和逻辑库搞混的错误,特别地写了对应的工具类:

/** * 数据源持有组件,便于应用直接访问数据源 * */ @Component public class DataSourceHolder { @Resource protected ShardingSphereDataSource dataSource; /** 物理库名(jdbc链接里的库名)和数据源的关系 */ private Map<String, HikariDataSource> hikariDataSourceMap; /** 逻辑库名-物理库名关系 多个逻辑库可能对应同一个物理库 */ private Map<String, String> dsNameMap; /** * 通过物理库名获取ds * * @param physicalSchemaName * @return */ public HikariDataSource getDataSourceByPhysicalSchemaName(String physicalSchemaName) { return hikariDataSourceMap.get(physicalSchemaName); } /** * 通过逻辑库名获取对应物理库名 * * @param dsName * @return */ public String getPhysicalSchemaName(String dsName) { return dsNameMap.get(dsName); } /** * 通过逻辑库名获取ds * * @param logicalSchemaName * @return */ public HikariDataSource getDataSourceByLogicalSchemaName(String logicalSchemaName) { String physicalSchemaName = getPhysicalSchemaName(logicalSchemaName); if (StringUtils.isBlank(physicalSchemaName)) { throw new RuntimeException("逻辑库名找不到对应物理库, logicalSchemaName=" + logicalSchemaName); } return hikariDataSourceMap.get(physicalSchemaName); } @PostConstruct public void initHikariDataSourceMap() { dsNameMap = Maps.newHashMap(); hikariDataSourceMap = Maps.newHashMap(); Map<String, DataSource> dataSourceMap = dataSource.getContextManager().getDataSourceMap(dataSource.getSchemaName()); dataSourceMap.forEach( (dsName, ds) -> { HikariDataSource hds = (HikariDataSource) ds; try (Connection connection = hds.getConnection(); ) { hikariDataSourceMap.put(connection.getCatalog(), hds); dsNameMap.put(dsName, connection.getCatalog()); } catch (SQLException e) { throw new RuntimeException("组装数据源map失败", e); } }); } }

对应地,获取数据库Connection的方法是:

HikariDataSource hikariDataSource = dataSourceHolder.getDataSourceByPhysicalSchemaName(dsName); Connection connection = hikariDataSource.getConnection();

并且也不再使用HintManager指定逻辑库。

联想到线上线下MySql实例的差异,我猜测是因为:

线下三个库是同一个MySql实例,那么元数据information_schema.tables是一样的,在哪个库都能查到对应表。

线上则是不同的实例,直接使用ShardingSphereDataSource对应Connection的元数据,并不总是预期的库。

这个猜测原因,也在之前第一阶段的排查吻合,能正常工作的代码所在应用,获取数据源的方式实际是

Map<String, DataSource> allDataSource = shardingSphereDataSource.getContextManager().getDataSourceMap("logic_db"); Connection connection = allDataSouce.get(logicalSchemaName).getConnection();

虽然它也用了HintManger,但我认为是没有意义的。

修复

综合以上的分析,最终的修复代码如下

List<String> tableNames = Lists.newArrayList(); try (Connection conn = dataSourceHolder.getDataSourceByPhysicalSchemaName(physicalSchemaName).getConnection()) { DatabaseMetaData metaData = conn.getMetaData(); try (ResultSet rs = metaData.getTables(physicalSchemaName, null, prefix + "%", new String[] {"TABLE"})) { while (rs.next()) { String tableName = rs.getString("TABLE_NAME"); tableNames.add(tableName); } } } catch (SQLException e) { throw new RuntimeException("处理大结果集失败", e); } return tableNames;

这次再部署到生产环境,运行符合预期。

可见,当你需要实际分库对应的元数据时,不要用shardingSphereDataSource,而是应该用它关联具体分库的dataSource,也即shardingSphereDataSource.getContextManager().getDataSourceMap(),再用这个分库dataSource获取元数据。

否则,线下线上不同的MySql实例配置,会导致不同的现象,难以排查真正的原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 7:52:13

30万字312道高频Java面试题52道场景题总结(附答案)

最近有很多伙伴问我&#xff0c;有什么方法能够快速提升自己&#xff0c;通过阿里、腾讯、字节跳动、京东等互联网大厂的面试&#xff0c;我觉得短时间提升自己最快的手段就是背面试题&#xff1b;最近将2年拍成视频面试题文档总结成一份30万字的300道Java高频面试题64道场景题…

作者头像 李华
网站建设 2026/4/22 9:40:22

面试官:谈谈过滤器和拦截器的区别?

一、拦截器和过滤器的区别 1、拦截器(Interceptor)只对action请求起作用 即对外访问路径 而过滤器(Filter)则可以对几乎所有的请求都能起作用 包括css js等资源文件 2、拦截器(Interceptor)是在Servlet和Controller控制器之间执行 而过滤器(Filter)是在请求进入Tomcat容器之…

作者头像 李华
网站建设 2026/4/22 21:53:57

YOLOv9-e-Pose发布:人体姿态估计同样依赖GPU加速

YOLOv9-e-Pose发布&#xff1a;人体姿态估计同样依赖GPU加速 在智能制造车间的监控大屏上&#xff0c;一个工人突然弯腰的动作被系统瞬间捕捉——不是简单的“有人移动”&#xff0c;而是精确识别出他正在执行标准作业流程中的“拾取零件”步骤。与此同时&#xff0c;在千里之外…

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

YOLOv8m性能实测:在RTX 4090上达到150FPS

YOLOv8m性能实测&#xff1a;在RTX 4090上达到150FPS在智能制造工厂的质检线上&#xff0c;传送带以每分钟300件的速度飞速运转。传统视觉系统还在处理上一帧图像时&#xff0c;新的工件已经滑过摄像头视野——这种“看得见却来不及检”的窘境&#xff0c;曾是自动化升级的最大…

作者头像 李华
网站建设 2026/4/23 7:52:43

2025最新!自考党必看9个AI论文工具测评,哪款最靠谱?

2025最新&#xff01;自考党必看9个AI论文工具测评&#xff0c;哪款最靠谱&#xff1f; 2025年自考论文工具测评&#xff1a;为何需要一份权威榜单&#xff1f; 随着人工智能技术的不断进步&#xff0c;AI论文工具逐渐成为自考学生提升写作效率、优化论文结构的重要辅助。然而&…

作者头像 李华
网站建设 2026/4/23 9:20:38

学长亲荐10个AI论文工具,助你轻松搞定本科毕业论文!

学长亲荐10个AI论文工具&#xff0c;助你轻松搞定本科毕业论文&#xff01; 论文写作的救星&#xff0c;AI 工具如何改变你的学术之路 在本科毕业论文的撰写过程中&#xff0c;许多同学都会面临时间紧、任务重、思路混乱等难题。而随着 AI 技术的不断成熟&#xff0c;越来越多的…

作者头像 李华