它以为自己连的是MongoDB

前两天有幸拿到 KingbaseES V9R2C13(MongoDB兼容版)内测软件包,我第一件事是找台机器装上,然后把 Node.js 的 mongodb 驱动直接连过去。验证是否真的"兼容 MongoDB"。

环境是一台 Oracle Linux 8.10 x86 主机。软件放 /opt/kingbase/KingbaseES,数据放 /opt/kingbase/data,kingbase 用户跑库,systemd 管进程。

服务起来之后有个细节值得说:同一个 KingbaseES 进程监听了两个端口。54321 走 SQL,27017 走 MongoDB 协议。一套实例,两个入口。

驱动用的是 mongodb@6.21.0。基础回归过了 19 个场景,深度探测过了 38 个。几组我心里没底的用例,另外在 MongoDB 7.0.28 上跑了同一份 Node.js 脚本,两边对返回值和数据变化。

关于安装

原厂 ISO,setup.sh -i console,一路装完。目录和权限按常规数据库服务的习惯来:软件目录 /opt/kingbase/KingbaseES,数据目录 /opt/kingbase/data,kingbase 用户运行数据库,systemd 负责服务启停和开机自启。

MongoDB 协议这块能力来自 DocumentDB 扩展。测试库装了三个,版本都是 0.110-0:

text 复制代码
documentdb_core
documentdb
documentdb_extended_rum

代码还是熟悉的写法

驱动用 MongoDB URI 连接,认证方式按手册显式指定成 SCRAM-SHA-256。应用代码那边继续用 MongoClient、Db、Collection,没有额外包装。

javascript 复制代码
const { MongoClient } = require('mongodb');

const uri =
  'mongodb://<user>:<password>@127.0.0.1:27017/test' +
  '?authSource=admin' +
  '&authMechanism=SCRAM-SHA-256' +
  '&directConnection=true';

const client = new MongoClient(uri);
await client.connect();

const db = client.db('test');
const users = db.collection('users');
await users.insertOne({ name: 'Alice', tags: ['mongo', 'kingbase'] });

console.log(await users.findOne({ name: 'Alice' }));
await client.close();

握手回来 hello.ok = 1,maxWireVersion = 21,buildInfo.version = 7.0.42。驱动那边认为自己连的是一个 7.0 的 MongoDB。后面所有集合创建、文档写入、查询和更新,都在这个连接上做完。

BSON 和文档操作

先测的是 ObjectId、Date、Binary、Decimal128、数组和嵌套文档的写入与读取。深度用例还专门塞了 4MB 的文档和 50 层嵌套的文档,读回来核对长度和层级。

查询覆盖了范围条件、逻辑条件、数组匹配、exists、exists、exists、type、regex 和 expr。排序、分页、投影、计数、distinct 也都进了回归脚本。

更新这块用例最多:inc、inc、inc、mul、min、min、min、max、currentDate、currentDate、currentDate、rename、unset、unset、unset、bit,加上 push、push、push、pull、addToSet、addToSet、addToSet、pop 这些数组操作。

arrayFilters 我是单独挑出来测的。订单明细、库存条目、多层配置,改数组里指定的某一条是日常写法,兼容层最容易在这种地方露馅。

javascript 复制代码
await orders.updateOne(
  { orderNo: 'SO-20260723' },
  { $set: { 'items.$[item].status': '已发货' } },
  { arrayFilters: [{ 'item.sku': 'SKU-1001' }] }
);

结果是通过的:只有 SKU-1001 那条明细的状态变了,同一数组里的其他明细原样没动。upsert、$setOnInsert、replaceOne、updateMany、deleteMany 一并过了。bulkWrite 的有序和无序两种模式分别跑,写入条数和最终文档内容都和预期一致。

TTL 确实删掉了过期文档

索引测了普通、复合、唯一、稀疏、部分、TTL、文本、通配符和哈希。唯一索引撞到重复值时,驱动收到 11000,和原生一致。

TTL 这一项我没停在"索引建出来了"。元数据建得出来,不代表后台任务真的在跑。所以写了一条已经过期的文档,等后台任务执行,再去查剩余数量,结果是 0。

javascript 复制代码
await sessions.createIndex(
  { expireAt: 1 },
  { expireAfterSeconds: 0 }
);

collMod 改 TTL 过期时间也测了,命令会返回旧值和新值。英文文本索引用 text 加 search 查关键词,没问题。

聚合跑到了关联、递归和窗口计算

基础管道这一层没什么悬念:match、match、match、project、group、group、group、sort、facet、facet、facet、bucket、bucketAuto、bucketAuto、bucketAuto、sortByCount、$sample。

