编程 DuckDB 深度实战:进程内分析数据库凭什么单机跑 TB 级——向量化执行、列式存储与零拷贝数据湖全解析

2026-07-14 13:15:39 +0800 CST views 13

DuckDB 深度实战:进程内分析数据库凭什么单机跑 TB 级——向量化执行、列式存储与零拷贝数据湖全解析

如果你写过数据分析,大概率经历过这种尴尬:Pandas 读个 5GB 的 CSV 内存直接爆掉,Spark 起个集群又觉得为了几十 GB 数据杀鸡用牛刀,而 SQLite 跑个 GROUP BY 慢得像在考古。2026 年,这个问题的答案越来越清晰——DuckDB。它被称为"分析界的 SQLite",但内核里装着一套完全不同的引擎。本文从工程视角拆解它为什么快、什么时候该用、以及怎样把它用出花来。

一、背景:数据分析的"夹心层"困境

我们先把问题说清楚。一个做数据的程序员,日常面对的数据体量大致分三档:

  • KB~MB 级:业务库的几张表、接口返回的 JSON,SQLite 一把梭,或者干脆丢进 Pandas。
  • GB~TB 级:一天的埋点日志、一张宽表导出、几十个 Parquet 分区的湖仓文件。这个体量最尴尬。
  • PB 级:真正的"大数据",老老实实上 Spark / Flink / ClickHouse 集群。

尴尬就出在中间这档。它够大,大到 Pandas 扛不住;但又不够大,大到值得养一个分布式集群。传统解法要么忍(Pandas 分块 + 祈祷内存够),要么卷(为几十 GB 数据申请一套 Spark 资源),要么退(用 PostgreSQL 硬跑分析查询,然后看着 seq scan 发呆)。

DuckDB 的切入点非常精准:它就是为这中间一档而生的。它把自己定位为"进程内(in-process)的 OLAP 数据库",你可以像引用一个普通 Python 库那样 import duckdb,不装服务、不配端口、不启进程,然后直接对 TB 级数据跑 SQL。

这就有意思了。一个没有 server 的数据库,凭什么比装了 server 的 PostgreSQL 跑分析还快?答案藏在它的两个核心设计里:列式存储向量化执行引擎。我们一层层拆。

二、核心概念:先把几个被说烂的词讲透

2.1 OLTP 与 OLAP,不是两种数据库,是两种"读法"

很多人知道 OLTP(联机事务处理)和 OLAP(联机分析处理)的区别,但容易停留在"一个是 MySQL 一个是数仓"这种表面认知。从磁盘和 CPU 的视角看,本质区别在于数据被访问的模式

  • OLTP:一次读写一行(或几行)。UPDATE users SET balance=balance-10 WHERE id=42。它关心的是"单个记录的增删改查",行式存储天然友好——同一行的所有字段在磁盘上挨着,一次 IO 就读全了。
  • OLAP:一次扫描一列的几百万行。SELECT city, AVG(age) FROM users GROUP BY city。它关心的是"某一列在所有行上的聚合",行式存储就傻了——为了算一个 AVG(age),你得把每行整个读进来再丢掉无关的字段,IO 和缓存都被浪费。

SQLite、MySQL、PostgreSQL 默认都是**行式(row-based)存储,它们把一行作为一个存储单元。这对事务是好文明,对分析是灾难。DuckDB 从根上就是列式(columnar)**的:age 这一列的所有值物理上挨在一起,算 AVG 时只读 age 这一块连续内存,CPU 预取器和缓存命中率直接拉满。

2.2 列式存储不只是"换了个排列顺序"

把行存和列存的区别画出来就直观了。假设有这样一张表:

行式存储(SQLite/MySQL):
[1, Alice, 28, Beijing] [2, Bob, 34, Shanghai] [3, Carol, 31, Beijing] ...
一次读一行,字段连续

列式存储(DuckDB/ClickHouse/Parquet):
col_id:   [1, 2, 3, ...]
col_name: [Alice, Bob, Carol, ...]
col_age:  [28, 34, 31, ...]
col_city: [Beijing, Shanghai, Beijing, ...]
每一列是一个连续的数值数组

列式的红利不止"少读无关列"这么简单,它还带来两个杀手锏:

  1. 压缩率暴增。同一列的数据类型相同、取值范围相近(比如 age 都在 0~120 之间),用字典编码(dictionary encoding)、游程编码(RLE)、位宽压缩就能压得很小。DuckDB 对每列还会按类型自动选压缩算法。压缩后数据更小,意味着同样的 CPU 缓存能装下更多行,进一步加速。
  2. 向量化处理的天然后选。一列连续同类型的值,本质就是一个 typed array,CPU 可以用 SIMD 指令一条指令处理 8/16/32 个值。这正好引出 DuckDB 最硬核的设计。

