这篇写给“真要替”的人:你手上有一套 MongoDB 业务,准备收敛到金仓数据库(KingbaseES),但又不想把风险全押在一句“兼容”上。我更习惯用工程的方式把事情说透:先把适用场景划清楚,再把能在 Windows + ksql 上跑通的实操摆出来,最后再用一套验收口径把闭环做完整。

文章目录
-
- 1. 所谓“MongoDB兼容”,到底兼容到哪
- 2. 适用场景:哪些 MongoDB 业务我更愿意往金仓上收敛
- 3. 兼容性拆解:从 MongoDB 文档到金仓 JSONB 的映射思路
- 4. Windows + ksql 实操:用一张表把“存、查、改、索引”跑通
-
- 4.1 连接数据库
- 4.2 建表:一列 JSONB + 一列主键 + 一列高频冗余字段
- 4.3 插入文档:用 JSONB 直接落库
- 4.4 查询:按键/键值对过滤(更接近文档库的使用方式)
- 4.5 索引:给 JSONB 上 GIN,让“按键/键值对查”不再纯靠全表扫
- 4.6 更新:只改文档里的某一块(把“局部更新”写成可维护的 SQL)
- 5. 迁移步骤:从 MongoDB 导出到金仓落库(可回放、可重跑)
-
- 5.1 导出:建议用 JSON Lines(每行一个文档),方便分片与回放
- 5.2 落库:先落 staging(文本),再显式 cast 成 JSONB
- 6. 应用案例:我见过最“值回票价”的三类替换
-
- 6.1 订单扩展字段:交易表稳住,文档当扩展
- 6.2 用户画像/配置中心:结构常变但查询很固定
- 6.3 审计留痕:可追溯比“写得快”更重要
- 7. 兼容性坑点清单:不提前说清楚,后面一定会还债
- 结语:替不替,先用一套可跑通的闭环说话
1. 所谓“MongoDB兼容”,到底兼容到哪
在项目里聊“MongoDB 兼容性”,我一般会先把它拆成三层,不然讨论很容易跑偏:

2. 适用场景:哪些 MongoDB 业务我更愿意往金仓上收敛
如果你的 MongoDB 主要在做下面这些事,往金仓上收敛通常会更顺手:

- “结构不稳定但要强一致”的业务数据:比如订单扩展字段、风控标签、业务规则快照;一边是 JSON 形态灵活,另一边又需要事务一致性、审计、对账。
- 文档只是“附加信息”:主链路是关系表(用户、订单、账务),文档更多是扩展属性或请求/响应留痕。
- 查询模式可控:大部分查询集中在几个字段上(比如 type/status/uid/时间),而不是任意字段随便查。
- 容量与吞吐在可控范围:你不依赖“自动分片+横向扩容”这种文档库的分布式特性,更多需要的是稳定、可管、可审计。
反过来,如果你特别依赖文档库的分布式写入吞吐、天然分片/自动均衡能力,那评估阶段就得把这点写进风险清单:别指望靠一列 JSON 就去硬扛“分布式文档库”的定位。到底能不能替,最后看的是业务负载和 SLA,而不是看一个类型名。
3. 兼容性拆解:从 MongoDB 文档到金仓 JSONB 的映射思路