真正想看的是重的那几个。跨集合的 lookup、合并结果集的 unionWith、递归查询的 graphLookup,各写了单独用例。graphLookup,各写了单独用例。graphLookup,各写了单独用例。setWindowFields 的排名和窗口计算也过了。

javascript 复制代码
const result = await orders.aggregate([
  { $match: { status: '已完成' } },
  { $group: {
      _id: '$customerId',
      totalAmount: { $sum: '$amount' },
      orderCount: { $sum: 1 }
  } },
  { $sort: { totalAmount: -1 } },
  { $limit: 20 }
]).toArray();

out把聚合结果写进集合,一次通过。out 把聚合结果写进集合,一次通过。out把聚合结果写进集合,一次通过。merge 一开始报错,按 MongoDB 对 on 字段唯一索引的要求补上索引之后完成了合并。这个行为和原生也是一样的。

JSON Schema 拦得住不合规文档

测试环境开了 documentdb.enableSchemaValidation=on。建集合时定义 $jsonSchema,要求 name 必填且是字符串,age 是非负整数。

javascript 复制代码
await db.createCollection('validated_users', {
  validator: {
    $jsonSchema: {
      bsonType: 'object',
      required: ['name'],
      properties: {
        name: { bsonType: 'string' },
        age: { bsonType: 'int', minimum: 0 }
      }
    }
  }
});

合规文档正常写入。缺 name、name 类型不对、age 小于 0,数据库都返回 121 Document failed validation。也就是说,一部分基础数据规则可以压到集合上,不用全堆在应用层。

事务和 GridFS

事务从 startSession 开始,提交和回滚分别测。提交后查得到,回滚后查不到,多集合事务用同样的方式核对。

还有一条用例我比较在意:起一个活动事务,再用 killSessions 把会话干掉。会话结束之后,那个事务的未提交写入没有留在集合里。这条要是不通过,应用异常断连的场景会很难收拾。

GridFS 就是走完一圈:上传文件、下载、比对内容、删除。

javascript 复制代码
const bucket = new GridFSBucket(db, { bucketName: 'attachments' });
const uploadId = new ObjectId();

await new Promise((resolve, reject) => {
  const stream = bucket.openUploadStreamWithId(uploadId, 'report.pdf');
  stream.once('finish', resolve);
  stream.once('error', reject);
  stream.end(Buffer.from('report content'));
});

这轮测下来,我的判断

兼容度比我预期的高。

19 加 38 个场景,从 BSON 类型、CRUD、索引,一路到聚合、JSON Schema、事务和 GridFS,官方驱动全程没换写法,也没有为了绕开哪个不支持的特性去改代码。graphLookup、graphLookup、graphLookup、setWindowFields、arrayFilters、TTL 后台回收、killSessions 之后的事务清理,这几条是我事先觉得最容易出问题的,结果都过了。$merge 那次报错也不算兼容问题,补上 on 字段的唯一索引就通过,要求和原生 MongoDB 一致。

对已有项目来说,改造入口很清楚:换掉测试环境的连接串,把集合初始化、核心读写、索引和聚合管道跑一遍回归,跑完就知道数据层要动多少。以这次的结果看,用 Node.js 驱动的应用大概率不用动多少代码。

双端口是我觉得最实用的一点。老系统继续走 54321 的 SQL 口,新应用走 27017 的 MongoDB 口,数据平台按自己的习惯选。存量系统改造最怕的就是"一次性切换",这个设计直接把它变成了可以分阶段推进的事。

这轮的定位是功能验证,结论是能用,而且用得很顺。接下来我打算拿真实的集合结构和数据量再跑一轮,重点看性能、复制和高可用。功能这一层已经看清楚了,剩下的是把量压上去。各位朋友觉得Kingbase的这个版本如何?

相关推荐
会编程的土豆1 小时前
数据库mysql八股
数据库·笔记·八股
nVisual2 小时前
巡检任务与工单闭环方案
运维·服务器·数据库·数据中心基础设施
承渊政道2 小时前
从设备数据到AI洞察:时序数据的多模融合实践
数据库·人工智能·性能优化·金仓数据库·多模融合
科技圈快迅2 小时前
2026年数据库国产化替代性能调优方法论:从SQL基线建立到持续优化的全流程体系
数据库·sql
你有我备注吗2 小时前
SQL之数据更新
数据库·sql
跨境技工小黎2 小时前
动态住宅代理使用指南:粘性会话 vs. 每次请求轮换 IP,如何选择?
数据库
AAA@峥4 小时前
系统化学习 MySQL:数据类型、库表管理、增删改查全解析
数据库·学习·mysql
流星白龙11 小时前
【Redis】2.Redis重大版本
数据库·redis·junit
流星白龙11 小时前
【Redis】7.Hash表
数据库·redis·哈希算法