2.3 "进程内"到底意味着什么

进程内(in-process / embedded)是指数据库引擎跑在你的应用程序进程里,而不是一个独立服务。SQLite 也是进程内,所以你能 import sqlite3 直接用。但 DuckDB 的进程内和 SQLite 的进程内目的不同:

  • SQLite 的进程内是为了零部署、本地持久化一个小数据库(手机 App 的配置、桌面软件的缓存)。
  • DuckDB 的进程内是为了把分析引擎直接塞进你的数据 pipeline,避免"应用读数据 → 网络发给数据库 → 数据库算 → 网络传回结果"这种来回拷贝。数据不离开你的进程,算子直接作用在内存数组上。

这就是为什么 DuckDB 能跟 Pandas、PyArrow 玩"零拷贝"——因为它俩在同一个进程地址空间里,DuckDB 算完的结果可以直接把底层 buffer 借给 Arrow,不用序列化、不用跨进程。这点我们后面用代码验证。

三、架构分析:DuckDB 凭什么快

3.1 一条 SQL 在 DuckDB 里的旅程

和大部分现代数据库一样,DuckDB 的查询执行分几个阶段:

SQL 文本
  → 解析器(Parser):词法/语法分析,生成抽象语法树 AST
  → 绑定器(Binder):把表名、列名解析成具体对象,做类型检查
  → 逻辑计划(Logical Planner):生成关系代数计划(Scan/Filter/Join/Aggregate...)
  → 优化器(Optimizer):谓词下推、列裁剪、Join 重排、公共子表达式消除
  → 物理计划(Physical Planner):决定具体算子实现(Hash Join vs Merge Join)
  → 执行器(Execution Engine):向量化执行,按 pipeline 并行跑

这里有两个 DuckDB 区别于传统数据库的地方值得展开:优化器的下推能力向量化执行引擎

3.2 向量化执行引擎:DuckDB 的灵魂

要理解向量化为什么快,得先理解它的反面——传统的 Volcano(火山)模型

传统数据库(包括老版本 PostgreSQL 的执行器)用的是 tuple-at-a-time(一次处理一个元组)的火山模型:每个算子实现一个 next() 方法,父算子不断调用子算子的 next()下一行,处理完再往上抛。比如 Filter(age > 30) 这个算子,它的逻辑是:

// 火山模型:一次一行
loop:
    row = child.next()        // 取一行
    if not row: break
    if row.age > 30:          // 判断这一行
        return row            // 返回这一行

这个模型的问题在抽象开销:每处理一行,都要经历一次函数调用、一次虚函数分派(vtable lookup)、一次分支判断。当数据量是千万级时,这些 per-row 的解释开销累计起来比真正的计算还贵。CPU 把大量时间花在"调度算子"而不是"算数据"上,分支预测也因为行与行之间的条件跳转而频繁失败。

DuckDB 用的是 Vectorized / Batch 模型:每个算子一次处理**一整批(vector,默认 2048 行)**数据。算子不再是"给我下一行",而是"给我下一批,我一次处理 2048 行再给你一批"。

// 向量化模型:一次一批(batch of 2048 rows)
loop:
    batch = child.next_batch(2048)   // 取一批(列存的 typed array)
    if batch empty: break
    mask = batch.age > 30            // 一次性对 2048 个值做向量比较 → SIMD
    result = batch.filter(mask)      // 用 bitmap 过滤,连续内存
    return result

向量化的红利有三层:

  1. 摊薄了解释开销。一次 next_batch 处理 2048 行,函数调用和虚分派的次数直接降到 1/2048。算子的核心循环从"处理一个值"变成"处理一个数组"。
  2. 对 CPU 缓存和 SIMD 友好。数据以连续的同类型数组形式存在,CPU 的预取器能准确预测下一步要读哪块内存;batch.age > 30 这种比较,编译器可以生成 SIMD 指令(比如 AVX2 一条指令比 8 个 int32),真正并行处理。
  3. 分支更少、流水线更顺。一批数据的处理逻辑是统一的,没有"每行条件不同导致频繁跳转",CPU 指令流水线不容易被打断。

