一、为什么做这次测试
我们组维护的一套会员运营系统跑在 MySQL 8.0 上,这套系统有个很典型的"历史包袱":当年为了快速迭代,会员的标签、偏好设置这类结构不稳定的数据,全都塞进了 JSON 字段。几年下来,业务代码里 ->、->>、JSON_OVERLAPS 满天飞,报表 SQL 里还夹杂着 FORMAT、TIME_FORMAT、INSERT() 这类 MySQL 味道很重的函数。
最近在做数据库国产化替代的选型评估,KingbaseES 的 MySQL 兼容版进入了候选名单。宣传材料上"业务无缝迁移"说得很好,但对我们这种 JSON 重度用户来说,真正的问题只有一个:这些 JSON 操作符和函数,不改一行业务 SQL,能不能直接跑起来?
与其看文档列表,不如自己动手。正好赶上金仓社区的"产品体验官"活动,我把业务里最常用的 JSON 场景抽象成一组测试用例,在 KingbaseES V9R3C18(MySQL 兼容版)上逐条验证。先说结论:大面上确实能"平替",但我也实打实测出了两个函数级差异和一个显示差异------这些细节反而是本文最有价值的部分。
二、环境准备:一个小意外和一个小坑
打开金仓官网下载页选好 V9R3C18(MySQL 兼容版)后遇到第一个意外:安装包只有 Linux 版------X64 通用版,加上海光、飞腾、鲲鹏、兆芯四个国产芯片专用包,没有 Windows 版。我本来打算在本机 Windows 上装,只好换方案,翻出一台闲置的鲲鹏架构云服务器,正好用上官方的鲲鹏(Aarch64)专用包,顺便验证一下"国产数据库 + 国产芯片"这套组合的成色。
最终测试环境:
-
服务器:鲲鹏(aarch64)云主机,8G 内存
-
操作系统:Ubuntu 22.04.5 LTS
-
数据库:KingbaseES V9R3C18(MySQL 兼容版),
KingbaseES_V009R003C018B0003_Kunpeng64_install.iso(1.2G) -
授权:官网提供的开发版授权文件(免费,有效期 365 天)
-
对照组:MySQL 8.0(业务现网版本)

两个实用提示:
提示一:服务器上直接 wget 下载会被 OSS 防盗链拦截 ,报 AccessDenied: You are denied by bucket referer policy,加个 Referer 头就行:
Bash
wget --referer="https://www.kingbase.com.cn/" "<安装包下载地址>"
下载完记得校验,我这边 md5sum 和官网给的 0044c38465e337d601fed1d1f677aaa9 完全一致。
提示二:安装器强制非 root 用户运行(root 执行 setup.sh 会直接退出提示换用户),并且支持静默安装,全程不需要图形界面,对云服务器非常友好:
Bash
# root 下准备用户和目录
useradd -m -s /bin/bash kingbase
mkdir -p /opt/Kingbase && chown -R kingbase:kingbase /opt/Kingbase
mount -o loop KingbaseES_V009R003C018B0003_Kunpeng64_install.iso /mnt/kes
# 静默安装配置(ISO 里有模板 setup/silent.cfg,改两行即可)
cat > /opt/silent_my.cfg <<EOF
INSTALL_PATH=/opt/Kingbase/ES/V9
LICENSE_PATH=/opt/license_dev365.dat
COMPONENTS=Server,Interface
CREATE_SHORTCUT=no
EOF
# 切到 kingbase 用户执行静默安装
su - kingbase -c "sh /mnt/kes/setup.sh -i silent -f /opt/silent_my.cfg"
一分钟左右打出 Installation Success 100%。
接着踩到本次唯一一个真正的坑:初始化时报 libsasl2.so.3: cannot open shared object file 。原因是金仓的二进制按 RHEL/国产 OS 系打包(cyrus-sasl 的 soname 是 .so.3),而 Ubuntu 同一个库的 soname 是 .so.2。库本身是同一个东西,做个软链即可:
Bash
ln -s /usr/lib/aarch64-linux-gnu/libsasl2.so.2 /usr/lib/aarch64-linux-gnu/libsasl2.so.3
ldconfig
这也提醒我们:官方兼容性清单以国产操作系统和 RHEL 系为主,Ubuntu 用户要有处理这类小差异的心理准备。
然后初始化实例。V9R3C18 的 initdb 提供了 -m mysql 参数(这个版本只支持 mysql 模式,可见就是为 MySQL 迁移场景定制的),我还加了 --enable-ci 让字符串比较大小写不敏感,进一步贴近 MySQL 的默认行为:
Bash
initdb -D /opt/Kingbase/ES/V9/data -m mysql -E UTF8 --enable-ci -U system --pwfile=<密码文件>
sys_ctl -D /opt/Kingbase/ES/V9/data -l kingbase.log start
注意默认端口是 54321,不是 MySQL 的 3306(从 MySQL 过来的人第一次连基本都会在这愣一下)。ksql 连上确认版本:
SQL
SELECT version();
-- version
-- -------------------------
-- KingbaseES V009R003C018

