
背景SQLBot 与智能问数为什么依赖“备注”1.1 SQLBot 到底解决什么问题SQLBot简单理解就是一个“用自然语言查询数据库”的智能助手。用户不需要写 SQL只需要用业务语言描述需求例如“查一下最近 7 天每个支付渠道的订单金额”SQLBot 负责完成从自然语言到 SQL 的转换并执行查询、返回结果。这个能力在传统 BI 时代是难以想象的。过去做一个报表需要产品经理提需求、开发人员写 SQL、再配置图表往往一两天才能交付一个分析指标。而 SQLBot 走的是“语义解析 动态生成 SQL”的路线新问题进来就生成新查询边际成本非常低。但这也带来了一个容易被忽视的问题SQLBot 对数据库元数据的依赖远超传统 BI。传统 BI 的指标和维度是人工建模确定的模型只需要在已有的指标池里做选择而 SQLBot 是“即问即答”它必须自己理解数据库里每个表、每个字段的业务含义。如果表结构本身没有注释、字段命名随意那么模型再强大也只能靠猜。猜对了是运气猜错了就是线上事故。所以数据源导入备注并不是一个“锦上添花”的功能它直接决定了智能问数的准确率和可用性。备注的本质是把人工积累的业务经验显式化变成机器可以消费的上下文。1.2 数据源备注的三个层级数据源备注可以从三个粒度来梳理第一层是数据源级备注。描述这个数据库整体承载什么业务。比如“订单交易主库包含订单、支付、退款、售后等核心交易数据”或“商品中心库包含商品、类目、品牌、库存数据”。这一层备注帮助 SQLBot 判断“用户问的问题该去哪个库查”。第二层是表级备注。描述表的业务含义解决“这个问题对应哪张表”的映射问题。比如order_info表备注为“订单主表一个订单一条记录包含订单基本信息”order_pay表备注为“订单支付流水表一个订单可能有多条付款记录”。表级备注写清楚主键粒度和核心职责模型才能避免选错表。第三层是字段级备注。描述字段的业务含义和枚举取值。比如status字段备注为“订单状态1待支付、2已支付、3已发货、4已完成、5已取消”pay_amount备注为“实付金额单位元保留两位小数”。字段级备注是所有错误 SQL 里最需要解决的问题SQLBot 生成 WHERE 条件、GROUP BY 分组维度时依赖的就是字段语义。三个层级的备注并非孤立存在它们在 SQLBot 的语义解析链路中是逐层落地的。数据源级备注决定数据范围表级备注决定主表选择字段级备注决定条件和聚合粒度。任何一个层级缺失都会让模型退回到“字面猜测”模式。1.3 备注如何参与 SQL 生成我们可以用一个例子直观感受备注的影响。假设用户提问“统计每个省份的已完成订单数”。如果没有备注模型看到的表结构可能是CREATE TABLE order_info ( id bigint NOT NULL, province varchar(50) DEFAULT NULL, status tinyint DEFAULT NULL, amount decimal(10,2) DEFAULT NULL );模型只能猜测province是省份status是状态但“已完成”对应哪个数字无从知晓。最终生成的 SQL 可能是SELECT province, COUNT(*) FROM order_info WHERE status 1 GROUP BY province;如果 1 恰好是“待支付”这个结果就是完全错误的。如果我们在导入数据源时维护好了字段备注CREATE TABLE order_info ( id bigint NOT NULL COMMENT 订单主键, province varchar(50) DEFAULT NULL COMMENT 收货省份, status tinyint DEFAULT NULL COMMENT 订单状态1待支付、2已支付、3已发货、4已完成、5已取消, amount decimal(10,2) DEFAULT NULL COMMENT 实付金额元 ) COMMENT订单主表一张订单一条记录;SQLBot 就能准确解析出“已完成”对应status 4并按province分组。准确率提升是立竿见影的。后面章节我们就从工程视角来落地这套“带备注的数据源导入方案”。2. 环境准备与版本说明2.1 运行环境本文以 Java 后端技术栈为例既然场景涉及 Spring Boot、MyBatis Plus、多数据源和 ShardingSphere我们先统一环境范围。操作系统Windows / macOS / Linux 均可本文不依赖特定系统命令。JDK示例默认 JDK 8 或 JDK 11Spring Boot 2.x 场景下足够。构建工具Maven 3.6。数据库MySQL 5.7 或 8.0实际生产可按需替换为 PostgreSQL、Oracle 等。框架版本Spring Boot 2.x、MyBatis Plus 3.5.x、dynamic-datasource 3.x、ShardingSphere 5.x。版本说明这里强调一句Spring Boot 3.x 与 2.x 在包名和部分自动装配上存在差异dynamic-datasource 与 ShardingSphere 不同大版本的 API 也有调整。本文示例以业界最常见的 Spring Boot 2.x 组合为准如果项目使用的是 Spring Boot 3.x请把javax.annotation替换为jakarta.annotation并参考官方文档升级对应 starter 版本。2.2 Maven 依赖新建一个 Spring Boot 项目核心依赖如下dependencies !-- Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis Plus -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- dynamic-datasource 多数据源 -- dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version3.6.1/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- ShardingSphere JDBC -- dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactId version5.3.2/version /dependency !-- Lombok可选简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies这里的版本号建议以实际项目 Maven 仓库可用的版本为准。ShardingSphere 的 starter 在不同小版本间 API 差异较大如果遇到找不到类或方法签名不一致的问题大概率是版本不匹配优先检查 ShardingSphere、dynamic-datasource、MyBatis Plus 三者之间的兼容性。2.3 项目目录结构我建议按下面的结构组织代码包名清晰后续扩展更方便src/main/java/com/example/sqlbot ├── SQLBotApplication.java ├── config │ ├── DynamicDataSourceConfig.java │ └── ShardingDataSourceConfig.java ├── controller │ └── DataSourceMetaController.java ├── entity │ ├── DataSourceMeta.java │ └── TableMeta.java ├── mapper │ └── DataSourceMetaMapper.java ├── service │ ├── DataSourceMetaService.java │ └── impl/DataSourceMetaServiceImpl.java └── support ├── MetaDataLoader.java └── DataSourceRegistry.java其中DataSourceMetaController负责暴露数据源导入接口MetaDataLoader负责读取数据库字典信息DataS源码Registry负责把新导入的数据源注册到动态数据源路由中。3. 数据源导入备注的设计与实现3.1 数据源元数据模型数据源导入备注的第一步是有一个合适的数据结构来承载数据源连接信息 业务备注。我们定义一个DataSourceMeta实体package com.example.sqlbot.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data TableName(sqlbot_datasource_meta) public class DataSourceMeta { /** 主键 */ TableId(type IdType.AUTO) private Long id; /** 数据源唯一标识例如 order_db */ private String dataSourceKey; /** 数据库类型mysql、postgresql、oracle */ private String dbType; /** 连接地址 */ private String host; /** 端口 */ private Integer port; /** 数据库名 */ private String databaseName; /** 用户名 */ private String username; /** 密码生产环境务必加密存储 */ private String password; /** 数据源级备注描述该库整体的业务范围 */ private String remark; /** 是否启用1启用、0停用 */ private Integer enabled; /** 创建时间 */ private LocalDateTime createTime; /** 更新时间 */ private LocalDateTime updateTime; }这里的remark字段就是“数据源级备注”用来描述这个库整体的业务语义。比如“订单交易主库包含订单、支付、退款等核心交易数据”。在 SQLBot 导入数据源时这个备注应该作为必填项而不是选填项。对应的建表 SQLCREATE TABLE sqlbot_datasource_meta ( id bigint NOT NULL AUTO_INCREMENT, data_source_key varchar(64) NOT NULL COMMENT 数据源唯一标识, db_type varchar(32) NOT NULL COMMENT 数据库类型, host varchar(128) NOT NULL, port int NOT NULL, database_name varchar(128) NOT NULL, username varchar(128) NOT NULL, password varchar(256) NOT NULL, remark varchar(1024) DEFAULT NULL COMMENT 数据源备注供SQLBot理解业务, enabled tinyint NOT NULL DEFAULT 1, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_datasource_key (data_source_key) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTSQLBot数据源元信息表;注意data_source_key加了唯一索引防止同一个数据源被重复导入。3.2 表与字段注释读取有了数据源级备注还不够SQLBot 要真正理解业务还需要表级注释和字段级注释。这部分信息在 MySQL 的information_schema中已经存在我们可以在导入数据源时自动读取无需用户手工维护。具体来说读取某张表的注释和字段注释可以借助information_schema.columns和information_schema.tables。下面给出一个基于 JDBC 的元数据读取工具package com.example.sqlbot.support; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 读取数据库字典信息表注释、字段注释、字段类型 */ Slf4j Component public class MetaDataLoader { public static class ColumnMeta { private String columnName; private String columnType; private String columnComment; // getter / setter 省略建议使用 Lombok 简化 } public static class TableMeta { private String tableName; private String tableComment; private ListColumnMeta columns; // getter / setter 省略 } /** * 读取指定库下所有表的元数据 */ public ListTableMeta loadTableMeta(String url, String username, String password, String databaseName) { ListTableMeta result new ArrayList(); String tableSql SELECT table_name, table_comment FROM information_schema.tables WHERE table_schema ?; String columnSql SELECT column_name, column_type, column_comment FROM information_schema.columns WHERE table_schema ? AND table_name ? ORDER BY ordinal_position; try (Connection conn DriverManager.getConnection(url, username, password); PreparedStatement tablePs conn.prepareStatement(tableSql)) { tablePs.setString(1, databaseName); ResultSet tableRs tablePs.executeQuery(); while (tableRs.next()) { TableMeta tableMeta new TableMeta(); tableMeta.setTableName(tableRs.getString(table_name)); tableMeta.setTableComment(tableRs.getString(table_comment)); try (PreparedStatement columnPs conn.prepareStatement(columnSql)) { columnPs.setString(1, databaseName); columnPs.setString(2, tableMeta.getTableName()); ResultSet columnRs columnPs.executeQuery(); ListColumnMeta columns new ArrayList(); while (columnRs.next()) { ColumnMeta columnMeta new ColumnMeta(); columnMeta.setColumnName(columnRs.getString(column_name)); columnMeta.setColumnType(columnRs.getString(column_type)); columnMeta.setColumnComment(columnRs.getString(column_comment)); columns.add(columnMeta); } tableMeta.setColumns(columns); } result.add(tableMeta); } } catch (SQLException e) { log.error(读取数据库元数据失败, e); throw new RuntimeException(数据库元数据读取失败请检查连接配置, e); } return result; } }这段代码的作用是连接目标数据库查询information_schema下所有表和列的注释信息组装成对象列表。之所以在“导入数据源”时就读取是为了第一时间把这些元数据持久化下来后续 SQLBot 做语义解析时可以直接读取避免每次查询都去扫描一次字典。如果你希望更轻量也可以只读取用户选择的几张核心表而不是全库扫描。全库扫描在某些库表数量极大时会存在性能问题建议在导入接口中增加“全量/部分”开关。3.3 导入接口连接、校验、保存数据源导入接口的整体流程应该是接收前端传入的数据源配置和业务备注。校验参数合法性包括 Host、端口、库名是否为空。使用 DTO 中的配置创建 JDBC 连接连通性测试。调用MetaDataLoader读取表与字段注释。将连接信息、备注、表元数据统一持久化。把该数据源注册到动态路由数据源中供后续查询使用。Controller 定义如下package com.example.sqlbot.controller; import com.example.sqlbot.entity.DataSourceMeta; import com.example.sqlbot.service.DataSourceMetaService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; RestController RequestMapping(/api/datasource) public class DataSourceMetaController { Resource private DataSourceMetaService dataSourceMetaService; PostMapping(/import) public String importDataSource(RequestBody DataSourceMeta dto) { dataSourceMetaService.importDataSource(dto); return 数据源导入成功; } }Service 层是核心我建议把“校验、读取元数据、注册路由”三个步骤拆分成独立方法便于单独测试package com.example.sqlbot.service.impl; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.example.sqlbot.entity.DataSourceMeta; import com.example.sqlbot.mapper.DataSourceMetaMapper; import com.example.sqlbot.service.DataSourceMetaService; import com.example.sqlbot.support.DataSourceRegistry; import com.example.sqlbot.support.MetaDataLoader; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.sql.DataSource; Slf4j Service public class DataSourceMetaServiceImpl implements DataSourceMetaService { Resource private DataSourceMetaMapper dataSourceMetaMapper; Resource private DataSourceRegistry dataSourceRegistry; Resource private MetaDataLoader metaDataLoader; Resource private DataSource dataSource; Override public void importDataSource(DataSourceMeta dto) { // 1. 校验并测试连接 boolean connected metaDataLoader.testConnection(buildUrl(dto), dto.getUsername(), dto.getPassword()); if (!connected) { throw new RuntimeException(数据源连接失败请检查网络或账号权限); } // 2. 读取元数据并持久化 // 这里简化处理只保存连接配置 dataSourceMetaMapper.insert(dto); // 3. 注册到动态路由数据源 DataSource realDataSource dataSourceRegistry.buildDataSource(dto); DynamicRoutingDataSource drds (DynamicRoutingDataSource) dataSource; drds.addDataSource(dto.getDataSourceKey(), realDataSource); log.info(数据源导入成功{}备注{}, dto.getDataSourceKey(), dto.getRemark()); } private String buildUrl(DataSourceMeta dto) { return jdbc:mysql:// dto.getHost() : dto.getPort() / dto.getDatabaseName() ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; } }MetaDataLoader中补一个testConnection方法public boolean testConnection(String url, String username, String password) { try (Connection conn DriverManager.getConnection(url, username, password)) { return conn.isValid(5); } catch (SQLException e) { log.error(数据源连接测试失败: {}, e.getMessage()); return false; } }建议在实际项目中把元数据读取结果也持久化到sqlbot_table_meta、sqlbot_column_meta两张表而不是只保存连接配置。这样 SQLBot 做语义解析时可以直接读缓存表性能更好也方便后续人工修正备注。4. Spring Boot MyBatis Plus 多数据源实战4.1 动态数据源配置当系统中的数据源不再是一个而是“主库 多个业务库”时用dynamic-datasource是一个非常成熟的选择。它的核心思路是在 Spring Boot 启动时构建一个DynamicRoutingDataSource通过内部维护的数据源 Map 实现运行时切换业务代码用DS注解即可切换数据源。在application.yml中配置动态数据源spring: datasource: dynamic: # 默认数据源如果不加 DS 注解默认走这里 primary: master # 是否严格模式true 时未匹配到指定数据源会报错 strict: false datasource: master: url: jdbc:mysql://localhost:3306/sqlbot_meta?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver order: url: jdbc:mysql://localhost:3306/sqlbot_order?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver goods: url: jdbc:mysql://localhost:3306/sqlbot_goods?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver配置项说明primary表示默认数据源。对于 SQLBot 来说主库通常保存数据源元信息、问数历史、备注字典等系统数据。strict推荐设置为true。如果代码里DS(order)但配置里没有order严格模式会直接报错方便早发现问题。每个子数据源的url、username、password、driver-class-name分别对应实际库的连接信息。4.2 使用 DS 切换数据源在 MyBatis Plus 的 Service 或 Mapper 上直接加DS注解即可切换数据源。比如 SQLBot 首页需要展示“订单库的总订单量”对应的查询逻辑走order数据源package com.example.sqlbot.service.impl; import com.baomidou.dynamic.datasource.annotation.DS; import com.example.sqlbot.mapper.OrderReportMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Map; Service public class OrderQueryService { Resource private OrderReportMapper orderReportMapper; /** * 查询订单库的总订单量与总金额 */ DS(order) public MapString, Object queryOrderSummary() { return orderReportMapper.selectOrderSummary(); } }对应的 Mapperpackage com.example.sqlbot.mapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.Map; Mapper public interface OrderReportMapper { Select(SELECT COUNT(*) AS orderCount, SUM(pay_amount) AS totalAmount FROM order_info) MapString, Object selectOrderSummary(); }DS注解可以加载在类上也可以加载在方法上。方法上的注解优先级高于类。如果某个 Service 中大部分方法都走主库只有个别方法需要切换建议不加类级注解只在指定方法上加。4.3 多数据源下的 SQLBot 查询路由SQLBot 在实际“问数”时用户可能提问的是订单数据也可能是商品数据、会员数据。通常的做法是通过意图识别判断问题所属的业务域。映射到对应的dataSourceKey。使用DS(xxx)或手动调用DynamicRoutingDataSource.determineDataSource()切换数据源。我们可以封装一个数据源路由服务package com.example.sqlbot.support; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; Component public class QueryRouter { Resource private DynamicRoutingDataSource dynamicRoutingDataSource; public Connection getConnection(String dataSourceKey) throws SQLException { DataSource dataSource dynamicRoutingDataSource.getDataSource(dataSourceKey); if (dataSource null) { throw new IllegalArgumentException(未找到数据源 dataSourceKey); } return dataSource.getConnection(); } }这样做的好处是即便没有走 MyBatis Plus 的 Mapper 层也可以直接用 JDBC 执行动态 SQL。SQLBot 生成的 SQL 往往是运行时才拼好的走 JDBC 是最直接的执行方式。但是要注意QueryRouter拿到连接后必须手动关闭连接防止连接泄漏。5. 将 ShardingSphere 数据源注册到动态数据源5.1 为什么需要手动注册ShardingSphere JDBC 在启动时会创建一个ShardingSphereDataSource它本身实现了DataSource接口内部包含多个真实数据源和分片规则。但问题是dynamic-datasource 的DynamicRoutingDataSource并不认识 ShardingSphere 创建的 DataSource它只知道配置文件中显式列出的数据源。如果业务方既需要多数据源切换能力又需要对某个大数据表做分片常见的做法就是把 ShardingSphere 创建好的数据源也作为一种“数据源”注册到动态路由数据源中。这样上层代码既可以DS(master)走普通数据源也可以DS(sharding_order)走分片数据源对业务无感。5.2 创建 ShardingSphere 数据源我们可以在一个专门配置类中创建 ShardingSphere 数据源并把它注册到DynamicRoutingDataSource。package com.example.sqlbot.config; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory; import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration; import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration; import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration; import org.apache.shardingsphere.sharding.api.config.strategy.keygen.KeyGenerateStrategyConfiguration; import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; Slf4j Configuration public class ShardingDataSourceConfig { Resource private DataSource dataSource; PostConstruct public void registerShardingDataSource() throws Exception { // 1. 真实数据源订单库的两个分片 MapString, DataSource actualDataSources new HashMap(); actualDataSources.put(ds_0, createDataSource( jdbc:mysql://localhost:3306/order_0?useSSLfalseserverTimezoneAsia/Shanghai, root, root)); actualDataSources.put(ds_1, createDataSource( jdbc:mysql://localhost:3306/order_1?useSSLfalseserverTimezoneAsia/Shanghai, root, root)); // 2. 分片规则 ShardingRuleConfiguration ruleConfig new ShardingRuleConfiguration(); ShardingTableRuleConfiguration orderTableRule new ShardingTableRuleConfiguration(order_info, ds_${0..1}.order_info_${0..1}); orderTableRule.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration(id, snowflake)); orderTableRule.setTableShardingStrategy(new StandardShardingStrategyConfiguration(user_id, table_inline)); orderTableRule.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration(user_id, db_inline)); ruleConfig.getTables().add(orderTableRule); Properties props new Properties(); // 3. 创建 ShardingSphere 数据源 DataSource shardingDataSource ShardingSphereDataSourceFactory.createDataSource( actualDataSources, Collections.singletonList(ruleConfig), props ); // 4. 注册到动态路由数据源 DynamicRoutingDataSource drds (DynamicRoutingDataSource) dataSource; drds.addDataSource(sharding_order, shardingDataSource); log.info(ShardingSphere 数据源已注册到动态数据源keysharding_order); } private DataSource createDataSource(String url, String username, String password) { // 这里的实现取决于你使用的连接池推荐 HikariCP HikariConfig hikariConfig new HikariConfig(); hikariConfig.setJdbcUrl(url); hikariConfig.setUsername(username); hikariConfig.setPassword(password); hikariConfig.setDriverClassName(com.mysql.cj.jdbc.Driver); return new HikariDataSource(hikariConfig); } }注意ShardingSphere 5.x 的AlgorithmConfiguration定义方式在不同小版本中略有差异。上面代码里的StandardShardingStrategyConfiguration和KeyGenerateStrategyConfiguration在 5.3.x 版本中可用如果你用的是 5.1 或 5.2请按官方文档调整。关键思路一致构建ShardingRuleConfiguration然后通过ShardingSphereDataSourceFactory.createDataSource创建数据源对象。上面的分片规则含义是物理库ds_0、ds_1逻辑表order_info实际映射到ds_${0..1}.order_info_${0..1}即每个库下面还有两张分表。分库字段user_id分表字段user_id。主键策略雪花算法。这段配置是典型的分库分表配置具体分片数量和分片策略需要根据业务量调整。5.3 注册到 DynamicRoutingDataSource第 5.2 节中已经演示了注册的代码核心就两行DynamicRoutingDataSource drds (DynamicRoutingDataSource) dataSource; drds.addDataSource(sharding_order, shardingDataSource);这里需要注意一个细节DynamicRoutingDataSource持有的dataSourceMap在运行时是可变的addDataSource方法会把新的数据源放到内部 Map 中。因此即使你的数据源不在application.yml中配置只要运行期注册进去就能通过DS(sharding_order)动态切换。注册完成后之前的QueryRouter也可以直接获取分片数据源public Connection getShardingConnection() throws SQLException { DataSource shardingDataSource dynamicRoutingDataSource.getDataSource(sharding_order); return shardingDataSource.getConnection(); }这样一来SQLBot 在生成 SQL 时遇到涉及订单分片表的查询只需要指定dataSourceKey sharding_order执行层就会自动走 ShardingSphere 的路由逻辑把逻辑 SQL 改写成真实分片 SQL。5.4 验证 ShardingSphere 与动态数据源结合写一个测试接口验证注册结果package com.example.sqlbot.controller; import com.example.sqlbot.support.QueryRouter; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; RestController RequestMapping(/api/test) public class TestController { Resource private QueryRouter queryRouter; GetMapping(/sharding) public ListString testSharding() throws Exception { ListString result new ArrayList(); try (Connection conn queryRouter.getConnection(sharding_order); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT COUNT(*) AS cnt FROM order_info)) { while (rs.next()) { result.add(分片订单总数 rs.getLong(cnt)); } } return result; } }如果 ShardingSphere 数据源注册成功访问/api/test/sharding时SQL 会经过分片引擎改写实际在多个分表上执行并聚合返回结果。这一步能验证整个链路是否打通。6. 常见问题与排查思路在数据源导入备注、多数据源切换、ShardingSphere 注册这套组合场景中高频问题主要集中在几个方向。下面用表格梳理一下问题现象常见原因解决思路数据源导入时连接失败端口不可达、账号密码错误、MySQL 驱动不匹配先用数据库客户端手工连接一次排除网络与账号问题导入时读取不到表注释连接账号缺少information_schema读取权限给账号授权SELECT权限或调整元数据读取方式DS(order)不生效仍然走主库注解加在类上但方法上没有或方法参数未通过代理调用确认注解所在层级与数据源 key 完全匹配检查调用方是否加DS动态数据源找不到 ShardingSphere 数据源addDataSource未执行或注册时顺序在 Spring Bean 初始化之前确认PostConstruct执行成功检查日志中是否有注册成功记录ShardingSphere 启动报错版本不兼容、分片规则表达式错误、物理表不存在先简化分片规则确保分片表达式能映射到已有表再逐步还原复杂配置SQLBot 生成 SQL 中表名不存在逻辑表名与物理表名不一致检查 ShardingSphere 逻辑表配置确保查询语句使用的是逻辑表名注册的动态数据源在重启后消失addDataSource只在内存中生效未持久化如果 key 是动态导入的需要在每次启动时重新注册数据库密码明文存在库里安全规范问题采用接密件、KMS 或配置中心加密最小权限原则排查动态数据源问题时最实用的手段是打开dynamic-datasource的调试日志logging: level: com.baomidou.dynamic.datasource: debug日志中会打印每次请求路由到了哪个数据源可以快速判断是注解问题还是数据源注册问题。7. 最佳实践与工程建议7.1 备注体系要“导入时强约束使用中可修正”数据源导入备注最怕的是“用户不填、系统不查”。我的建议是数据源级remark设置为必填项导入时校验非空且不少于一定字数。表级、字段级备注不强制手工录入而是优先从information_schema自动读取对于没有注释的字段在 SQLBot 管理后台提供“批量补充注释”的功能。允许业务人员在后台修正、补充备注并且每次修正都要记录审计日志方便追踪“为什么某个 SQL 突然变了”。备注质量是智能问数的核心资产宁可导入时慢 1 秒也要把元数据抓全。7.2 多数据源 key 的命名规范数据源key相当于数据源的唯一标识命名不规范会导致 SQLBot 意图识别错乱。推荐命名规范业务域前缀 下划线 库类型后缀例如order_db、goods_db、member_db。分片数据源以sharding_开头例如sharding_order。禁止使用db1、db2这类无业务含义的命名。命名规范一旦确定最好在导入接口中做正则校验从源头避免脏数据。7.3 密码加密与最小权限本文示例中密码直接放在 DTO 和实体中仅用于演示。生产环境必须做到数据库账号按业务域隔离SQLBot 只授予只读权限。密码字段加密存储不落明文日志。连接配置统一放到配置中心Nacos、Apollo动态刷新不用重新部署。权限的最小化原则同样适用于 ShardingSphere分片数据源账号只需要SELECT权限即可。7.4 动态注册的数据源要支持启动自愈DynamicRoutingDataSource.addDataSource是内存级操作。如果数据源 key 是通过管理后台导入的重启后需要重新注册。建议在启动时做一次“数据源元信息对比”从sqlbot_datasource_meta表查询所有启用的数据源。遍历判断是否已存在于DynamicRoutingDataSource。如果缺失则重新构建连接并注册。这样才能保证停机维护后动态数据源不会出现“配置丢失”的假象。8. 总结与学习路线本文围绕“SQLBot 数据源导入备注智能问数更清晰”这个核心话题完整覆盖了三个层面的内容第一我们解释了 SQLBot 这类智能问数工具为什么高度依赖元数据并把数据源备注拆解为数据源级、表级、字段级三个层级。备注不是给用户看的文档而是给 NLP 模型吃的上下文质量越高生成 SQL 的准确率越高。第二我们通过一个完整的 Spring Boot MyBatis Plus 项目实现了数据源导入接口、表注释/字段注释读取、动态数据源切换。这套代码可以直接作为 SQLBot 数据源管理模块的底座。第三我们解决了一个工程难点如何把 ShardingSphere 创建的数据源注册到 dynamic-datasource 的动态路由中。这样 SQLBot 既能兼容普通多数据源也能支持分库分表的复杂场景。下一步你可以继续深入的方向有基于 OpenAI 或开源大模型把本文的数据源备注体系接入 Prompt 模板做真实的自然语言转 SQL 实验。为核心业务表补充更细粒度的字段枚举备注构建行业字典。研究 ShardingSphere 分片键选择、分布式主键、读写分离等更高级配置。为数据源管理后台增加“备注质量分”评估提醒用户哪些表备注不足。如果你正在做智能问数、数据中台或 BI 增强产品数据源备注都是一个值得投入的基础工程。先把元数据打牢再谈模型调优效果会事半功倍。