1. 概述
本文将介绍 SirixDB 是什么,以及它的核心设计目标。接着会带你了解 SirixDB 提供的基于游标的事务性低级 API。
SirixDB 是一个支持时间版本的 NoSQL 文档数据库,适用于处理数据演化场景。它不会覆盖磁盘上的任何数据,因此可以高效地恢复和查询资源的完整版本历史。此外,SirixDB 在存储开销上也做了优化。
如果你对构建轻量级文档数据库、版本控制、数据恢复等方面感兴趣,那么这篇文章非常适合你。
2. SirixDB 特性
SirixDB 是一个日志结构、支持时间版本的 NoSQL 文档数据库,内置了两种原生数据模型:二进制 XML 存储和 JSON 存储。
2.1. 设计目标
SirixDB 的核心设计目标包括以下几点:
✅ 并发性:使用极少锁机制,适用于多线程系统
✅ 异步 REST API:操作可独立进行,每个事务绑定特定版本,支持一个写事务与多个只读事务并发执行
✅ 版本控制/历史记录:最小存储开销下保存资源的完整历史版本,读写性能可调
✅ 数据完整性:类似 ZFS,父节点存储子节点页面的完整校验和,可检测数据损坏
✅ 写时复制(Copy-on-Write):数据不会被覆盖,修改时复制到新位置
✅ 按记录/按版本存储:不仅按页版本化,还支持按记录版本化,减少复制量
✅ 原子性保证(无 WAL):无须写前日志即可保证系统一致性,断电不会导致数据损坏
✅ 日志结构 & SSD 友好:批量写入,顺序提交,不覆盖已提交数据
SirixDB 的低级 API 支持 JSON 和 XML 资源的存储、遍历与比较。后续文章中我们还会介绍更高层次的 API,如 XQuery、RESTful API 等。
使用 SirixDB 至少需要 Java 11。
3. Maven 依赖配置
要使用 SirixDB,可以通过 Maven 引入 sirix-core
依赖:
<dependency>
<groupId>io.sirix</groupId>
<artifactId>sirix-core</artifactId>
<version>0.9.3</version>
</dependency>
或者使用 Gradle:
dependencies {
implementation 'io.sirix:sirix-core:0.9.3'
}
4. SirixDB 的树结构编码
SirixDB 中的每个节点通过以下方式引用其他节点:
firstChild/leftSibling/rightSibling/parentNodeKey/nodeKey
节点 ID 是由顺序生成器生成的唯一稳定 ID。
每个节点可以拥有第一个子节点、左兄弟、右兄弟和父节点。此外,SirixDB 还支持记录子节点数量、后代数量以及节点哈希值。
5. 创建一个包含单个资源的数据库
下面是一个创建数据库并导入 JSON 文件的示例:
var pathToJsonFile = Paths.get("jsonFile");
var databaseFile = Paths.get("database");
Databases.createJsonDatabase(new DatabaseConfiguration(databaseFile));
try (var database = Databases.openJsonDatabase(databaseFile)) {
database.createResource(ResourceConfiguration.newBuilder("resource").build());
try (var manager = database.openResourceManager("resource");
var wtx = manager.beginNodeTrx()) {
wtx.insertSubtreeAsFirstChild(JsonShredder.createFileReader(pathToJsonFile));
wtx.commit();
}
}
说明:
- 首先创建数据库,然后创建一个资源
- 使用
wtx
开启一个写事务并导入 JSON 文件 - 使用 Java 的 try-with-resources 自动关闭资源
⚠️ 注意:XML 资源的创建方式与此类似,只是使用的 API 略有不同。
6. 打开数据库资源并进行遍历
6.1. JSON 资源的前序遍历
我们可以使用只读事务来遍历整个 JSON 资源:
try (var database = Databases.openJsonDatabase(databaseFile);
var manager = database.openResourceManager("resource");
var rtx = manager.beginNodeReadOnlyTrx()) {
new DescendantAxis(rtx, IncludeSelf.YES).forEach((unused) -> {
switch (rtx.getKind()) {
case OBJECT:
case ARRAY:
LOG.info(rtx.getDescendantCount());
LOG.info(rtx.getChildCount());
LOG.info(rtx.getHash());
break;
case OBJECT_KEY:
LOG.info(rtx.getName());
break;
case STRING_VALUE:
case BOOLEAN_VALUE:
case NUMBER_VALUE:
case NULL_VALUE:
LOG.info(rtx.getValue());
break;
default:
}
});
}
- 使用
DescendantAxis
进行前序(深度优先)遍历 - 节点哈希值默认是自底向上构建的
- 数组和对象节点没有名称和值字段
SirixDB 提供了多种遍历轴,包括:
✅ XPath 所有轴
✅ LevelOrderAxis(层序遍历)
✅ PostOrderAxis(后序遍历)
✅ NestedAxis(链式轴)
✅ ConcurrentAxis(并发遍历)
接下来我们介绍 VisitorDescendantAxis,它可以根据节点类型执行不同的操作。
6.2. VisitorDescendantAxis 遍历
SirixDB 支持使用访问者模式定义节点行为。我们可以为 VisitorDescendantAxis
设置一个访问者:
public enum VisitResultType implements VisitResult {
SKIPSIBLINGS,
SKIPSUBTREE,
CONTINUE,
TERMINATE
}
SKIPSIBLINGS
:跳过当前节点的兄弟节点SKIPSUBTREE
:跳过当前节点的子树CONTINUE
:继续遍历TERMINATE
:立即终止遍历
默认每个访问方法返回 CONTINUE
,所以我们只需重写感兴趣的节点方法即可。
使用方式如下:
var axis = VisitorDescendantAxis.newBuilder(rtx)
.includeSelf()
.visitor(new MyVisitor())
.build();
while (axis.hasNext()) axis.next();
MyVisitor
实现了Visitor
接口- 每次遍历调用对应的
visit()
方法
6.3. 时间轴(Time Travel Axis)
SirixDB 支持“时间旅行”功能,可以遍历某个节点在不同版本中的状态。
使用 TimeAxis
可以遍历指定节点在不同修订版本中的快照:
try (var database = Databases.openJsonDatabase(databaseFile);
var manager = database.openResourceManager("resource");
var rtx = manager.beginNodeReadOnlyTrx()) {
new TimeAxis(rtx).forEach((unused) -> {
LOG.info("Revision: " + rtx.getRevision());
LOG.info("Value: " + rtx.getValue());
});
}
TimeAxis
遍历指定节点在所有修订版本中的状态- 可用于查看某个字段的历史变化
✅ 适用场景:审计、数据回滚、变更追踪等
以上内容为 SirixDB 的核心使用介绍,后续我们将继续介绍其高级 API 与应用场景。