一个常见误解:很多人以为 DuckDB"快是因为用了 SIMD"。准确说,SIMD 是向量化的结果之一而非原因。根本原因是"批量处理"降低了 per-row 解释开销,而批量是同类型连续数组,才让 SIMD、缓存预取这些 CPU 优化手段能派上用场。向量化是因,SIMD 是果。

3.3 列式存储格式与压缩

DuckDB 内部用 Data Chunk 作为数据交换的基本单位(就是上面说的 2048 行的批次),每个 Data Chunk 里每一列是一个 Vector。落盘(持久化到 .duckdb 文件)时,数据按列分块存储,并对每列根据类型选择压缩:

  • 低基数列(比如 city、status)→ 字典编码,存储 (字典, 整数索引),体积骤减。
  • 有序/重复列 → 游程编码(RLE)。
  • 数值列 → 按实际取值范围选择最小位宽(bitpacking),比如 age 用 7 位就够了,不用占满 32 位 int。

压缩不仅省磁盘,更省内存和 IO——读到缓存里的是压缩态或按需解压,同样的 L3 缓存能容纳更多行,算得更快。

3.4 与 Apache Arrow / Parquet 的"近亲"关系

这是 DuckDB 在 Python 生态里封神的关键设计。它内部的内存格式和 Apache Arrow 的列式内存格式高度兼容,因此:

  • 读 Parquet 文件时,可以直接把 Parquet 解码成 Arrow 格式的 buffer,几乎零转换。
  • DuckDB 的查询结果可以直接以 Arrow Table 的形式吐给 Python,不复制数据。
  • 和 Pandas 互转也只是一层薄封装(df = duckdb.query(...).df()duckdb.df(df))。

换句话说,DuckDB 不是"又一个数据库",而是把分析计算引擎嵌进了 Arrow 生态——你用 Pandas 做预处理、用 DuckDB 做重查询、用 PyArrow 做列式传输,三者共享同一块内存,没有序列化拷贝。这套组合在现代数据栈里被称为"本地优先(local-first)的数据分析"。

3.5 扩展机制:内核小而插件多

DuckDB 的内核保持精简,能力靠**扩展(extension)**热插拔。官方维护的扩展涵盖:

  • parquet / json:读写这两种文件格式(核心分析场景)。
  • httpfs:直接读 S3 / HTTP(S) 上的文件,这是搭轻量数据湖的关键。
  • spatial:地理空间函数。
  • fts:全文检索。
  • arrow:与 Arrow 深度集成。
  • excel / sqlite / postgres / mysql:直接读这些外部数据源。
  • iceberg / delta:对接主流表格式(table format)。

扩展是按需从官方仓库拉取的,第一次 INSTALL 之后就缓存本地。这让 DuckDB 既能保持单文件轻量,又能按需变身为"能读整个湖仓"的查询引擎。

四、代码实战:从 hello world 到数据管道

下面所有示例基于 DuckDB 1.5+(2026 年的稳定线),Python API。先装:

pip install duckdb

4.1 最朴素的一击:直接查一个 Parquet

假设你有从数据湖导出的 events.parquet,不用 Pandas 加载,直接 SQL 打上去:

import duckdb

# 直接对 Parquet 文件跑 SQL,文件不需要"导入"进库
con = duckdb.connect()  # 不传路径 = 内存模式(in-memory)
rows = con.execute("""
    SELECT event_type, COUNT(*) AS cnt
    FROM 'events.parquet'
    WHERE ts >= TIMESTAMP '2026-07-01'
    GROUP BY event_type
    ORDER BY cnt DESC
    LIMIT 10
""").fetchall()

for r in rows:
    print(r)

注意这里 'events.parquet' 是直接当表名用的字符串字面量。DuckDB 会自动识别文件格式、推断 schema、把查询下推到文件读取阶段。整个文件可能几十 GB,但如果你只查某几列 + 带过滤,DuckDB 只读需要的列块(列裁剪),且 Parquet 自带行组(row group)统计,能跳过不满足过滤条件的行组(谓词下推)。它不会把整个文件读进内存。

4.2 多文件 + 通配符:扫一整个分区目录

数据湖常见的布局是按日期分目录:s3://lake/events/dt=2026-07-01/part-0.parquetdt=2026-07-02/...。DuckDB 直接支持 glob:

con = duckdb.connect()

