这几个月一直在弄国产数据库环境的项目落地,最绕不开的一件事就是把 ShardingSphere5.4.1 接到人大金仓 8.6 上。老实讲,刚开始我以为这活儿不复杂,毕竟 ShardingSphere 从 5.3.x 开始就在 DatabaseType 里加了 KingbaseES,官方文档也写了支持金仓,可真正把驱动换掉、配置改完之后,才发现“支持”两个字背后全是细节:元数据加载失败、分页数据重复、加密字段查不到、Oracle 遗留语法解析报错,一个接一个。这篇文章就是我来来回回踩坑之后的完整记录,适合正在做金仓适配,或者准备把分库分表中间件切到国产库的团队参考。
1. 适配前的两个疑问:金仓到底兼容什么,ShardingSphere 又认什么
1.1 先摸清金仓 8.6 的“身份”
很多人在适配前根本没把数据库环境理清楚,上来就改配置,结果浪费大量时间。金仓 8.6 不是一个单一形态的数据库,它有多种兼容模式,最常见的是 PG(PostgreSQL)兼容模式和 ORA(Oracle)兼容模式,部分版本还支持 MySQL 兼容模式。这个差异直接决定了 ShardingSphere 用什么方言解析你的 SQL。
我这次适配的环境是在 PG 兼容模式下运行的,这一点很关键。你可以通过下面的 SQL 确认当前兼容模式:
SHOW database_mode;如果返回的是pg,那么后续的 SQL 解析、元数据加载都会按 PostgreSQL 的规则走;如果返回ora,那你得额外小心,因为 ShardingSphere 的解析器可不会因为金仓开了 Oracle 兼容模式就自动变成 Oracle 方言。
另外还要确认几个基础信息:
- 默认端口:金仓 8.6 默认端口一般是54321,而不是 PostgreSQL 的 5432。
- 驱动类名:
com.kingbase8.Driver。 - JDBC URL 前缀:
jdbc:kingbase8://。 - 默认 schema:PG 模式下通常是
public,ORA 模式下可能是sys或用户名同名 schema。
这些信息决定了你接下来的数据源配置长什么样。我见过不少同事直接把jdbc:postgresql://改成jdbc:kingbase8://、驱动换成金仓驱动就以为完事了,结果一启动就报元数据加载失败,根因往往是 schema 没对上。
1.2 ShardingSphere 是怎么识别数据库类型的
ShardingSphere 5.x 使用 SPI 机制管理数据库类型,识别方式主要有两条路径:一是根据 JDBC URL 前缀判断,二是通过Connection.getMetaData().getDatabaseProductName()拿到的产品名匹配。
在 5.4.1 中,金仓对应的是内置的KingbaseES类型。日志里一般会输出类似:
Database type: KingbaseES如果驱动版本比较老,getDatabaseProductName()返回的可能是PostgreSQL,此时 ShardingSphere 会按 PostgreSQL 方言处理。一般也能用,因为在 PG 兼容模式下两者的 SQL 语法高度一致,但如果你在 ORA 模式下跑,又不小心写了 Oracle 特有语法,解析阶段就会出问题。
还有一个隐藏坑:如果驱动返回的产品名在 ShardingSphere 的注册表里找不到,启动时就会直接抛:
Cannot get database type from registry: unknown database type这种报错需要用代码手动指定 DatabaseType,下面第三章会专门讲。
1.3 适配前的版本与功能清单
大版本确认好之后,我建议把适配范围圈清楚,不是所有 ShardingSphere 特性都要一步到位。这次我划定的范围是:
| 功能 | 是否适配 | 备注 |
|---|---|---|
| 单库分表 | 是 | 最核心场景 |
| 读写分离 | 是 | 与分片共存 |
| 数据加密 | 是 | 逻辑列与密文列映射 |
| 分布式主键 | 是 | 用 SNOWFLAKE,不走数据库序列 |
| 分布式事务 XA | 否 | 先本地事务兜底 |
| ShardingSphere-Proxy | 否 | 前端协议兼容性风险大,见第六章 |
版本上我用的是 ShardingSphere 5.4.1、金仓 V8.6、JDK 8、HikariCP 连接池。如果你用 JDK 17 也没问题,但 ShardingSphere 5.4.1 在 JDK 8 上最稳。先把最小功能集跑通,再逐步放开,排查问题会容易很多。
2. 驱动替换和连接串配置:看似一分钟,实际坑不少
2.1 金仓驱动不在 Maven 中央仓库,得手动装
金仓的 JDBC 驱动kingbase8不会出现在 Maven 中央仓库里,我第一次直接用仓库坐标拉取就失败了。它通常在金仓安装目录的Interface/JDBC下,文件名类似kingbase8-8.6.0.jar。你需要把它手动安装到本地仓库:
mvn install:install-file \ -Dfile=kingbase8-8.6.0.jar \ -DgroupId=cn.com.kingbase \ -DartifactId=kingbase8 \ -Dversion=8.6.0 \ -Dpackaging=jar装完之后在pom.xml里引入:
<dependency> <groupId>cn.com.kingbase</groupId> <artifactId>kingbase8</artifactId> <version>8.6.0</version> </dependency>有些项目不方便装本地依赖,也可以直接把 jar 放到lib目录,用系统依赖方式引入。总之要先确认驱动真正打进了运行环境,因为后面所有连接池初化化失败,一半以上都是这个原因。
2.2 数据源 YAML 配置实例
ShardingSphere 5.4.1 用 YAML 配置数据源时,我建议坚持用 HikariCP 作为连接池。下面这份配置是经过实测可用的:
dataSources: ds_0: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.kingbase8.Driver jdbcUrl: jdbc:kingbase8://127.0.0.1:54321/demo?currentSchema=public&useUnicode=true&characterEncoding=UTF-8 username: system password: xxxxxx maximumPoolSize: 50 minimumIdle: 5 connectionTimeout: 30000 idleTimeout: 600000 rules: - !SHARDING tables: t_order: actualDataNodes: ds_0.t_order_$->{0..3} tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: t_order_inline keyGenerateStrategy: column: order_id keyGeneratorName: snowflake shardingAlgorithms: t_order_inline: type: INLINE props: algorithm-expression: t_order_$->{order_id % 4} keyGenerators: snowflake: type: SNOWFLAKE props: worker-id: 1 props: sql-show: true有几个细节要注意:
driverClassName一定要写全,有些版本的 HikariCP 可以根据 URL 自动推断,但在金仓驱动上偶尔推断不出来,宁可显式写上。currentSchema=public这个参数建议无条件加上,它能避免很多元数据加载和 schema 找不到的诡异问题。sql-show: true必须开,排查问题全靠它看逻辑 SQL 和真实下发 SQL。
2.3 连接池初始化失败的几个典型报错
这部分我直接给出报错和排查方向,帮你少走弯路:
| 报错信息 | 实际原因 | 处理方式 |
|---|---|---|
Failed to load driver class com.kingbase8.Driver | 驱动 jar 没打进去 | 检查依赖和打包产物,确认 lib 里有驱动 |
Connection is not available, request timed out | 连接串配错或端口不通 | telnet 127.0.0.1 54321先测连通性 |
The connection attempt failed | URL 或用户密码问题 | 先用 DBeaver 等工具直接连,排除库端问题 |
Cannot load driver class: com.kingbase.Driver | 驱动类名写错 | 确认是com.kingbase8.Driver,不是com.kingbase.Driver |
从实践来看,连接池这层问题最不值得花时间,优先用客户端工具证明“JDBC 能连通”,再回头检查 ShardingSphere 配置,问题一下子就定位了。
3. 元数据加载失败:ShardingSphere 启动阶段的头号拦路虎
3.1 元数据加载在启动时到底做了什么
ShardingSphere 5.x 在初始化分片、加密等规则时,会主动加载真实表的元数据,包括表结构、列名、主键、索引等。它通过 JDBC 的DatabaseMetaData接口实现,也就是说需要执行一系列基于information_schema和pg_catalog的查询。
金仓 8.6 在 PG 兼容模式下,information_schema和pg_catalog基本是兼容的,大部分查询能正常返回。但有一个前置条件:连接的用户必须有权限访问目标 schema,且currentSchema指向正确。如果currentSchema没配,而 ShardingSphere 又拿不到默认 schema,它可能会遍历所有 schema 去找表,轻则加载慢,重则看到重复表名直接报冲突。
3.2 schema 与 currentSchema 的真实坑
我这次遇到的现象是:启动日志里能看到加载数据源成功,但随后报:
Table 't_order' does not exist in database 'demo'诡异的是表明明建了。后面查下来,问题出在Connection.getSchema()返回的是null,ShardingSphere 无法确认表所在的 schema,最终在元数据加载阶段把表过滤掉了。
解决方式就是在 JDBC URL 上显式指定 schema:
jdbc:kingbase8://127.0.0.1:54321/demo?currentSchema=public如果你的表建在sys或app等下,就把public替换成对应 schema。这一步对于金仓 8.6 来说非常关键,建议直接当标配写进连接串。
另外还有一个不容易注意的点:金仓某些版本的主键元数据需要通过pg_index获取,如果业务表建成了无主键堆表,ShardingSphere 加载到的主键信息就是空的。这会影响后续基于主键的 SQL 改写和去重逻辑。适配前最好给业务表补上主键,哪怕是用雪花算法生成的分布式主键,也需要在真实表上建立物理主键约束。
3.3 手动指定 DatabaseType 的兜底办法
正常情况下,ShardingSphere 5.4.1 能通过驱动自动识别金仓为KingbaseES,日志里也会打印对应的数据库类型。但如果你用的是旧版金仓驱动,或者驱动对getDatabaseProductName()的实现有偏差,识别就可能失败。
手动指定数据库类型的做法是:用ShardingSphereDataSourceFactory.createDataSource重载方法,在代码里显式传入DatabaseType:
DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource( dataSourceMap, ruleConfigs, new Properties(), DatabaseTypeEngine.getDatabaseType("KingbaseES"));这里有一点需要提醒:5.4.1 源码里DatabaseTypeEngine的包名在不同小版本中可能有调整,具体类路径以你本地 jar 为准。不同版本的字符串值也可能是KINGBASE或KingbaseES,建议写个小的单元测试先打出来确认,避免编译过了运行时报No such database type。
如果是在纯 YAML 环境下没法改 Java 代码,那就优先升级驱动的兼容版本,让自动识别正常工作。手动指定是兜底,不该是首选。
4. SQL 解析与改写:分页、分片键和加密下推的实测记录
4.1 解析层:为什么 PG 解析器能顶半个金仓
ShardingSphere 5.4.1 的 SQL 解析是分层的:先用对应方言的解析器把 SQL 解析成 AST,再统一标准化成内部逻辑 SQL,最后根据分片、加密等规则改写。
金仓在 PG 兼容模式下,CRUD 语法与 PostgreSQL 高度一致,所以 ShardingSphere 内置的 PostgreSQL 方言解析器几乎可以“白嫖”。实际适配中大量问题不出在解析层,而出在业务 SQL 里残留的 Oracle 风格语法。
最典型的就是ROWNUM。比如下面这条 SQL,在金仓 ORA 模式下能执行:
SELECT * FROM t_order WHERE ROWNUM <= 10;但在 ShardingSphere 里走到 PostgreSQL 方言解析器时,会直接报:
SQL parse error. You may have an error in this SQL statement: non support token 'ROWNUM'类似的还有(+)外连接、START WITH ... CONNECT BY层次查询、SYSDATE等。凡是这类 Oracle 专属写法,在金仓适配 ShardingSphere 的场景下统统不建议留。处理方式很简单,统一改成 PG 风格:
SELECT * FROM t_order LIMIT 10;我建议在适配阶段做一轮 SQL 合规扫描,把所有ROWNUM、NVL、SYSDATE等 Oracle 痕迹清掉,省得后面上线时被用户的一条临时查询打挂。
4.2 分页改写:LIMIT 与 FETCH FIRST 的兼容细节
分页是分片场景下改写最频繁的逻辑。ShardingSphere 会对分页 SQL 做改写,必要时还要在内存里做二次归并。比如你要查第 2 页、每页 20 条,实际下发到每个分片的 SQL 会变成取前 40 条再归并截断。
这种机制对金仓本身是透明的,因为 PG 模式支持标准的LIMIT ? OFFSET ?语法,PreparedStatement 的参数绑定也正常。但有两个细节值得注意:
- 如果业务 SQL 用了
FETCH FIRST ? ROWS ONLY这种写法,需要确认金仓驱动支持预编译参数绑定。实测中金仓 8.6 对FETCH FIRST的动态参数支持偶尔会有兼容问题,报错类似于syntax error at or near "?"。稳妥起见,统一改写成LIMIT ? OFFSET ?。 - 分片键上的分页查询,如果
ORDER BY字段不是分片键,ShardingSphere 会把排序下推到每个分片执行,再内存归并。这个过程中如果ORDER BY字段本身是加密逻辑列,改写后的真实列名可能跟原始排序条件对不上,出现“列不存在”的报错。处理办法是让加密字段只作为数据存储列,不作为排序和查询条件列使用。
4.3 加密字段下推:列名大小写是金仓最容易踩的坑
数据加密是 ShardingSphere 的高频功能。配置上,你需要定义逻辑列、密文列、明文列和加密算法。下面是我实际用过的最小配置:
- !ENCRYPT encryptors: aes_encryptor: type: AES props: aes-key-value: 1234567890abcdef tables: t_user: columns: phone: cipherColumn: phone_cipher plainColumn: phone_plain encryptorName: aes_encryptor写入时,业务 SQL 只需要关心逻辑列phone,ShardingSphere 会自动改写为INSERT INTO t_user (phone_cipher, phone_plain),并把明文加密后写入密文列。
这个机制本身不区分数据库品牌,但在金仓上我遇到一个很实际的问题:大小写敏感。PG 兼容模式对未加引号的标识符一律折叠为小写。如果你的建表 DDL 写的是:
CREATE TABLE t_user ("Phone_Cipher" VARCHAR(100), "Phone_Plain" VARCHAR(100));那么 ShardingSphere 改写出来的列名是phone_cipher,双双对不上,报错:
column "phone_cipher" does not exist解决办法很粗暴:建表时所有表名、列名一律小写,不要加双引号。如果你被历史遗留的“驼峰加引号”表结构绑住,那只能在加密规则里把cipherColumn配成和物理列完全一致的大小写,并在 SQL 里也带引号访问,但这种做法的维护成本很高,不推荐。
4.4 分片键路由的非分片键查询代价
分片场景下还有一个容易忽略的问题:如果你按order_id分片,但查询条件只用user_id,ShardingSphere 会把这个查询广播到所有分片,再合并结果。这个过程对金仓是透明的,但性能会随分片数量线性下降。我建议在 SHARDING 规则里配置绑定表:
bindingTables: - t_order,t_order_item哪怕是一对多的关联查询,只要分片键一致,就可以避免笛卡尔积式的跨分片关联。这个跟数据库品牌无关,但适配金仓时尤其值得做,因为金仓在并发处理上不如 PostgreSQL 原生环境宽裕,少一些广播查询,压力能小不少。
5. 一次分页查询错乱问题的完整排查链路
5.1 现象:第一页正常,第二页出现第一页的数据
适配过程中我遇到一个很经典的坑,值得单独写一节。现象是:t_order按order_id % 4分片,业务查询时第一页数据正常,翻到第二页时,出现了第一页已经出现过的数据。
第一反应是分页改写参数错了,但连着检查好几遍 SQL,都没发现逻辑问题。后来把所有相关日志打开,才发现问题不在 ShardingSphere,而在金仓驱动的连接参数。
5.2 排查过程:从 SQL 日志到 JDBC 驱动参数
第一步,打开 ShardingSphere 的 SQL 日志。配置:
props: sql-show: true日志里会输出Logic SQL和Actual SQL。我看到逻辑 SQL 是:
SELECT * FROM t_order ORDER BY create_time LIMIT 20 OFFSET 20;而实际下发的 SQL 也是:
SELECT * FROM t_order_0 ORDER BY create_time LIMIT 20 OFFSET 20; SELECT * FROM t_order_1 ORDER BY create_time LIMIT 20 OFFSET 20;参数完全正确,所以第一层排除 ShardingSphere 改写问题。
第二步,检查金仓是否真的按OFFSET 20返回。我用金仓客户端手工执行同样的 SQL,结果也正确。于是问题收敛到 JDBC 驱动层。
第三步,排查连接串参数。我发现初始化时为了优化大结果集,连接串里加了:
useCursorFetch=true&defaultRowFetchSize=100这个参数在 PostgreSQL JDBC 里用于开启游标分批取数。PG JDBC 在使用游标模式下,如果事务处于自动提交,setFetchSize不会生效,但金仓驱动的部分版本对这种组合的处理有瑕疵,会导致分页查询在内存和游标之间串页,最终出现翻页重复。
5.3 根因与修复方案
根因就是useCursorFetch=true和 ShardingSphere 分页归并机制叠加后,游标取数把“偏移量”重复计算了。修复方案很简单:金仓 8.6 环境下,去掉useCursorFetch和defaultRowFetchSize,让分页数据一次性进入 ShardingSphere 内存归并。
修改后的连接串:
jdbc:kingbase8://127.0.0.1:54321/demo?currentSchema=public&useUnicode=true&characterEncoding=UTF-8改完之后连续翻页 30 轮,没有重复也没有漏行。这个坑给我一个教训:从 PostgreSQL 迁移到金仓时,连接串参数不能原样照搬。金仓驱动虽然内核沿袭了 PG,但游标取数这类行为差异很大,最好在生产压测前逐项验证。
5.4 排查链路的通用价值
这套排查链路其实适用于所有“ShardingSphere + 国产数据库”的诡异查询问题:
- 先看
Logic SQL和Actual SQL,确认中间件改写是否正确。 - 再用数据库客户端手工执行
Actual SQL,确认数据库本身返回是否正确。 - 最后检查 JDBC 连接参数和驱动版本,重点怀疑与 fetchSize、游标、事务隔离相关的参数。
按这个顺序走,大部分问题能在半小时内定位。反过来,如果你一上来就怀疑 ShardingSphere 的 SQL 改写,很容易陷入反复翻源码的泥潭,浪费时间。
6. 事务模式和 Proxy 选型:适配方案的边界在哪儿
6.1 本地事务与 XA 在金仓上的表现
ShardingSphere 5.4.1 支持三种事务模式:本地事务、XA 事务和 BASE 事务。
本地事务是最稳的选择。金仓 8.6 在 PG 兼容模式下,单库单事务完全没问题,ShardingSphere 的多分片写入在本地事务下会根据路由结果顺序提交,只要业务对一致性要求不跨分片,这个模式完全够用。
XA 事务理论上支持,但要满足两个前提:一是金仓驱动实现了javax.sql.XADataSource接口,二是应用里引入 Atomikos 或 Narayana 的事务管理器。我在测试中发现,金仓 8.6 驱动能做 XA 注册,但和 Atomikos 的日志目录权限、连接池代理对象之间存在较多组合问题。如果你对金仓 XA 不是特别熟,我更建议用 Seata 这类 BASE 方案,或者干脆在业务层做本地消息表补偿,别把分布式事务这个重担压给数据库适配层。
如果你确实要用 XA,测试时可以从简单场景开始:
TransactionTypeHolder.set(TransactionType.XA);然后只做“两个分片同时写入”的冒烟测试,观察事务管理器日志里是否出现XAER_RMERR或XAER_RMFAIL。出现这类错误基本可以确认驱动和事务管理器不兼容,趁早换方案。
6.2 ShardingSphere-JDBC 与 Proxy 的现实选择
关于 ShardingSphere-Proxy,这里要给一个比较直接的建议:在人大金仓 8.6 的适配初期,优先选 ShardingSphere-JDBC 嵌入式模式,不要一上来就上 Proxy。
原因在于 Proxy 是一个独立数据库中间件进程,客户端需要以对应的数据库协议连接它。ShardingSphere-Proxy 5.4.1 对外主要支持 MySQL 和 PostgreSQL 协议,金仓的自有客户端协议并非标准 PG wire protocol。如果金仓侧没有开启 PG 兼容协议端口,你的应用直接用金仓驱动连 Proxy,大概率会握手失败或者认证失败。
| 接入场景 | 推荐方案 | 原因 |
|---|---|---|
| 单个 Java 应用需要分片加密 | ShardingSphere-JDBC | 嵌入式无协议问题,排查简单 |
| 多个应用共享同一套分片规则 | 先 JDBC + 配置中心,再评估 Proxy | 多应用一致性问题优先靠配置管理解决 |
| 非 Java 应用需要分片能力 | Proxy + 金仓 PG 兼容协议端口 | 需要提前验证协议连通性,风险较高 |
| 要求全链路透明免改造 | 不推荐 ShardingSphere | 建议评估数据库内置分布式方案 |
如果你的团队确实需要 Proxy,我的建议是先确认金仓 8.6 能不能开启兼容 PG 协议的对外端口,然后写一个最小客户端做一次连接测试,通过后再谈 Proxy 部署。
6.3 主键策略:别让 sequence 成为瓶颈
分片场景下,主键生成是经常被遗忘的一环。许多业务从 Oracle 迁到金仓时习惯用SERIAL或NEXTVAL('seq_xxx'),但在 ShardingSphere 分片架构里,单库 sequence 生成的全局唯一 ID 会冲突,多库 sequence 又很难保证顺序一致。
更合适的方式是使用 ShardingSphere 的分布式主键生成器。我的推荐是 SNOWFLAKE:
keyGenerators: snowflake: type: SNOWFLAKE props: worker-id: 2配套的表结构里,主键列用BIGINT,和雪花算法返回的 long 完全匹配。如果你的表已经用了 UUID 字符串主键,那就把策略配成:
keyGenerators: uuid: type: UUID并保证主键列是VARCHAR(36)。实测中,SNOWFLAKE 的性能和稳定性比 UUID 好,尤其在金仓这种对字符串索引不占优势的数据库上,能优先用雪花就用雪花。
另外要留意:ShardingSphere 的 SNOWFLAKE 默认会生成带时间戳的长整型,如果你下游系统对 ID 长度有严格限制,比如只有 MySQL 的BIGINT无符号范围,那也问题不大。但如果对接的老系统用了INT类型主键,雪花 ID 会溢出,这时候只能改用 UUID,没有别的选择。
6.4 适配完成后的快速冒烟验证清单
最后分享一个我每次适配新库都会跑的冒烟链路,顺手整理成清单。你在金仓 8.6 上做完基本配置后,按下面的顺序跑一遍,能覆盖绝大多数核心风险点:
- 通过 ShardingSphere 执行一条
INSERT,确认分片路由和主键生成正常。 - 按分片键执行
SELECT,确认能定位到正确分片。 - 按非分片键执行
SELECT,确认广播查询能合并结果。 - 连续翻三页分页数据,确认无重复无漏行。
- 对加密字段执行一次写入和查询,确认密文列、明文列读写正常。
- 查看日志中的
Actual SQL,确认 schema 前缀、列名大小写都符合预期。
跑完这条链路,适配的核心风险基本都覆盖到了,剩下就是等压测暴露性能问题。我在实际项目里的体会是,金仓 8.6 的兼容性并没有很多文章写得那么可怕,真正让人头疼的都是环境细节:schema 没对上、驱动参数照搬、历史遗留 SQL 不兼容。只要把这些细节当成一等公民对待,ShardingSphere5.4.1 接人大金仓8.6这套组合是能稳定跑下去的。