“阅读原文”,在网页里查看,体验更佳。 温馨提示:在微信关注 Python猫,发送一个数字“”,即可领取 折优惠券,订阅专栏可享 ...

📅 发布时间:2026/7/14 17:49:11 👁️ 浏览次数:
“阅读原文”,在网页里查看,体验更佳。 温馨提示:在微信关注 Python猫,发送一个数字“”,即可领取  折优惠券,订阅专栏可享  ...
谔涸骨刀但是在一些场景中是需要绕过mybatis直接做一些操作的特别是和数据库元数据相关的操作包括表的结构变更。比如我遇到的场景先查询各个分库中有哪些前缀为table_的表并给这些表加一列col_x。我结合现有代码和大语言模型先写了一版线下运行良好但是线上的某些分库死活找不到对应的分表没法进行后续的处理。这个问题查了很久昨天终于解决了因此分享出来。存在问题的代码Componetpublic class TableAlterHandler {Resource private ShardingSphereDataSource dataSource;public List findTablesByPrefix(String prefix, String physicalSchemaName) {if (StringUtils.isBlank(prefix) || StringUtils.isBlank(physicalSchemaName)) {throw new RuntimeException(分表前缀或分库名为空);}List 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?useSSLfalseautoReconnecttruecharacterEncodingutf8serverTimezoneAsia/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()这段。好巧不巧在我排查的第一阶段和第二阶段中间我写了一个迁移表的功能完全新写了查询表名的代码并且为了不再犯物理库和逻辑库搞混的错误特别地写了对应的工具类/*** 数据源持有组件便于应用直接访问数据源**/Componentpublic class DataSourceHolder {Resource protected ShardingSphereDataSource dataSource;/** 物理库名(jdbc链接里的库名)和数据源的关系 */private Map hikariDataSourceMap;/** 逻辑库名-物理库名关系 多个逻辑库可能对应同一个物理库 */private Map 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);}PostConstructpublic void initHikariDataSourceMap() {dsNameMap Maps.newHashMap();hikariDataSourceMap Maps.newHashMap();Map 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 allDataSource shardingSphereDataSource.getContextManager().getDataSourceMap(logic_db);Connection connection allDataSouce.get(logicalSchemaName).getConnection();虽然它也用了HintManger但我认为是没有意义的。修复综合以上的分析最终的修复代码如下List 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实例配置会导致不同的现象难以排查真正的原因。