3. 核心概念与数据模型
在正式用 DuckDB 处理金融数据之前,必须先理解它为什么“快”、快在哪里,以及它如何组织数据。这一节围绕五个核心概念展开:列式存储、向量化执行、OLAP 特性、表与视图、金融数据结构概览。理解这些概念,能帮助你在后续章节中做出正确的建模与查询决策,而不是把 DuckDB 当成“另一个 SQLite”来用。
一、列式存储:为分析而生的物理布局
传统行式数据库(如 MySQL、SQLite)把一行记录的所有字段连续存放在一起,适合“按主键取一整行”的事务操作。而 DuckDB 采用列式存储:同一列的所有值连续存放。金融数据天然是“宽表 + 大量扫描”的场景,例如一张日频行情表有 5000 只股票、20 年、每天 10 个字段,行式存储读取收盘价一列时也要把整行拖进来,而列式存储只需读取该列的数据块。
列式存储带来三个直接收益:一是按需读取,只扫描查询涉及的列;二是高压缩率,同一列数据类型一致、重复度高,字典编码、RLE、位图压缩效果显著;三是利于向量化,连续内存布局让 CPU 可以批量处理。下面是一个典型的列式读取示意:
-- 行式思维:SELECT * 才能拿到 close
-- 列式思维:只读 close 列,其他列不进入内存
SELECT trade_date, close
FROM quotes
WHERE symbol = 'AAPL'
AND trade_date BETWEEN DATE '2020-01-01' AND DATE '2020-12-31';
二、向量化执行:一次处理一批,而非一行
DuckDB 的执行引擎是向量化的。传统火山模型一次处理一行,函数调用开销大;向量化执行一次处理一个向量(通常 1024 或 2048 行),把循环下沉到紧凑的 C++ 内核中,减少虚函数调用并提升 CPU 缓存命中率。对金融研究而言,这意味着对百万行级别的因子计算、分组聚合、窗口函数,延迟通常从秒级降到毫秒级。
向量化与列式存储是互补的:列式提供连续内存,向量化提供批量算子。二者结合,使 DuckDB 能在单机上完成过去需要 Spark 集群才能做的中等规模分析。
三、OLAP 特性:面向分析的工作负载
OLAP(联机分析处理)强调大范围扫描、聚合、分组、排序和多表连接,而不是高频单行写入。DuckDB 的 OLAP 特性体现在:支持完整的 SQL 分析函数、支持并行扫描与聚合、支持超出内存的数据集(溢出到磁盘)、支持直接查询 Parquet/CSV 等外部文件。下表对比了 OLTP 与 OLAP 在金融场景中的差异:
| 维度 | OLTP(如交易库) | OLAP(如 DuckDB 研究库) |
|---|---|---|
| 典型操作 | 单行插入、更新、按主键查询 | 全表扫描、聚合、窗口、连接 |
| 数据布局 | 行式 | 列式 |
| 金融场景 | 订单撮合、账户流水 | 因子回测、收益归因、风险敞口 |
| 优化目标 | 低延迟、高并发 | 高吞吐、扫描效率 |
四、表与视图:物理落地与逻辑抽象
在 DuckDB 中,表(TABLE)是物理存储,视图(VIEW)是保存的查询逻辑。金融研究中常见的做法是:用表存放清洗后的原始数据(如日行情、财报),用视图封装常用计算口径(如前复权价格、滚动波动率),避免重复写 SQL。
-- 物理表:落地清洗后的行情
CREATE TABLE quotes AS
SELECT symbol, trade_date, open, high, low, close, volume
FROM read_parquet('data/quotes/*.parquet');
-- 逻辑视图:封装复权与收益计算
CREATE VIEW daily_returns AS
SELECT symbol, trade_date,
close / lag(close) OVER (PARTITION BY symbol ORDER BY trade_date) - 1 AS ret
FROM quotes;
表适合稳定、需要索引或频繁连接的数据;视图适合口径统一、随底层表变化而自动更新的派生指标。二者配合,能让研究代码保持简洁且可复现。
五、金融数据结构概览
金融数据大体可分为四类:行情数据(时间序列,按 symbol + date 组织)、基本面数据(财报,按 symbol + period 组织)、因子数据(衍生指标,按 symbol + date 组织)、交易与持仓数据(事件流,按时间戳组织)。它们共同构成研究的数据底座。理解每类数据的粒度与主键,是设计表结构的前提。
六、常见误区与复盘
- 误区一:把 DuckDB 当 OLTP 用。高频单行写入、频繁更新不是它的强项。研究场景应“批量写入、批量读取”。
- 误区二:忽视列裁剪。写
SELECT *会破坏列式存储的优势,应显式列出所需列。 - 误区三:视图滥用。嵌套多层视图会降低可读性,建议视图只封装一层稳定口径。
- 误区四:主键设计随意。行情表应以
(symbol, trade_date)为逻辑主键,避免重复数据导致回测偏差。
小结:列式存储决定了 DuckDB 的物理效率,向量化执行决定了它的计算速度,OLAP 特性决定了它的适用边界,表与视图决定了你的建模方式,而金融数据结构决定了你如何组织主键与时间维度。把这五点串起来,后续的导入、清洗、因子计算与回测才能建立在稳固的基础上。