三、测试设计:一个真实的会员运营场景
为了让测试贴近业务而不是"Hello World 式"地过函数列表,我把线上表结构简化成一张 member 表:tags 存会员标签(JSON 数组),profile 存会员画像(嵌套 JSON 对象)------这是我们线上最典型的两种 JSON 用法。
SQL
CREATE TABLE member (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
phone VARCHAR(20),
points INT DEFAULT 0,
tags JSON,
profile JSON,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO member (name, phone, points, tags, profile) VALUES
('张伟', '13812345678', 12580, '["vip","sports","digital"]',
'{"level":3,"city":"北京","prefs":{"push":true,"email":false}}'),
('李娜', '13987654321', 3200, '["reading","travel"]',
'{"level":2,"city":"上海","prefs":{"push":false,"email":true}}'),
('王强', '13711112222', 860, '["sports","travel","food"]',
'{"level":1,"city":"北京","prefs":{"push":true,"email":true}}'),
('赵敏', '13633334444', 25999, '["vip","luxury"]',
'{"level":4,"city":"深圳","prefs":{"push":true,"email":false}}');
这一步先给了我一个好印象:AUTO_INCREMENT、DATETIME DEFAULT CURRENT_TIMESTAMP、JSON 字面量插入,全部原样通过,一个语法错误都没有。
四、实测一:-> 与 ->> 操作符------引号、优先级、布尔值
MySQL 里这对操作符的核心区别:-> 返回 JSON 类型(字符串带双引号),->> 返回去引号后的文本。业务代码里两种写法混用得很厉害,必须都验证:
SQL
SELECT name,
profile->'$.city' AS city_json,
profile->>'$.city' AS city_text
FROM member;
实测输出:
Plain
name | city_json | city_text
------+-----------+-----------
张伟 | "北京" | 北京
李娜 | "上海" | 上海
王强 | "北京" | 北京
赵敏 | "深圳" | 深圳
city_json 带引号、city_text 裸文本,和 MySQL 分毫不差。接着是 WHERE 条件和运算优先级,这才是真正决定"业务 SQL 要不要改"的地方:
SQL
-- 1. ->> 取文本后和字符串比较:命中 张伟、王强 ✓
SELECT name FROM member WHERE profile->>'$.city' = '北京';
-- 2. -> 取 JSON 标量后和数字比较:命中 张伟、赵敏 ✓
SELECT name FROM member WHERE profile->'$.level' >= 3;
-- 3. 优先级验证:正确解析为 (profile->>'$.level') + 1,输出 4/3/2/5 ✓
SELECT name, profile->>'$.level' + 1 AS next_level FROM member;
-- 4. 嵌套路径 + 布尔值,两种写法:均命中 张伟、王强、赵敏 ✓
SELECT name FROM member WHERE profile->>'$.prefs.push' = 'true';
SELECT name FROM member WHERE profile->'$.prefs.push' = true;
第 3 条是活动海报里点名的"操作符优先级"问题:如果解析器优先级处理不对,->>'$.level' + 1 可能被解析成对路径做加法直接报错。实测正确地先取值再运算。第 4 条的 JSON 布尔值和 SQL 布尔值隐式转换是各家数据库最容易出差异的角落,两种写法结果都与 MySQL 一致------这四条全绿,说明 ->/->> 这一层可以放心。

顺手测了一个 MySQL 做不到的写法------->**** 链式调用:
SQL
SELECT profile->'$.prefs'->>'$.push' AS probe_chain FROM member LIMIT 1;
-- 输出:true
MySQL 8.0 里 -> 左侧必须直接是 JSON 列,这种链式写法会报语法错误;KingbaseES 直接支持。这是它 PostgreSQL 基因带来的超集能力:迁移过去的旧 SQL 不用改,新写的 SQL 还能更灵活。
五、实测二:JSON_OVERLAPS------运营圈人的核心函数
JSON_OVERLAPS 是 MySQL 8.0.17 才加入的较新函数,判断两个 JSON 文档是否有交集。我们运营后台用它做"任一标签命中"的人群圈选,属于一旦不兼容就要改代码的硬依赖:
SQL
-- 圈出打了 sports 或 travel 任一标签的会员("或"语义)
SELECT name, tags FROM member
WHERE JSON_OVERLAPS(tags, '["sports","travel"]');
-- 对比:JSON_CONTAINS 要求候选集整体被包含("且"语义)
SELECT name, tags FROM member
WHERE JSON_CONTAINS(tags, '["sports","travel"]');
实测输出:
Plain
-- JSON_OVERLAPS:三人命中
name | tags
------+------------------------------
张伟 | ["vip", "sports", "digital"]
李娜 | ["reading", "travel"]
王强 | ["sports", "travel", "food"]
-- JSON_CONTAINS:仅王强(两个标签都有)
name | tags
------+------------------------------
王强 | ["sports", "travel", "food"]
一个"或"一个"且",两个函数的结果集都与 MySQL 精确一致。连 JSON_OVERLAPS 这种 8.0.17 才出现的新函数都覆盖到了,兼容的版本追得比我预期的紧。

六、实测三:字符串与格式化函数------终于测出真差异了
前面一路全绿,到报表函数这里终于出现了本次测试最有价值的发现。还是用真实风格的"脱敏报表"来测:
SQL
SELECT
CONCAT(LEFT(name, 1), '**') AS masked_name,
INSERT(phone, 4, 4, '****') AS masked_phone,
FORMAT(points, 0) AS points_display
FROM member
WHERE profile->>'$.city' = '北京';
差异一:INSERT() 字符串函数不存在。 上面的查询直接报错:
Plain
ERROR: function INSERT(varchar, integer, integer, unknown) does not exist
LINE 3: INSERT(phone, 4, 4, '****') AS masked_phone,
HINT: No function matches the given name and argument types.
MySQL 里 INSERT(str, pos, len, newstr) 是个和 INSERT INTO 语句同名的字符串替换函数,我们用它做手机号打码。KingbaseES V9R3C18 没有实现这个函数。好在替代方案是现成的,实测两种都可行:
SQL
-- 方案 A:PostgreSQL 系的 OVERLAY,语义完全对应
SELECT OVERLAY(phone PLACING '****' FROM 4 FOR 4) FROM member;
-- 输出:138****5678 ✓
-- 方案 B:LEFT + RIGHT 拼接,两边数据库通用
SELECT CONCAT(LEFT(phone,3), '****', RIGHT(phone,4)) FROM member;
-- 输出:138****5678 ✓
差异二:FORMAT() 不加千分位。 把 INSERT() 从查询里拿掉后报表能跑了,但 FORMAT(points, 0) 输出的是 12580------MySQL 会输出 12,580(千分位逗号正是这个函数在报表里存在的意义)。函数存在、不报错、但行为不同,这种"静默差异"比直接报错更值得警惕,因为它能骗过冒烟测试,直到业务方盯着报表问"逗号哪去了"。
差异三(显示层):|| 的语义与显示。 MySQL 默认把 || 当逻辑或(除非开 PIPES_AS_CONCAT),PG 系把它当字符串拼接,这是两个阵营的经典分歧。实测 KingbaseES 的 MySQL 模式语义站在了 MySQL 这边:
SQL
SELECT profile->>'$.city' || '市' FROM member LIMIT 1; -- 输出 f(当作逻辑或运算)
SELECT 1 || 0 AS or_probe; -- 输出 t
语义对齐了,但布尔结果显示为 PG 风格的 t/f 而不是 MySQL 的 1/0------SQL 层不用改,但如果应用代码里有按字符串 '1'/'0' 解析布尔结果的逻辑就要留意。迁移建议很简单:字符串拼接一律写 ****CONCAT(),它在两边行为完全一致。
剩下两个函数顺利通过:
SQL
SELECT TIME_FORMAT('09:05:30', '%H小时%i分'); -- 09小时05分 ✓
SELECT TIME_FORMAT('134:05:30', '%H小时%i分'); -- 134小时05分 ✓(>24h 的 TIME 也正确)
-- QUOTE 生成补数 SQL,转义正确
SELECT CONCAT('UPDATE member SET points = points + 100 WHERE name = ',
QUOTE(name), ';')
FROM member WHERE JSON_OVERLAPS(tags, '["vip"]');
-- 输出:UPDATE member SET points = points + 100 WHERE name = '张伟'; 等两条 ✓

七、兼容性结论汇总
以下均为本人在鲲鹏服务器上的真实测试结果(KingbaseES V009R003C018):
| 测试项 | 业务场景 | MySQL 8.0 | KingbaseES V9R3C18 |
|---|---|---|---|
-> / ->> 基础提取 |
画像字段读取 | ✅ | ✅ 完全一致 |
->> 参与算术运算(优先级) |
等级计算 | ✅ | ✅ 先取值后运算 |
| 嵌套路径 + JSON 布尔比较 | 推送偏好过滤 | ✅ | ✅ 两种写法均一致 |
JSON_OVERLAPS |
标签圈人(或语义) | ✅ 8.0.17+ | ✅ 结果集一致 |
JSON_CONTAINS |
标签圈人(且语义) | ✅ | ✅ 结果集一致 |
LEFT / RIGHT / CONCAT |
姓名/手机脱敏 | ✅ | ✅ 一致 |
QUOTE |
动态 SQL 转义 | ✅ | ✅ 一致 |
TIME_FORMAT(含 >24h) |
时长格式化 | ✅ | ✅ 一致 |
INSERT() 字符串函数 |
手机号打码 | ✅ | ❌ 函数不存在,用 OVERLAY 或 LEFT+RIGHT 替代 |
FORMAT() 千分位 |
报表数字展示 | 12,580 |
⚠️ 输出 12580,无千分位 |
-> 链式调用 |
深层 JSON 提取 | ❌ 不支持 | ✅ 支持(超集能力) |
12 个测试点:9 个完全一致,1 个函数缺失(有平滑替代),1 个行为差异,1 个显示差异,外加 1 个超集能力。
八、总结与给金仓的建议
结论:以我们这套 JSON 重度使用的会员系统为样本,KingbaseES V9R3C18 的 MySQL 兼容做到了"核心路径零改造"------JSON 操作符、JSON 函数、主流字符串函数原样可用;但它不是 100% 的像素级复刻,INSERT() 缺失和 FORMAT() 千分位差异说明迁移前的兼容性扫描不能省。 对选型来说这是个合格偏好的答卷:差异都集中在函数边缘地带,且都有一行代码级的替代方案;而链式 -> 这类超集能力算是意外之喜。
安装体验上,静默安装对云服务器很友好,开发版授权免费好拿;扣分项是 Ubuntu 不在一线支持清单(libsasl2 软链那个坑)和 Windows 安装包的缺席。
给金仓团队的几点建议(也算体验官的本职):
-
补齐
INSERT()字符串函数 ,并让FORMAT()输出千分位------这两个都是报表场景高频函数,属于"补上就少一批迁移工单"的性价比之王。 -
提供"MySQL 函数/操作符兼容矩阵"官方速查表,把"完全一致 / 行为有差异 / 未实现+替代方案"标清楚。我这篇文章本质上就是在人肉生成这张表的一小角。
-
Ubuntu/Debian 系的依赖提示 :安装器检测到缺
libsasl2.so.3时给出软链或安装建议,比让用户自己 ldd 排查友好得多。 -
布尔值显示 :MySQL 模式下把布尔结果显示为
1/0而非t/f,能再消灭一类应用层适配。
下一篇我计划测 MySQL 原生驱动/JDBC 直连 + 窗口函数与 PREPARE/EXECUTE,把"从兼容到进阶"这条路走完。有同样在做替代评估的朋友,欢迎评论区交流你们踩过的坑。