# swordfish **Repository Path**: dolphindb/swordfish ## Basic Information - **Project Name**: swordfish - **Description**: DolphinDB In-memory Database - **Primary Language**: C++ - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-03-17 - **Last Updated**: 2025-05-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Swordfish 参考手册 自 3.00.2 版本起,该 Readme 不再进行维护。用户可移步至:[《Swordfish 概述》](https://docs.dolphindb.cn/zh/swordfish/cpp_swordfish.html)。 **版权声明** *© DolphinDB, Inc。2023。浙江智臾科技有限公司保留所有权利。**未经版权所有者**事先**书面许可**,**本文**档中受版权保护的**任何部分不得以任何形式**或通过**任何**手段(图片、电子或机械方式,包括影印、录像或存储在电子检索系统中)**进行复制**。* **免责声明** 由于产品设计和实现的不断迭代,本文件的内容可能会被修改,恕不另行通知。DolphinDB, Inc. 对因使用本文件而造成的任何错误或损失不承担任何责任。 **适用受众与环境** *受众* Swordfish 是 **DolphinDB, Inc.** 开发的一款嵌入式版本数据库产品。目前提供以下主要功能: - 交易型内存数据库 - DolphinDB 函数库 - DolphinDB 脚本语言 - DolphinDB 批计算引擎和流计算引擎 Swordfish 仅面向付费的商业用户,不提供社区版。商业用户如需测试或试用,请电邮联系 sales@dolphindb.com 或已知的 DolphinDB 销售人员。 通过阅读本手册,能够达成以下目标: - 了解 Swordfish 的基本概念。 - 了解常用使用场景。 - 了解类的基本语法和使用方法。 - 了解版本许可证(license)对于产品使用的影响。 - 掌握流计算引擎在该产品中的基本使用方法。 - 掌握调用函数的方法。 - 掌握面向场景的脚本编写基本方法。 *环境* 本手册描述的产品适用于以下硬件环境和软件环境。 - **硬件配置**:104 Intel(R) Xeon(R) Gold 5320 CPU @ 2.20GHz - **操作系统**:CentOS Linux release 7.9.2009 (Core) **目录** - [Swordfish 参考手册](#swordfish-参考手册) - [系统概述](#系统概述) - [背景](#背景) - [架构](#架构) - [特性](#特性) - [Swordfish 与 DolphinDB](#swordfish-与-dolphindb) - [流计算引擎](#流计算引擎) - [License 有效性检查](#license-有效性检查) - [使用方法](#使用方法) - [运行时环境的启停](#运行时环境的启停) - [打开/创建数据库](#打开创建数据库) - [连接数据库](#连接数据库) - [DDL 操作](#ddl-操作) - [DML 操作](#dml-操作) - [事务处理](#事务处理) - [脚本执行](#脚本执行) - [功能与函数](#功能与函数) - [数据库文件](#数据库文件) - [索引](#索引) - [事务](#事务) - [异常](#异常) - [Low-level DML 接口](#low-level-dml-接口) - [脚本样例](#脚本样例) - [通过运行 DolphinDB 脚本创建流数据引擎](#通过运行-dolphindb-脚本创建流数据引擎) - [流引擎直接输出到内存 OLTP 表](#流引擎直接输出到内存-oltp-表) - [内存 OLTP 表订阅接收引擎输出的流表](#内存-oltp-表订阅接收引擎输出的流表) - [调用自定义函数和内置函数](#调用自定义函数和内置函数) - [智能指针(Smart Pointer)](#智能指针smart-pointer) - [数据写入、插入、查询、更新与删除](#数据写入插入查询更新与删除) - [操作与维护](#操作与维护) - [错误管理](#错误管理) - [配置管理](#配置管理) - [性能测试](#性能测试) - [主键索引查询测试](#主键索引查询测试) - [向空表插入数据测试](#向空表插入数据测试) - [流数据实时计算测试](#流数据实时计算测试) - [数据类型支持](#数据类型支持) - [接口说明](#接口说明) - [参考](#参考) ## 系统概述 ### 背景 在一些金融行业的应用场景中,例如交易系统,其主要工作负载来源于对关系表的高频度、高并发的更新和查询操作。这样的应用场景要求数据的读写和计算能够具有低延迟、高并发的特征,同时保证极高的数据一致性,并提供 ACID 事务的支持。传统的存储引擎由于其架构的设计出发点是将数据存储在磁盘上,在面对上述场景要求时,软硬件层面面临巨大挑战,无法很好地满足上述苛刻的性能要求。 ### 架构 为满足上述金融行业中的场景化需求,**DolphinDB,Inc.** 推出了以内存为数据存储环境,以 DolphinDB 内置函数和支持的自定义函数为功能实现途径,以 C++ 语言编写的运行脚本为数据库操作方式的 Swordfish 链接库式数据库(以下简称 Swordfish)。 ### 特性 Swordfish 将所有数据都存储在内存中, 省去磁盘 I/O 的开销; 以行存的形式来组织数据, 主要适用于 OLTP 的场景, 支持创建 B+ 树索引 (主键索引和二级索引) 来应对高频度、高并发的更新和查询操作。 Swordfish 支持事务, 默认为 snapshot 隔离级别。它同时提供 Write-Ahead-Logging 和 checkpoint 机制以保证数据的持久化。为了加速重启时的恢复过程, Swordfish 应用了并行恢复机制。 ### Swordfish 与 DolphinDB 传统的数据库是 Server/Client 模式, 即使是在同一个进程, 数据也需要通过网络传输。为了省去网络传输的开销, Swordfish 采用了类似于 SQLite 的嵌入式模式, 对外提供一个共享链接库, 用户可以直接用 C++ 代码来操作数据库。 ### 流计算引擎 使用 DolphinDB 处理历史数据时,可以通过 SQL 语句配合内置计算函数进行查询和计算(全量或增量)。但在实时数据流计算场景下,计算要求高效和即时,全量查询和计算则无法满足该场景的需求。因此, DolphinDB 精心研发了适合流计算场景的引擎,系统内部采用了增量计算,优化了实时计算的性能。 实际应用中,流数据引擎的计算结果可以输出到共享内存表、流数据表、消息中间件、数据库、API 等终端,以做进一步处理。计算复杂表达式时,亦可将多个流数据引擎通过级联的方式合并成一个复杂的数据流拓扑。 Swordfish 完全支持 DolphinDB 提供的应对不同计算场景的多种引擎,详情参考:[内置流式计算引擎](https://docs.dolphindb.cn/zh/funcs/themes/streamingEngine.html)。 ### License 有效性检查 社区版 License 暂不支持该功能。如需使用,请前往[官网下载页面](https://dolphindb.cn/product#downloads-top),点击 Swordfish 产品的“试用 License”,申请对应 License。 初始化运行时环境时, 如果 License 中授权的 CPU 核数小于机器实际的 CPU 核数,则 `DolphinDBLib::initializeRuntime` 将会失败,类似以下的信息会记录在log 日志中: ``` The license limits 8 cpu cores, less than the actual cpu cores (12). ``` ## 使用方法 ### 运行时环境的启停 使用 Swordfish 前, 需要先使用 `DolphinDBLib::initializeRuntime` 初始化 DolphinDB 的运行时环境;在所有操作完成后, 应使用 `DolphinDBLib::finalizeRuntime` 销毁 DolphinDB 的运行时环境。 用法: ``` // 初始化 DolphinDB 运行时环境 DolphinDBLib::initializeRuntime(); // ... // 所有数据库操作完成后,销毁 DolphinDB 运行时环境 DolphinDBLib::finalizeRuntime(); ``` ### 打开/创建数据库 打开 (创建) 数据库: ``` using oltp::DBOption; using oltp::DB; // 以默认选项打开或创建数据库 DBOption option; shared_ptr db = make_shared("test_db", option); ``` ### 连接数据库 连接一个已打开的数据库: ``` using oltp::Connection; Connection conn(*db); ``` ### DDL 操作 ``` // 表包含5列 std::vector colDesc; colDesc.emplace_back("a", DT_INT, 0); colDesc.emplace_back("b", DT_INT, 0); colDesc.emplace_back("c", DT_BOOL, 0); colDesc.emplace_back("d", DT_LONG, 0); colDesc.emplace_back("e", DT_STRING, 0); // 以 "a" 作为主键创建主键索引 std::vector pk{"a"}; // 以 "b,c,d" 作为复合键创建 unique 的二级索引 // 以 "d" 作为键创建 non-unique 的二级索引 std::vector>> secondaryKeys{ {true, {"b", "c", "d"}}, {false, {"d"}} }; // 创建表 "table1" conn.createTable("table1", colDesc, pk, secondaryKeys); // 删除表 "table1" conn.dropTable("table"); ``` ### DML 操作 ``` // 插入 conn.insert(...); // 查询 conn.query("table1", {}, conn.makeFilters("a>5,a<10")); conn.execute("select * from table1 where a>5,a<10"); conn.execute("select * from table1 where a>5,a<10 order by a"); // 更新 conn.update("table1", conn.makeColumnUpdateDefs("b=b+1"), conn.makeFilters("a=5")); conn.execute("update table1 set b=b+1 where a=5"); // 删除 conn.remove("table1", conn.makeFilters("a=5")); conn.execute("delete from table1 where a=5"); ``` ### 事务处理 ``` // 开始事务 conn.beginTransaction(); // 执行一系列的 DML 操作 conn.insert(...); conn.update(...); conn.update(...); // 提交事务 conn.commit(); // 或者回滚事务: conn.rollback(); // 或者使用 `transaction` 方法, 在一个事务里面执行多条语句, 最后 commit. conn.transaction([&]() { conn.insert(...); conn.update(...); conn.update(...); }); ``` ### 脚本执行 ``` ConstantSP obj = conn.execute(R"( a = 1; b = 2; a + b // 注意,这里不能加分号 `;`, 否则不会返回运算的结果 )"); ConstantSP obj = conn.execute(R"( select * from table1 order by a // 注意,这里不能加分号 `;`, 否则不会返回查询的结果 )"); ``` ## 功能与函数 ### 数据库文件 举例来说,创建了一个名为 *test_db* 的数据库, 其目录组织如下所示: ![test_db](images/test_db_structure.png) - *LOCK*: 文件锁 - *LOCK_RECOVER*: 文件锁, 用于只读模式 - *test_db.wal*: Write-Ahead-Log 文件(如果开启了 WAL) - *teset_db.ckp*: checkpoint 文件(如果开启了 checkpoint) 除此之外,如果数据库运行过程中由于某些原因崩溃了,这个目录下面可能会出现一些临时文件: - *test_db.wal._in_checkpoint_* - *test_db.wal._in_recovery_* - *teset_db.ckp._in_checkpoint_* 这些临时文件在下一次打开数据库时会自动被清理(以只读模式打开数据库也会清理这些文件)。 ### 索引 在建表时,必须指定一个主键索引。主键索引是唯一的,无法插入键重复的数据。可以指定多个二级索引。每个索引的键都可以是复合的(多个列的组合)。 创建二级索引可以加速某些查询,但是创建过多的二级索引,可能会严重降低写入/更新的性能。 ### 事务 可以使用 `Connection::{beginTransaction,commit,rollback}` 来显式地声明事务的范围,在一个事务范围内,所有的 DML 操作都会一起成功或一起失败。不可以在事务范围内执行 DDL 操作,否则会抛异常。 如果不显式地声明事务的范围,那么每一个 DML 操作都是一个独立的事务。例如,调用 insert 接口插入100条数据,在数据库内部会自动开启一个事务,保证全部数据要么都插入,要么一条都不插入。 ### 异常 所有的 DML 操作都可能会抛出两种异常: - `NeedRetryException`,由于并发事务而导致的读写冲突,不是错误。 - `NotRetryException`,是一种无法处理的错误。例如,写入的表不存在或写入的数据和表的 schema 不匹配时会抛出该异常。 **注意** > 只有使用 `Connection::beginTransaction` 显式地声明了事务的范围之后,才有可能抛出 `NeedRetryException` 异常。 在显式地声明了事务的范围之后,用户如果捕获到 `NeedRetryException`,需要回滚当前的事务,开启新的事务重新开始执行。如果不显式地声明事务的范围,数据库内部已经自动做了这些操作。 显式开启事务来执行 DML 操作的代码通常如下所示: ``` while (true) { try { conn.beginTransaction(); // 执行一系列 DML 操作 conn.commit(); break; } catch (const NeedRetryException &ex) { continue; // 重试 } catch (...) { conn.rollback(); throw; } } ``` 由于上述代码较为繁琐,所以提供了 `Connection::transaction` 这个与上述代码片段等效的接口,用法如下: ``` conn.transaction([&]() { // 执行一系列 DML 操作 }); ``` ### Low-level DML 接口 `Connection` 类中分别提供了一个 low-level 查询接口和写入接口用于查询和写入数据。使用这些接口时,用户需要提前分配好内存,然后把它传入这个接口,使用方法参考:[数据写入插入查询更新与删除](#数据写入插入查询更新与删除)。 **注意** > 对于 low-level 查询接口,数据库内部不会检查传入的内存大小是否是足够的;对于 low-level 写入接口,数据库内部不会检查传入的数据是否和表的 schema 匹配。用户必须保证这些要求,否则可能会出现段错误。 low-level 查询接口相比于 high-level 接口,性能大约有 20% 的提升。而 low-level 写入接口相比于 high-level 接口性能几乎没有提升(忽略了构造数据的时间,只统计接口的实际执行时间)。 ## 脚本样例 在 Swordfish 中实现具体功能的脚本编写基本逻辑如下: - 初始化运行时环境:`DolphinDBLib::initializeRuntime()` - 连接到数据库:`Connection conn(*db)` - 数据库操作 - 清理环境 - 关闭运行时:`DolphinDBLib::finalizeRuntime()` 面向以下场景例子的脚本编译方法及要求见:[README.demo](README.demo.md)。 ### 通过运行 DolphinDB 脚本创建流数据引擎 在以下场景例子中,初始化运行时环境并连接到数据库后,以先后次序实现了以下功能: 1. 定义算子 2. 创建流数据引擎、流数据表、输出表 3. 订阅流数据表 4. 定义生产数据的方法 5. 执行对 200 行数据的计算 6. 清理环境 7. 关闭运行时以释放内存 C++ 例子如下: ```cpp int main() { DolphinDBLib::initializeRuntime(); // 初始化运行时环境 oltp::DBOption option; shared_ptr db = make_shared("test_db", option); Connection conn(*db); // 连接数据库 std::cout << "Initialize calculation" << std::endl; conn.execute(R"( // 算子 def sum_diff(x, y){ return (x-y)/(x+y) } @state def factor1(price) { a = ema(price, 20) b = ema(price, 40) c = 1000 * sum_diff(a, b) return ema(c, 10) - ema(c, 20) } // 创建引擎、订阅流表 share streamTable(1:0, `time`sym`price, [TIMESTAMP,STRING,DOUBLE]) as tickStream result = table(1000:0, `sym`time`factor1, [STRING,TIMESTAMP,DOUBLE]) rse = createReactiveStateEngine(name="reactiveDemo", metrics=[