MySQL 的 JSON 字段迁到金仓还能用吗?——KingbaseES V9R3C18 JSON 操作符与函数兼容实测

一、为什么做这次测试

我们组维护的一套会员运营系统跑在 MySQL 8.0 上,这套系统有个很典型的"历史包袱":当年为了快速迭代,会员的标签、偏好设置这类结构不稳定的数据,全都塞进了 JSON 字段。几年下来,业务代码里 ->->>JSON_OVERLAPS 满天飞,报表 SQL 里还夹杂着 FORMATTIME_FORMATINSERT() 这类 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 &amp;&amp; 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_INCREMENTDATETIME 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() 字符串函数 手机号打码 ❌ 函数不存在,用 OVERLAYLEFT+RIGHT 替代
FORMAT() 千分位 报表数字展示 12,580 ⚠️ 输出 12580,无千分位
-> 链式调用 深层 JSON 提取 ❌ 不支持 ✅ 支持(超集能力)

12 个测试点:9 个完全一致,1 个函数缺失(有平滑替代),1 个行为差异,1 个显示差异,外加 1 个超集能力。

八、总结与给金仓的建议

结论:以我们这套 JSON 重度使用的会员系统为样本,KingbaseES V9R3C18 的 MySQL 兼容做到了"核心路径零改造"------JSON 操作符、JSON 函数、主流字符串函数原样可用;但它不是 100% 的像素级复刻,INSERT() 缺失和 FORMAT() 千分位差异说明迁移前的兼容性扫描不能省。 对选型来说这是个合格偏好的答卷:差异都集中在函数边缘地带,且都有一行代码级的替代方案;而链式 -> 这类超集能力算是意外之喜。

安装体验上,静默安装对云服务器很友好,开发版授权免费好拿;扣分项是 Ubuntu 不在一线支持清单(libsasl2 软链那个坑)和 Windows 安装包的缺席。

给金仓团队的几点建议(也算体验官的本职):

  1. 补齐 INSERT() 字符串函数 ,并让 FORMAT() 输出千分位------这两个都是报表场景高频函数,属于"补上就少一批迁移工单"的性价比之王。

  2. 提供"MySQL 函数/操作符兼容矩阵"官方速查表,把"完全一致 / 行为有差异 / 未实现+替代方案"标清楚。我这篇文章本质上就是在人肉生成这张表的一小角。

  3. Ubuntu/Debian 系的依赖提示 :安装器检测到缺 libsasl2.so.3 时给出软链或安装建议,比让用户自己 ldd 排查友好得多。

  4. 布尔值显示 :MySQL 模式下把布尔结果显示为 1/0 而非 t/f,能再消灭一类应用层适配。

下一篇我计划测 MySQL 原生驱动/JDBC 直连 + 窗口函数与 PREPARE/EXECUTE,把"从兼容到进阶"这条路走完。有同样在做替代评估的朋友,欢迎评论区交流你们踩过的坑。

相关推荐
IpdataCloud2 小时前
跨境AI金融风险怎么防?IP风险画像的实战应用指南
数据库·tcp/ip·金融·ip
Alan_754 小时前
API 接口慢调用根因定位:从 TCP 建连到数据库 IO 的全栈排查实战
数据库
多巴胺梦想家5 小时前
事务与并发控制:当多人同时操作数据库
服务器·数据库·oracle
howard20055 小时前
PostgreSQL起步
数据库·postgresql
秋田君6 小时前
QT_QT布局详解
开发语言·数据库·qt
可乐ea6 小时前
【Redis八股|第8篇】Redis 分布式锁原理与 Redisson 使用
数据库·redis·分布式·面试题·redis八股
李燚7 小时前
Eino devops 调试系统源码:GraphCompileCallback 怎么工作(第50篇-E36)
数据库·golang·agent·devops·graphql·aiagent·eino
ClouGence7 小时前
Oracle 到 OceanBase 迁移方案横评:停机导出/导入 vs OMS vs CDC 工具
数据库·oracle
Meya11277 小时前
不同规模机房怎么选 U 位系统?8 柜小型机房、40 柜数据中心完整选型参考
服务器·网络·数据库