来源:https://mariadb.org/duckdb-storage-engine-for-mariadb-when-the-sea-lion-learns-to-quack/
MariaDB 的 DuckDB 存储引擎:当海狮学会嘎嘎叫
对 MariaDB 的 DuckDB 存储引擎的早期预览------列式、向量化分析,与您的事务型表紧密相邻。
问题所在
MariaDB 的 InnoDB 在其设计目标上表现出色:事务处理。逐行插入、更新、点查询、强一致性。但是,当您要求它扫描数千万行以进行多路连接和若干聚合操作时,行存储就必须付出巨大的努力。
通常的解决方案是搭建一个独立的分析系统,然后构建 ETL 管道将数据复制进去。这样一来,您需要维护两个系统,并且还要忍受它们之间的延迟。
如果分析引擎就位于同一台服务器内部呢?------ 相同的 SQL,相同的客户端,相同的数据目录?
我们构建了什么
MariaDB 的 DuckDB 存储引擎正是做到了这一点。使用 ENGINE=DuckDB 创建表,其数据以 DuckDB 原生的列式格式存储;针对它的分析查询则通过 DuckDB 的向量化、多核引擎运行------在 MariaDB 服务器内部进行。
无需单独的集群。无需 ETL。无需新的协议。使用熟悉的 mariadb 客户端,一种 SQL 方言,InnoDB 表和 DuckDB 表在同一个数据库中共存。
关于 MariaDB ColumnStore 的说明
MariaDB 已经提供了 ColumnStore,这是一个用于大规模分析的成熟列式引擎------通常部署为横向扩展的多节点数据仓库。DuckDB 引擎则针对不同的应用场景:单节点、进程内、零运维的分析引擎,与您的 InnoDB 表相邻,用于轻量级嵌入式分析和 HTAP。两者是互补而非竞争关系------当您需要分布式数据仓库时使用 ColumnStore,当您希望进行进程内分析且无需额外组件时,则使用 DuckDB 引擎。
30 秒快速体验
像创建任何其他表一样创建 DuckDB 表:
sql
CREATE TABLE sales (
id BIGINT PRIMARY KEY,
product VARCHAR(64),
amount DECIMAL(12,2),
sold_at TIMESTAMP
) ENGINE=DuckDB;
使用普通 SQL 查询它------整个 SELECT 语句会被下推到 DuckDB:
sql
SELECT product, SUM(amount) AS revenue
FROM sales
GROUP BY product
ORDER BY revenue DESC
LIMIT 10;
而使其真正有用的部分是------跨引擎连接。单个查询可以同时连接 DuckDB 分析表和 InnoDB 操作表。端到端示例如下------两个不同引擎的表,在一个语句中连接:
sql
-- 分析数据在 DuckDB 中,操作数据在 InnoDB 中
CREATE TABLE analytics.orders (id BIGINT PRIMARY KEY, product_id INT, amount DECIMAL(12,2)) ENGINE=DuckDB;
CREATE TABLE inventory.products (id INT PRIMARY KEY, name VARCHAR(64)) ENGINE=InnoDB;
INSERT INTO inventory.products VALUES (1, 'Wireless Keyboard'), (2, 'Mechanical Switch');
INSERT INTO analytics.orders VALUES (4217, 1, 1299.00), (4218, 2, 42.00);
-- 一个查询,两种引擎
SELECT d.id, d.amount, i.name
FROM analytics.orders d -- ENGINE=DuckDB
JOIN inventory.products i -- ENGINE=InnoDB
ON d.product_id = i.id
WHERE d.amount > 1000;
DuckDB 处理连接、聚合和排序;InnoDB 行通过 MariaDB 优化器按需生成。无需复制,无需 ETL。
性能表现
我们在 TPC-H SF10(约 11 GB,8660 万行,lineitem 表 6000 万行)上对该引擎进行了基准测试。分析引擎有两个关键指标:数据加载速度和查询速度。
硬件/环境:Intel Core i7-13700H(14 核 / 20 线程),64 GB RAM,NVMe SSD。MariaDB 11.4.13 内置 DuckDB v1.5.2,duckdb_memory_limit=8 GiB,threads=20。预热运行,稳定在约 ±15% 误差。
查询延迟------全部 22 个 TPC-H 查询
完整套件预热运行约需 4.3 秒:
| 查询 | 耗时 (秒) | 查询 | 耗时 (秒) | 查询 | 耗时 (秒) |
|---|---|---|---|---|---|
| q01 | 0.253 | q09 | 0.337 | q17 | 0.116 |
| q02 | 0.076 | q10 | 0.253 | q18 | 0.318 |
| q03 | 0.134 | q11 | 0.049 | q19 | 0.207 |
| q04 | 0.135 | q12 | 0.152 | q20 | 0.166 |
| q05 | 0.142 | q13 | 0.606 | q21 | 0.486 |
| q06 | 0.070 | q14 | 0.128 | q22 | 0.113 |
| q07 | 0.133 | q15 | 0.101 | 总计 | 4.30 |
| q08 | 0.145 | q16 | 0.178 |
每个查询还需支付少量固定成本(约 40 毫秒),用于客户端连接和下推设置------在最轻量的查询上比较明显,而在重分析查询上可以忽略不计,因为 DuckDB 的向量化执行占据主导。
批量加载
| 路径 | 总加载时间 |
|---|---|
引擎内 COPY (run_in_duckdb,8 GB 内存) |
33 秒 |
LOAD DATA LOCAL INFILE |
~400 秒(6.7 分钟) |
通过 run_in_duckdb() 函数,在进程内使用 DuckDB 原生的并行读取器加载外部 CSV,可在约 33 秒 内摄取全部 8660 万行数据。标准的 LOAD DATA LOCAL INFILE 路径大约慢 12 倍 ,因为每一行都经过 MariaDB 的 SQL/handler 层路由,串行化了 COPY 并行执行的工作。对于批量加载,建议通过 run_in_duckdb 使用引擎内 COPY,并为大表提高 duckdb_memory_limit。
工作原理(简述)
DuckDB 的速度基于三大支柱:列式存储(只读取所需的列)、向量化执行(以缓存友好的批量处理数据)和并行性(利用所有核心)。
在 MariaDB 内部,该引擎接入两个地方:
- 查询下推 ------整个 SELECT 语句(包括 WHERE、JOIN、GROUP BY、ORDER BY)通过 MariaDB 的
select_handler接口交给 DuckDB,由 DuckDB 的优化器规划整个查询。 - 跨引擎扫描------当查询混合了 DuckDB 和非 DuckDB 表时,非 DuckDB 表通过完整的 MariaDB 执行管道按需流入 DuckDB,因此索引访问和谓词评估仍发生在 MariaDB 端。
这是简要版本。更深入的架构文章即将发布。
为什么采用插件形式
DuckDB 引擎以 MariaDB 插件的形式提供------这是一个经过深思熟虑的重要选择。MariaDB 的可插拔架构使得这样的组件可以拥有独立于服务器本身的成熟度。
- 对服务器稳定性无影响------因为存储引擎插件是按需加载的,并且通过定义良好的接口进行隔离,它可以被添加到一个 GA 版本中,而不会危及核心服务器的稳定性。如果您不加载插件,对您没有任何影响。
- 按自身节奏成熟------该插件可以按照自己的节奏迭代、稳定并达到生产就绪状态,与服务器的发布周期解耦。早期采用者今天就可以选择加入;其他人则不受影响。
- 干净的按需启用------当您需要该功能时安装插件,不需要时则不安装。长期以来为 InnoDB、Spider 和 ColumnStore 等引擎提供支持的相同机制,现在也为 DuckDB 提供了支持。
这正是让 MariaDB 能够安全地提供雄心勃勃的新功能的模式:需要时功能强大,不需要时无感隐身。
适用场景
良好适用
- HTAP ------ InnoDB 用于事务,DuckDB 用于分析,均在同一个数据库中。
- 即席分析查询 ------ 大数据量的连接、聚合、窗口函数,无需导出数据。
- 消除 ETL ------ 分析引擎在进程内运行。
当前限制(仍处于早期阶段)
- DECIMAL 精度上限为 38 位(DuckDB 的限制)。
- DuckDB 引擎的表需要 PRIMARY KEY,并拒绝非 UTF-8 字符集。
- 某些 MariaDB 函数尚不支持下推(例如
GROUP_CONCAT()、DATE_FORMAT()),将回退到 MariaDB 执行。 - 比 MariaDB 默认
sql_mode更严格的 GROUP BY 要求。 - 不支持 XA 事务;跨引擎外部扫描目前是单线程的。
- 对于非 UTF-8 字符集,排序规则是近似的。
路线图
这仅仅是开始。我们接下来的方向:
- 分析型 GIS ------ 开放 DuckDB 的 Spatial 扩展,使地理空间分析能在 MariaDB 内部运行。
- 仅 InnoDB 数据上的更快 HTAP ------ 通过 DuckDB 的向量化执行对纯 InnoDB 表运行查询,即使不涉及 DuckDB 表也能加速分析。
- 部分查询下推 ------ 实现一个派生 handler,将复杂查询的部分下推到 DuckDB,使混合工作负载可以卸载其分析子查询。
- 数据湖访问 ------ 通过 DuckDB 的扩展生态系统访问外部数据湖协议和格式。
尝试一下
获取预构建包
最快路径:MariaDB 的社区 CI 在 ci.mariadb.org/68929/ 发布预构建包。选择与您的平台和架构匹配的目录------每个操作系统/架构组合都有一个,因此您需要选择正确的路径。对于 Ubuntu 24.04 (amd64),路径是:
https://ci.mariadb.org/68929/amd64-ubuntu-2404-deb-autobake/
将该目录用作软件包仓库(或直接从其中下载 .deb 文件)即可安装,无需编译。对于其他目标,请替换为对应的文件夹------例如 amd64-rhel-9-rpm-autobake/、aarch64-ubuntu-2404-deb-autobake/ 等。请注意,这些包未签名,仅适用于测试目的。
从源码构建
另外,该引擎在 storage/duckdb/ 目录下随树提供。克隆与您的目标 MariaDB 版本匹配的分支并进行构建:
bash
git clone --recurse-submodules -b 11.4 https://github.com/MariaDB/server.git mariadb-server
cd mariadb-server
./storage/duckdb/build.sh -D # 安装构建依赖 (需要 root)
./storage/duckdb/build.sh # 构建 + 安装
自行构建软件包
更愿意自己制作可分发的软件包?在安装构建依赖(build.sh -D)之后,build.sh -p 会为您的平台生成 RPM 或 DEB。详情请参见引擎 README 中的"构建软件包"部分:
bash
./storage/duckdb/build.sh -p # 构建 RPM (Rocky/Fedora/Amazon Linux) 或 DEB (Debian/Ubuntu)
启用引擎
DuckDB 引擎必须在服务器启动时加载------此步骤是必须的。将 duckdb.cnf 文件放入您服务器的 config-include 目录:
-
Debian / Ubuntu:
/etc/mysql/mariadb.conf.d/duckdb.cnf -
RHEL / Fedora / openSUSE:
/etc/my.cnf.d/duckdb.cnf[mysqld]
plugin-maturity=alpha
plugin-load-add=ha_duckdb.so
duckdb-memory-limit=8G # 默认只有 1G ------ 在测试前值得调高
plugin-load-add 加载引擎,而 plugin-maturity=alpha 是必需的,因为该插件目前处于 alpha 成熟度级别。注意默认的 duckdb-memory-limit 只有 1 GB ------ 对于任何认真测试,都值得先将其调高,因为它是大负载和查询的主要瓶颈。重启服务器后,您就可以创建 ENGINE=DuckDB 表了。
这是一个预览版------我们欢迎测试者、错误报告和反馈。
许可证
该引擎基于 GPL v2 许可(与 MariaDB 服务器相同),而 DuckDB 本身基于宽松的 MIT 许可证分发。
致谢
这项工作借鉴了 DuckDB 项目------其简洁、可嵌入、MIT 许可的引擎使这样的集成成为可能------也借鉴了阿里巴巴的 AliSQL,其 2025 年 12 月将 DuckDB 集成到 MySQL 兼容服务器中的版本是一个有价值的参考和验证点。
后续还有更多内容------包括更深入的架构探讨和更全面的基准研究。