一、上篇回顾与这篇要回答的问题

上一篇我在一台鲲鹏(aarch64)+ Ubuntu 22.04 的云服务器上装好了 KingbaseES V9R3C18(MySQL 兼容版),把业务里 JSON 重度使用的场景逐条实测了一遍:12 个测试点 9 个与 MySQL 完全一致,测出 INSERT() 缺失、FORMAT() 无千分位两个函数级差异,还发现了 -> 链式调用这样的超集能力。SQL 语法层的结论是"核心路径零改造"。

但语法兼容只回答了一半问题。真实的迁移评估还有更底层的一问:应用程序怎么连上来? MySQL 的驱动、客户端、连接池配置能不能原样复用?以及再进一步:分析类 SQL(窗口函数)和动态 SQL(PREPARE/EXECUTE)这些"进阶"能力的成色如何?

这篇就把这三块全部实测完。测试环境与上篇完全相同:KingbaseES V009R003C018,端口 54321,ksql + 上篇建好的 member 表(4 名会员,tags/profile 两个 JSON 字段)。

二、驱动层真相:兼容的是语法,不是协议

2.1 第一步侦察:它到底在用什么协议说话

先看服务器上最基础的事实——监听端口和配置:

ss -lnt | grep 54321                     # 只有 54321,没有 3306
grep -i mysql kingbase.conf              # MySQL 相关参数都是语法/插件层的
ls /opt/Kingbase/ES/V9/Interface/jdbc/   # 看官方给了什么驱动

在这里插入图片描述

三个信息量很大的事实:

  1. 整个实例只监听 54321 一个端口,没有 3306,配置文件里也找不到任何 MySQL 协议监听参数;

  2. shared_preload_libraries 里加载的 plmysqlmysql_interval_style = on 这些参数,全是语法/行为层的兼容开关;

  3. Interface/jdbc/ 目录里官方同时打包了 kingbase8-9.0.0.jar(金仓驱动)和 postgresql-42.2.9.jar原生 PostgreSQL 驱动)——这个组合已经把答案剧透了。

2.2 不信邪:拿 MySQL 原生客户端直连试试

侦察归侦察,动手试了才算数。装个 MySQL 8.0 客户端直接怼上去:

mysql -h127.0.0.1 -P54321 -usystem --connect-timeout=5 -e 'SELECT 1'

在这里插入图片描述

结果是教科书级的报错:

ERROR 2013 (HY000): Lost connection to MySQL server at
'waiting for initial communication packet', system error: 110

这个报错很有讲头:MySQL 协议是 server 先发包(服务端先发 greeting/握手包),而 PG 系协议是 client 先发包(客户端先发 startup message)。mysql 客户端连上 54321 后干等服务端的握手包,KingbaseES 那头则在等客户端先开口——双方互相等待,直到超时。一个 ERROR 2013 把协议层的真相钉死了:KingbaseES V9R3C18 的 MySQL 兼容不包含 MySQL 线协议mysql 客户端、MySQL Connector/J、pymysql 这些走原生协议的东西是连不上来的。

2.3 正确姿势:换驱动 jar,SQL 一行不改

那应用怎么迁?答案就在官方给的 jdbc 目录里。我写了个最小 JDBC 测试程序,故意让它执行上篇同款的 MySQL 风格 SQL(JSON ->> 提取 + 窗口函数 + PreparedStatement 绑参):

Connection conn = DriverManager.getConnection(url, user, pass);
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(
    "SELECT name, profile->>'$.city' AS city, points, " +
    "RANK() OVER (ORDER BY points DESC) AS rk FROM member");
// ...
PreparedStatement ps = conn.prepareStatement(
    "SELECT name FROM member WHERE profile->>'$.city' = ?");
ps.setString(1, "北京");

用金仓驱动跑,URL 是 jdbc:kingbase8://127.0.0.1:54321/test

在这里插入图片描述

一次通过:驱动报告 Kingbase8 JDBC Driver V009R003C018,JSON 操作符、窗口函数、? 绑参(注意:JDBC 层的 ? 占位符是驱动处理的,完全正常,和后文 SQL 层 PREPARE 的差异是两回事)全部原样执行。

更让我意外的是第二个实验——用官方随包的原生 PostgreSQL 驱动连,URL 换成 jdbc:postgresql://

在这里插入图片描述

同一个测试程序、一字未改的 SQL,照样全流程跑通。这意味着 KingbaseES 对 PG 生态是协议级兼容的,连接池(HikariCP/Druid)、监控工具这些围绕 PG 协议的生态大概率都能直接用。

2.4 驱动层结论