# 用 * 通配符扫多个文件,甚至跨分区
sql = """
    SELECT dt, COUNT(*) AS cnt
    FROM 's3://lake/events/dt=2026-07-*/part-*.parquet'
    GROUP BY dt
    ORDER BY dt
"""
# 读 S3 需要 httpfs 扩展
con.execute("INSTALL httpfs; LOAD httpfs;")
con.execute("SET s3_region = 'ap-east-1';")
# 如需认证:
# con.execute("SET s3_access_key_id = '...'; SET s3_secret_access_key = '...';")

for r in con.execute(sql).fetchall():
    print(r)

这就是 DuckDB 作为"轻量数据湖查询引擎"的用法——你不需要 Spark、不需要 Presto,本地一个进程就能对 S3 上的成百上千个 Parquet 分区跑 SQL,且下推优化让大部分 IO 被裁剪掉。

4.3 与 Pandas / PyArrow 的零拷贝互操作

这是日常最舒服的用法。DuckDB 和 DataFrame 之间互转几乎没有成本:

import duckdb
import pandas as pd

df = pd.DataFrame({
    "user_id": [1, 2, 3, 1, 2],
    "amount": [100, 200, 50, 30, 80],
    "day":    ["2026-07-01", "2026-07-01", "2026-07-01", "2026-07-02", "2026-07-02"],
})

# Pandas → DuckDB:把 DataFrame 注册成临时视图,不拷贝底层数据
con = duckdb.connect()
con.register("payments", df)

res = con.execute("""
    SELECT day, SUM(amount) AS total, COUNT(DISTINCT user_id) AS users
    FROM payments
    GROUP BY day
""").df()   # 结果直接变回 DataFrame
print(res)

# DuckDB → PyArrow(零拷贝):底层 buffer 共享,适合做列式下游
arrow_tbl = con.execute("SELECT * FROM payments").arrow()
print(arrow_tbl.schema)

con.register("payments", df) 的关键在于:DuckDB 通过 Arrow 接口借用了 Pandas DataFrame 的底层列式 buffer,而不是把数据逐行复制进来。对大 DataFrame,这省下的是数倍内存和可观的拷贝时间。

4.4 窗口函数与复杂分析

DuckDB 完整支持窗口函数、CTE、JOIN、子查询,写复杂分析毫不费力。比如"算每个用户每天的消费在其自身历史中的排名和累计占比":

con = duckdb.connect()
con.register("payments", df)

sql = """
WITH daily AS (
    SELECT
        user_id,
        day,
        SUM(amount) AS day_total
    FROM payments
    GROUP BY user_id, day
)
SELECT
    user_id,
    day,
    day_total,
    SUM(day_total) OVER (
        PARTITION BY user_id
        ORDER BY day
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total,
    RANK() OVER (
        PARTITION BY user_id
        ORDER BY day_total DESC
    ) AS best_day_rank
FROM daily
ORDER BY user_id, day
"""
print(con.execute(sql).df())

CTE + 窗口函数这套在标准数仓 SQL 里很常见,DuckDB 把这套能力带到了本地进程内,你写法和写 Hive/Spark SQL 基本一致,迁移零成本。

4.5 多数据源 JOIN:把 SQLite、Postgres、CSV 缝在一起

DuckDB 能同时 ATTACH 多种外部数据源,在一条 SQL 里跨源 JOIN。这在"临时对账、数据迁徙核对"场景极好用:

con = duckdb.connect()

# 挂一个 SQLite 库
con.execute("ATTACH 'old_app.db' AS legacy (TYPE sqlite)")

# 读一个 CSV(自动推断 schema、类型)
# 直接 JOIN 内存表 + SQLite 表 + CSV
sql = """
    SELECT l.user_id, l.email, c.order_count, c.total_amount
    FROM legacy.users AS l
    JOIN read_csv_auto('orders_export.csv') AS c
      ON l.user_id = c.user_id
    WHERE c.total_amount > 1000
"""
print(con.execute(sql).df())

read_csv_auto 会自动探测分隔符、表头、每列类型(甚至能识别日期格式),比 Pandas 的 read_csv 在脏数据上更健壮。

4.6 批量写入:Appender 与持久化

分析库也要写。DuckDB 提供高效的 Appender 做逐行/批量插入,比一条条 INSERT 快得多:

con = duckdb.connect("analytics.duckdb")  # 持久化到单文件

con.execute("""
    CREATE TABLE IF NOT EXISTS events (
        id      BIGINT,
        user_id BIGINT,
        amount  DOUBLE,
        ts      TIMESTAMP
    )
""")

