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 不是用来替代交易系统的。它更适合作为“研究沙盒”:把行情、财务、因子数据落地为本地列式文件,用 SQL 快速迭代假设。

四、表与视图:物理落地与逻辑抽象

在 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 列式存储 行情数据 symbol + date 基本面数据 symbol + period 因子数据 symbol + date 交易/持仓 timestamp DuckDB 列式表 / 视图 按列压缩 · 向量化扫描 · SQL 分析 研究输出:因子回测 / 收益归因 / 风险敞口 本地、可复现、毫秒级迭代

六、常见误区与复盘

  • 误区一:把 DuckDB 当 OLTP 用。高频单行写入、频繁更新不是它的强项。研究场景应“批量写入、批量读取”。
  • 误区二:忽视列裁剪。写 SELECT * 会破坏列式存储的优势,应显式列出所需列。
  • 误区三:视图滥用。嵌套多层视图会降低可读性,建议视图只封装一层稳定口径。
  • 误区四:主键设计随意。行情表应以 (symbol, trade_date) 为逻辑主键,避免重复数据导致回测偏差。
金融数据对时间对齐极其敏感。不同频率(日频、分钟频、财报期)的数据在连接前,必须明确对齐规则,否则容易产生前视偏差(look-ahead bias)。

小结:列式存储决定了 DuckDB 的物理效率,向量化执行决定了它的计算速度,OLAP 特性决定了它的适用边界,表与视图决定了你的建模方式,而金融数据结构决定了你如何组织主键与时间维度。把这五点串起来,后续的导入、清洗、因子计算与回测才能建立在稳固的基础上。