连接方式 结果 说明
mysql 命令行客户端 → 54321 ❌ ERROR 2013 无 MySQL 线协议监听
MySQL Connector/J、pymysql 等原生驱动 ❌ 同上 协议层不互通
kingbase8 JDBC(jdbc:kingbase8:// ✅ 全通 官方推荐路径
原生 PostgreSQL JDBC(jdbc:postgresql:// ✅ 全通 PG 生态协议级兼容

一句话:兼容发生在 SQL 语法层,不在网络协议层。 对 Java 应用来说,迁移动作 = 换一个驱动 jar + 改一行 JDBC URL;MyBatis/JPA 里成千上万条 MySQL 风格 SQL 不用动——这正是上篇语法兼容结论的价值所在。评估时把"驱动替换"列进改造清单即可,工作量可控但不能漏。

三、窗口函数:分析 SQL 的成色

运营后台最常见的两类分析查询,直接在 member 表上实测。

场景一:分城市积分榜。ROW_NUMBER() 做城市内排名、RANK() 做全局排名,分区键直接用 JSON 提取表达式——这是对解析器和优化器的双重考验:

SELECT name, profile->>'$.city' AS city, points,
       ROW_NUMBER() OVER (PARTITION BY profile->>'$.city' ORDER BY points DESC) AS city_rank,
       RANK()       OVER (ORDER BY points DESC) AS total_rank
FROM member;

在这里插入图片描述

结果完全正确:北京两人内部排出 1/2,全局 1-4 名与积分严格对应。PARTITION BY 里塞 JSON 表达式都没有任何问题。

场景二:累计合计、名次差距与会员分层。 SUM() OVER 滚动累计、LAG() 取前一名积分算差距、NTILE(2) 把会员分成头部/尾部两层:

SELECT name, points,
       SUM(points) OVER (ORDER BY points DESC) AS running_total,
       points - LAG(points) OVER (ORDER BY points DESC) AS gap_to_prev
FROM member;

SELECT name, points, NTILE(2) OVER (ORDER BY points DESC) AS tier FROM member;

在这里插入图片描述

累计值 25999 → 38579 → 41779 → 42639 逐行正确,LAG 差值、NTILE 分层也全对。窗口函数这块是 KingbaseES 的舒适区——毕竟 PG 基因里窗口函数比 MySQL 8.0 资历老得多,这一节没测出任何差异,全绿通过

四、动态 SQL:PREPARE/EXECUTE 的一个关键差异

MySQL 的动态 SQL 四件套(PREPARE ... FROM / SET @var / EXECUTE ... USING / DEALLOCATE PREPARE)在报表平台、分库分表中间件里很常见,这也是活动海报点名的测试项。逐步拆解着测:

-- 语句形式本身:支持
PREPARE ok_q FROM 'SELECT count(*) AS total FROM member';
EXECUTE ok_q;              -- 返回 4 ✓
DEALLOCATE PREPARE ok_q;

-- MySQL 原样写法:FROM 字符串里用 ? 占位符
PREPARE city_q FROM 'SELECT name FROM member WHERE profile->>''$.city'' = ?';
-- ERROR: syntax error at end of input ✗

在这里插入图片描述

结果一半一半:PREPARE ... FROM**** 这个 MySQL 专属语句形式是支持的(PG 原生只有 PREPARE ... AS),SET @c = '北京' 这样的 MySQL 用户变量也支持;但 FROM 字符串里的 ?** 占位符不认**,直接语法报错。

那带参数的动态 SQL 怎么写?把 ? 换成 PG 风格的 $1 试试——混合形态一次通过

PREPARE city_q FROM 'SELECT name, points FROM member WHERE profile->>''$.city'' = $1';
SET @c = '北京';
EXECUTE city_q USING @c;   -- 张伟 12580 / 王强 860 ✓
SET @c = '深圳';
EXECUTE city_q USING @c;   -- 赵敏 25999 ✓
DEALLOCATE PREPARE city_q;

在这里插入图片描述

这个组合很有意思:语句骨架是 MySQL 的(FROM 形式 + @ 变量 + USING),占位符是 PG 的($1)。迁移成本 = 把 SQL 字符串里的 ? 批量替换成 ****$1..$N,一处正则能解决的事,但如果没提前测过,上线时一定会踩。再强调一遍容易混淆的点:这个差异只存在于 SQL 层的 PREPARE 字符串里;JDBC/驱动层 PreparedStatement 的 ? 由驱动转换,完全不受影响。

五、本篇结论汇总

测试项 MySQL 8.0 KingbaseES V9R3C18 迁移动作
MySQL 线协议直连(客户端/原生驱动) ❌ 协议不互通 换驱动
kingbase8 / 原生 PG JDBC 驱动 ✅ 全通 改 URL + 换 jar
JDBC PreparedStatement ? 绑参 ✅ 一致
ROW_NUMBER/RANK(含 JSON 表达式分区) ✅ 一致
SUM OVER / LAG / NTILE ✅ 一致
PREPARE ... FROM 语句形式 ✅ 支持
SET @var + EXECUTE ... USING ✅ 支持
PREPARE 字符串内 ? 占位符 ❌ 需改 $1..$N 一处正则替换

两篇合起来,"从兼容到进阶"这条路算是走完了:语法层高度兼容(上篇 12 项测 9 项全绿)、协议层走 PG 生态(本篇实锤)、进阶特性窗口函数全绿、动态 SQL 一个占位符差异。对一个真实的 MySQL 业务来说,改造清单已经能列得很具体:换驱动 jar、改连接 URL、扫描替换 INSERT()/FORMAT()/PREPARE 里的 ?,其余大盘不动。

六、给金仓的建议

  1. 文档里把"协议层"和"语法层"的兼容边界写在最显眼的位置。“MySQL 全栈兼容"的宣传语容易让人以为 Connector/J 能直连,第一次连接失败的体验杀伤力很大——一页"驱动与连接迁移对照表”(MySQL 驱动 → 对应金仓/PG 驱动 + URL 写法)就能解决。

  2. 评估提供 MySQL 线协议代理的可行性。哪怕作为独立组件(类似 AWS Babelfish 对 SQL Server 的做法),能让 mysql 客户端和原生驱动直连,"无缝迁移"才算真正闭环。

  3. PREPARE 字符串内支持 ? 占位符,或在报错信息里提示"请使用 $N 占位符"——现在的 syntax error at end of input 让人完全摸不着头脑,我是靠逐段二分才定位到 ? 上的。

  4. mysql 客户端连接失败是挂起等超时而不是立即拒绝,排查体验不好;如果服务端能对非 PG 协议握手快速断开甚至回一句错误,会友好很多。

系列下一篇考虑走 DBA 方向:备份恢复(sys_rman)+ 主备集群搭建。评估数据库不能只看"跑得对",还得看"坏得起"

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