# 方式一:从 DataFrame 直接建表/追加(最高效)
big_df = pd.DataFrame(...)   # 假设已有一大批数据
con.execute("INSERT INTO events SELECT * FROM (SELECT * FROM df_view)")
con.register("df_view", big_df)
con.execute("INSERT INTO events SELECT * FROM df_view")

# 方式二:Appender 流式写入(适合逐条产生、批量 flush)
appender = con.append("events")
for record in stream_of_records():   # 假设是流式数据源
    appender.append(row=record)
appender.flush()   # 显式刷盘,控制写入节奏

持久化模式(传数据库文件路径而非内存)下,DuckDB 把数据写成列式 .duckdb 文件,后续查询直接享受列存+压缩的优势,单文件即可分发、备份、git 忽略。

五、性能优化:把 DuckDB 榨干

DuckDB 开箱已经很快,但几个开关和写法能再上一个台阶。

5.1 并行执行:pipeline 级多线程

DuckDB 在执行阶段按 pipeline(算子链)做任务并行,多个 pipeline 在多核上同时跑。默认开启,核数自动探测。你可以用 PRAGMA 查看和调整:

-- 查看当前线程数
PRAGMA threads;

-- 手动设为 8 线程(比如容器里想限制资源)
PRAGMA threads = 8;

注意:并行对"扫描 + 聚合"这种吞吐型查询收益最大,对返回单行的小查询几乎无感。别指望用多线程拯救一条 O(N²) 的烂 JOIN。

5.2 谓词下推与分区裁剪:少读就是最快

这是 DuckDB 在大数据文件上"显得快"的最主要原因。两个机制:

  • 列裁剪(Column Pruning):查询只 SELECT a, b,执行器只读这两列的块,其他列的物理数据根本不进内存。
  • 谓词下推(Predicate Pushdown)WHERE ts > '2026-07-01' 会被下推到文件扫描层。对 Parquet,DuckDB 读每个 row group 的 min/max 统计,直接跳过整段不满足条件的 row group;对分区目录(dt=2026-07-0X),直接不打开不匹配的分区文件。

实战建议:查询时把过滤条件写在 WHERE 里,让优化器去下推,别先 SELECT * 全部读进来再在 Python 里过滤。后者等于主动放弃 DuckDB 最大的优势。

# 好:过滤下推到文件扫描层,只读需要的行组
con.execute("""
    SELECT city, AVG(latency)
    FROM 's3://lake/logs/dt=2026-07-*/part-*.parquet'
    WHERE latency > 100 AND status = 500
    GROUP BY city
""")

# 差:先全量读进内存,再在 Python 里筛,等于没用 DuckDB
df = con.execute("SELECT * FROM 'logs.parquet'").df()
df[(df.latency > 100) & (df.status == 500)]

5.3 内存与溢出(Spilling):TB 级数据的底气

DuckDB 默认用内存,但当中间结果(比如一个大 Hash Join 的哈希表、一个超长排序)超过内存阈值时,会自动溢出到磁盘(spilling),用临时文件兜底,而不是崩溃。相关旋钮:

-- 限制内存上限(比如 4GB),超出则溢出到磁盘
PRAGMA memory_limit = '4GB';

-- 临时文件目录(溢出用)
PRAGMA temp_directory = '/data/duckdb_tmp';

这意味着你完全可以用一台 16GB 内存的笔记本,对 TB 级 Parquet 跑聚合——只要磁盘够,DuckDB 会默默把溢出的中间结果写到 temp_directory。这也是它能"单机跑 TB 级"的技术底气之一。

5.4 与 ClickHouse / Pandas / PostgreSQL 的基准直觉

很多人问"DuckDB 和 ClickHouse 谁快"。先说结论:它们是不同物种,不该直接比绝对速度

维度DuckDBClickHousePostgreSQLPandas
部署形态进程内、单文件独立 server、集群独立 server库(内存)
存储列式列式行式行式/列式混合
并发写单写者多写多写 MVCC
数据量级GB~TB(单机)GB~PB(集群)GB~TBMB~GB(内存限制)
跨进程/网络否(进程内)是(客户端/服务)
典型场景本地分析、ETL、数据湖查询实时分析服务、可观测性事务 + 中等分析小数据探索

一个粗略的直觉基准(同一台 8 核机器,10GB Parquet 的 GROUP BY 聚合):

  • Pandas:内存吃紧,可能 OOM,或分块后很慢。
  • PostgreSQL:得先 COPY 进表(分钟级导入),再跑,行存导致扫描慢,整体最慢之一。
  • DuckDB:直接对文件 SQL,列式扫描 + 向量化,通常比 Pandas 快 5~20 倍,比 PG 快一个数量级。
  • ClickHouse:单独 server,导入后查询本身极快,但"起服务 + 导入"的固定成本高,对一次性本地分析反而不如 DuckDB 顺手。