4. Windows + ksql 实操:用一张表把“存、查、改、索引”跑通
我的环境是 Windows 且在 ksql 上操作,下面所有演示默认满足:
- Windows 已安装 KingbaseES,并创建了 Oracle 模式实例
- 你能在 PowerShell / Windows Terminal 中执行 ksql
如果你在这一步就卡住了(端口不通、服务没起来、ksql 连不上),我之前整理过一篇从安装到连通性验证的笔记,按步骤走基本不会翻车: Windows 安装 KingbaseES V9R1C10 与 Oracle 兼容特性实战
概念解释我就不在这里铺开了,先把环境确认下来更重要:只要你能在 ksql 里把关键语法跑起来,后面这些实操才算“落地”。
下面所有命令都按 Windows 环境、ksql 客户端来写(复制 SQL 本体即可)。
4.1 连接数据库
ksql -h 127.0.0.1 -p 54321 -U system -d test
端口这块按你的实例来,我这边 Oracle 兼容版用的是 54322,所以截图里会看到 54322。 
4.2 建表:一列 JSONB + 一列主键 + 一列高频冗余字段
DROP TABLE IF EXISTS t_user_doc;
CREATE TABLE t_user_doc(
user_id VARCHAR(64) PRIMARY KEY,
doc_type VARCHAR(32) NOT NULL,
doc JSONB NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
我这里把 doc_type 单独冗余出来,是因为它通常就是最高频的过滤条件之一。把它做成普通列再建索引,比每次都从 JSONB 里解析要稳得多。
4.3 插入文档:用 JSONB 直接落库
INSERT INTO t_user_doc(user_id, doc_type, doc)
VALUES
('u001', 'profile', '{"name":"张三","age":28,"tags":["vip","beta"],"city":"南京"}'::jsonb),
('u002', 'profile', '{"name":"李四","age":35,"tags":["vip"],"city":"上海"}'::jsonb),
('u003', 'event', '{"event":"login","ip":"10.2.3.4","device":{"os":"windows","ver":"11"}}'::jsonb);
COMMIT;
4.4 查询:按键/键值对过滤(更接近文档库的使用方式)
在金仓的 JSONB 操作符里,@> 表示“左边的 JSONB 在顶层是否包含右边的路径/值项”,? 表示“某个键是否存在”。用这两类条件写查询,会更贴近文档库里那种“按字段过滤”的习惯[
— 查 profile 且 city=上海 的用户
SELECT user_id
FROM t_user_doc
WHERE doc_type = 'profile'
AND doc @> '{"city":"上海"}'::jsonb
ORDER BY user_id;
— 查 doc 顶层是否存在 tags 键
SELECT user_id
FROM t_user_doc
WHERE doc ? 'tags'
ORDER BY user_id;

如果你要把某个字段“抽出来当文本/数字”做排序或聚合,我建议写法上把类型转换摆在明面上,别把隐式转换的坑留到线上才踩:
— 按 age 排序(示例:把 doc 里的 age 转成整数)
SELECT user_id, doc
FROM t_user_doc
WHERE doc_type = 'profile'
ORDER BY (doc–>>'age')::int DESC;

4.5 索引:给 JSONB 上 GIN,让“按键/键值对查”不再纯靠全表扫
手册里明确提到:GIN 索引可用于在大量 JSONB 文档中高效搜索键或键值对,并且也提到了不同操作符类在性能/灵活性上的取舍[^kb-json-datatypes]。这里先用默认做一版最稳:
CREATE INDEX idx_t_user_doc_doc_gin ON t_user_doc USING GIN (doc);
索引建完以后,建议用执行计划确认一下是否真的走了索引(别凭感觉):
EXPLAIN ANALYZE
SELECT user_id
FROM t_user_doc
WHERE doc @> '{"city":"上海"}'::jsonb;

4.6 更新:只改文档里的某一块(把“局部更新”写成可维护的 SQL)
文档库里大家都喜欢“改一个字段就完事”。在金仓里也能做到类似效果:用 JSONB 的拼接/删除等操作符,把更新范围写清楚,后面维护也省心[^kb-json-operators]。
— 给 u001 的文档追加一个字段(示例:last_login)
UPDATE t_user_doc
SET doc = doc || '{"last_login":"2026-02-04 10:00:00"}'::jsonb,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = 'u001';
COMMIT;

5. 迁移步骤:从 MongoDB 导出到金仓落库(可回放、可重跑)
迁移这块我更推荐拆成“可重复执行”的三段:导出、落地、验证。别一次性脚本跑到底,出了问题你会发现最难受的不是修,而是没法回放、没法重跑。
#mermaid-svg-FdaJ8yHhH8Iwwsxo{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .error-icon{fill:#552222;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .marker.cross{stroke:#333333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo p{margin:0;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .cluster-label text{fill:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .cluster-label span{color:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .cluster-label span p{background-color:transparent;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .label text,#mermaid-svg-FdaJ8yHhH8Iwwsxo span{fill:#333;color:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .node rect,#mermaid-svg-FdaJ8yHhH8Iwwsxo .node circle,#mermaid-svg-FdaJ8yHhH8Iwwsxo .node ellipse,#mermaid-svg-FdaJ8yHhH8Iwwsxo .node polygon,#mermaid-svg-FdaJ8yHhH8Iwwsxo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .rough-node .label text,#mermaid-svg-FdaJ8yHhH8Iwwsxo .node .label text,#mermaid-svg-FdaJ8yHhH8Iwwsxo .image-shape .label,#mermaid-svg-FdaJ8yHhH8Iwwsxo .icon-shape .label{text-anchor:middle;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .rough-node .label,#mermaid-svg-FdaJ8yHhH8Iwwsxo .node .label,#mermaid-svg-FdaJ8yHhH8Iwwsxo .image-shape .label,#mermaid-svg-FdaJ8yHhH8Iwwsxo .icon-shape .label{text-align:center;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .node.clickable{cursor:pointer;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .arrowheadPath{fill:#333333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FdaJ8yHhH8Iwwsxo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FdaJ8yHhH8Iwwsxo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FdaJ8yHhH8Iwwsxo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .cluster text{fill:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .cluster span{color:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FdaJ8yHhH8Iwwsxo rect.text{fill:none;stroke-width:0;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .icon-shape,#mermaid-svg-FdaJ8yHhH8Iwwsxo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .icon-shape p,#mermaid-svg-FdaJ8yHhH8Iwwsxo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .icon-shape rect,#mermaid-svg-FdaJ8yHhH8Iwwsxo .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FdaJ8yHhH8Iwwsxo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FdaJ8yHhH8Iwwsxo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FdaJ8yHhH8Iwwsxo :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
梳理集合与查询模式字段使用频率/索引/容量
定义落地模型主键 + JSONB + 冗余列
MongoDB 导出JSON Lines/分片文件
目标库落地staging text -> cast jsonb
一致性验证行数/抽样/关键查询回归
通过?
定位与重跑按文件/按窗口回放
灰度切换双写/只读/回滚预案
5.1 导出:建议用 JSON Lines(每行一个文档),方便分片与回放
导出方式你们团队怎么习惯都行,我只盯两点:
- 一行一个文档,方便按文件/按批次回放
- 保留原始 _id,迁移后方便做抽样对照
5.2 落库:先落 staging(文本),再显式 cast 成 JSONB
先建两张表:一张 staging 用来接文件,一张目标表用来承载 JSONB:
DROP TABLE IF EXISTS stg_mongo_doc;
CREATE TABLE stg_mongo_doc(
user_id VARCHAR(64),
doc_type VARCHAR(32),
doc_raw TEXT
);
用 ksql 的 \\copy 把文件导入 staging(示例:tab 分隔,Windows 路径记得用双反斜杠或直接用单引号包裹):
\\copy stg_mongo_doc(user_id, doc_type, doc_raw) FROM 'D:\\\\mig\\\\user_doc.tsv' WITH (FORMAT csv, DELIMITER E'\\t', HEADER true)
再把 staging 的 doc_raw 显式转成 JSONB 写入目标表:
INSERT INTO t_user_doc(user_id, doc_type, doc)
SELECT user_id, doc_type, doc_raw::jsonb
FROM stg_mongo_doc;
COMMIT;
这种“两段式”落地的好处是:一旦遇到脏数据(JSON 语法不合法),你能很快定位到具体哪一行、哪个文件、哪个批次;而不是整套迁移脚本跑到一半炸掉,剩下的只能靠猜。
6. 应用案例:我见过最“值回票价”的三类替换
6.1 订单扩展字段:交易表稳住,文档当扩展
做电商/交易系统时,订单核心字段(金额、状态、时间)天然适合关系型;但扩展字段(渠道参数、风控标签、营销命中规则)经常变。 做法就是:订单主表走关系字段,扩展字段用 JSONB 挂在同一个库里,避免跨库一致性问题。
6.2 用户画像/配置中心:结构常变但查询很固定
画像的字段多、变更频繁,但业务查询往往只盯几个条件:tag/city/level/更新时间。 这种“字段多但用得少”的模型,用 JSONB + 冗余列 + 索引 很容易跑得又稳又省事。
6.3 审计留痕:可追溯比“写得快”更重要
很多系统要保留请求/响应、变更前后快照。你不一定需要在文档库里做复杂查询,更多是“存得下、查得到、可审计”。 把审计数据落在同一个数据库里,反而更好管控权限、备份与验收口径。
7. 兼容性坑点清单:不提前说清楚,后面一定会还债
文档库中,“没有这个字段”和“字段存在但值为 null”常常被当作同一件事对待,但在转为 SQL 查询的时候,务必在条件里予以区分。
数值精度:JSONB 对数字类型的范围和精度存在限制,手册清楚表明,超出数据库 numeric 范围的数字将会被拒收,JSON 文本类型则不然,迁移之前需专门考量大数/高精度字段。
索引策略:并非要对每个字段均实施索引设置,首先要分析查询模式,然后决定多余列及JSONB索引的设置情况。
不要指望仅靠 JSON 来完成全部解析工作,在线上环境当中,最常遇到的问题就是“把所有的过滤条件都记录在 JSONB 中”,这样最终只能不断增加机器数量,对于那些高频出现的字段来说,应当执行冗余设计。
结语:替不替,先用一套可跑通的闭环说话
我不太相信一句“兼容”能自动解决项目问题。我更相信两件事:
-
你能在本地,把“建表、写入、查询、索引、更新、导入、验证”完整跑一遍;
-
你能把“哪些场景适合替、哪些场景不适合替”写进验收口径里,避免上线后用业务来做实验。
把这两件事做好,MongoDB的替换就不是凭空想象,而是变成一项可以执行,检查,还能回滚的工程项目交付。



