首页 > 数据库 > 国内数据库 > 它以为自己连的是MongoDB
前两天有幸拿到 KingbaseES V9R2C13(MongoDB兼容版)内测软件包,我第一件事是找台机器装上,然后把 Node.js 的 mongodb 驱动直接连过去。验证是否真的”兼容 MongoDB”。
环境是一台 Oracle Linux 8.10 x86 主机。软件放 /opt/kingbase/KingbaseES,数据放 /opt/kingbase/data,kingbase 用户跑库,systemd 管进程。
驱动用的是 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 负责服务启停和开机自启。
documentdb_core documentdb documentdb_extended_rum
驱动用 MongoDB URI 连接,认证方式按手册显式指定成 SCRAM-SHA-256。应用代码那边继续用 MongoClient、Db、Collection,没有额外包装。
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。后面所有集合创建、文档写入、查询和更新,都在这个连接上做完。
先测的是 ObjectId、Date、Binary、Decimal128、数组和嵌套文档的写入与读取。深度用例还专门塞了 4MB 的文档和 50 层嵌套的文档,读回来核对长度和层级。
查询覆盖了范围条件、逻辑条件、数组匹配、$exists、$type、$regex 和 $expr。排序、分页、投影、计数、distinct 也都进了回归脚本。
更新这块用例最多:$inc、$mul、$min、$max、$currentDate、$rename、$unset、$bit,加上 $push、$pull、$addToSet、$pop 这些数组操作。
arrayFilters 我是单独挑出来测的。订单明细、库存条目、多层配置,改数组里指定的某一条是日常写法,兼容层最容易在这种地方露馅。
await orders.updateOne(
{ orderNo: 'SO-20260723' },
{ $set: { 'items.$[item].status': '已发货' } },
{ arrayFilters: [{ 'item.sku': 'SKU-1001' }] }
);
结果是通过的:只有 SKU-1001 那条明细的状态变了,同一数组里的其他明细原样没动。upsert、$setOnInsert、replaceOne、updateMany、deleteMany 一并过了。bulkWrite 的有序和无序两种模式分别跑,写入条数和最终文档内容都和预期一致。
索引测了普通、复合、唯一、稀疏、部分、TTL、文本、通配符和哈希。唯一索引撞到重复值时,驱动收到 11000,和原生一致。
TTL 这一项我没停在”索引建出来了”。元数据建得出来,不代表后台任务真的在跑。所以写了一条已经过期的文档,等后台任务执行,再去查剩余数量,结果是 0。
await sessions.createIndex(
{ expireAt: 1 },
{ expireAfterSeconds: 0 }
);
collMod 改 TTL 过期时间也测了,命令会返回旧值和新值。英文文本索引用 $text 加 $search 查关键词,没问题。
基础管道这一层没什么悬念:$match、$project、$group、$sort、$facet、$bucket、$bucketAuto、$sortByCount、$sample。
真正想看的是重的那几个。跨集合的 $lookup、合并结果集的 $unionWith、递归查询的 $graphLookup,各写了单独用例。$setWindowFields 的排名和窗口计算也过了。
const result = await orders.aggregate([
{ $match: { status: '已完成' } },
{ $group: { _id: '$customerId', totalAmount: { $sum: '$amount' }, orderCount: { $sum: 1 }
} },
{ $sort: { totalAmount: -1 } },
{ $limit: 20 }
]).toArray();
$out 把聚合结果写进集合,一次通过。$merge 一开始报错,按 MongoDB 对 on 字段唯一索引的要求补上索引之后完成了合并。这个行为和原生也是一样的。
测试环境开了 documentdb.enableSchemaValidation=on。建集合时定义 $jsonSchema,要求 name 必填且是字符串,age 是非负整数。
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。也就是说,一部分基础数据规则可以压到集合上,不用全堆在应用层。
事务从 startSession 开始,提交和回滚分别测。提交后查得到,回滚后查不到,多集合事务用同样的方式核对。
还有一条用例我比较在意:起一个活动事务,再用 killSessions 把会话干掉。会话结束之后,那个事务的未提交写入没有留在集合里。这条要是不通过,应用异常断连的场景会很难收拾。
GridFS 就是走完一圈:上传文件、下载、比对内容、删除。
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、$setWindowFields、arrayFilters、TTL 后台回收、killSessions 之后的事务清理,这几条是我事先觉得最容易出问题的,结果都过了。$merge 那次报错也不算兼容问题,补上 on 字段的唯一索引就通过,要求和原生 MongoDB 一致。
对已有项目来说,改造入口很清楚:换掉测试环境的连接串,把集合初始化、核心读写、索引和聚合管道跑一遍回归,跑完就知道数据层要动多少。以这次的结果看,用 Node.js 驱动的应用大概率不用动多少代码。
这轮的定位是功能验证,结论是能用,而且用得很顺。接下来我打算拿真实的集合结构和数据量再跑一轮,重点看性能、复制和高可用。功能这一层已经看清楚了,剩下的是把量压上去。各位朋友觉得Kingbase的这个版本如何?
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/23825935/viewspace-3176627/ ,如需转载,请注明出处,否则将追究法律责任。