所以选型口诀是:要本地快速分析、ETL、数据湖即席查询 → DuckDB;要高并发分析服务、实时监控 → ClickHouse;要事务 + 强一致 → PostgreSQL。 DuckDB 不是来替代谁的,它是补齐 SQLite 在分析方向上的短板。

六、总结与展望:DuckDB 到底改变了什么

6.1 它真正解决的是"分析的工具链摩擦"

DuckDB 的价值不在某个单项指标登顶,而在于把"重查询能力"塞进了最轻量的形态。过去做分析,轻量(SQLite/Pandas)的算得慢、装得爽;重的(Spark/ClickHouse)算得快、部署烦。DuckDB 把"算得快"和"装得爽"第一次合在了一起:一个 pip 包、一个 import、一个文件,TB 级分析就开干了。

它把"分析界的 SQLite"这个比喻落到了实处——不是性能像 SQLite,而是使用姿态像 SQLite,但内核是 OLAP 引擎

6.2 什么时候不该用 DuckDB

讲清楚能做什么,更要讲清楚不能做什么,这才是工程判断:

  1. 高并发写入服务:DuckDB 是单写者模型,不支持多进程同时写。它为你、为分析而生,不为每秒上千次 INSERT 的 Web 后端而生。这种场景老老实实用 PostgreSQL / MySQL。
  2. 跨机器分布式计算:单进程、单节点。数据真的到 PB 级、需要横向扩展时,还是得上 Spark / ClickHouse 集群,或把 DuckDB 当作"单节点内的加速核"嵌进更大 pipeline。
  3. 需要严格事务隔离的多用户系统:DuckDB 的强项是一人、一个进程、一次性把分析做完。

6.3 2026 年的生态走向

几个值得关注的方向:

  • MotherDuck 与云边协同:DuckDB 背后公司推出的云服务,把本地 DuckDB 和云端实例打通,本地算小、云端算大、结果可同步。这代表了"本地优先分析"的主流化。
  • 湖仓格式直读iceberg / delta 扩展让 DuckDB 能直接查询主流表格式的分区,进一步蚕食"轻量 Presto"的领地。
  • AI/LLM 数据层:RAG 和 Agent 系统需要高效检索、转换大批量文档与表格,DuckDB 常作为本地向量/结构化数据的查询底座,和嵌入式向量检索互补。
  • Notebook 原生:Jupyter / 各类 BI 工具里,DuckDB 正成为"默认的内核查询引擎",取代 Pandas 做重活。

6.4 给你的行动建议

如果你是后端 / 数据工程师,今天就可以在本地试三件事,成本几乎为零:

  1. 下次要分析一个几十 GB 的 CSV/Parquet,别先 pd.read_csv,改用 duckdb.query("SELECT ... FROM 'file'"),感受下内存和速度的差异。
  2. 把一个你平时用 Python 循环算的聚合逻辑,改写成一条 SQL,交给 DuckDB 的向量化引擎跑,代码更短、更快。
  3. 搭轻量数据湖时,DuckDB + Parquet + httpfs(S3) 作为"只读分析面",和 PostgreSQL(事务面)分工,能省掉一整套 Spark/Presto 的运维。

DuckDB 的出现提醒我们一件事:很多"大数据"问题,其实只是"中数据"问题被错误的工具放大了。 在盲目上分布式之前,先想想——这一份数据,DuckDB 能不能在本地一次算完?大概率能。


参考资料与进一步阅读:DuckDB 官方文档(duckdb.org)的 Architecture、Data Types、Extensions 章节;Apache Arrow 内存格式规范;Parquet 文件格式规范(列存 + 行组统计 + 谓词下推机制);Volcano vs Vectorized 执行模型的经典论文(如 "MonetDB/X100: Hyper-Pipelining Query Execution")。本文所有代码示例基于 DuckDB 1.5+ Python API,可在本地环境直接运行验证。

推荐文章

Gin 与 Layui 分页 HTML 生成工具
2024-11-19 09:20:21 +0800 CST
Go的父子类的简单使用
2024-11-18 14:56:32 +0800 CST
Golang 随机公平库 satmihir/fair
2024-11-19 03:28:37 +0800 CST
FastAPI 入门指南
2024-11-19 08:51:54 +0800 CST
程序员茄子在线接单