Featured image of post DuckDB ATTACH 零 ETL 实战:一条 SQL 打通 PostgreSQL + MySQL + CSV

DuckDB ATTACH 零 ETL 实战:一条 SQL 打通 PostgreSQL + MySQL + CSV

告别数据孤岛!DuckDB ATTACH 命令让你无需 ETL 就能跨 PostgreSQL、MySQL、CSV 实时联合查询。本文手把手教你安装扩展、连接数据库、编写跨库 JOIN,附带性能优化和最佳实践。

跨库查询架构

一、数据孤岛:每个分析师都经历过的崩溃时刻

想象一下这个场景:

  • 销售数据躺在 PostgreSQL 里
  • 用户信息存在 MySQL 中
  • 补充数据还在 Excel 或 CSV 文件里

老板问:「上个月华东区的 VIP 客户销售额是多少?」

你的本能反应是——打开终端,写 Python 脚本:

# 传统做法:200 行代码,半小时手工操作
import pandas as pd
from sqlalchemy import create_engine

# 第 1 步:连 PostgreSQL 导出订单
pg_conn = create_engine("postgresql://user:pass@localhost/sales_db")
orders = pd.read_sql("SELECT * FROM orders WHERE date >= '2026-08-01'", pg_conn)

# 第 2 步:连 MySQL 导出用户
mysql_conn = create_engine("mysql+pymysql://user:pass@localhost/user_db")
users = pd.read_sql("SELECT * FROM users WHERE tier = 'VIP'", mysql_conn)

# 第 3 步:读 CSV
regions = pd.read_csv("~/data/region_mapping.csv")

# 第 4 步:三次 merge
result = orders.merge(users, left_on="user_id", right_on="id")
result = result.merge(regions, left_on="region", right_on="region_code")

# 第 5 步:聚合、分析、出报表
output = result.groupby(["region_name", "tier"]).agg(...)

整个过程至少 30 分钟到 1 小时,而且每次换个条件就要重写。更重要的是,这个脚本 无法复用——换个日期范围,全部重来。

今天,用 DuckDB 的 ATTACH 命令,一条 SQL 搞定这一切。 不需要 Python,不需要 Pandas,不需要任何 ETL 流程。


二、核心原理:DuckDB 如何"挂载"外部数据源

DuckDB 的 ATTACH 命令可以像挂载云盘一样,把外部数据库和数据文件直接挂进来,然后在同一条 SQL 里跨数据源查询。

2.1 支持的 Attaching 类型

数据源ATTACH 语法适用场景
SQLiteATTACH 'file.db' (TYPE SQLITE)本地轻量数据库
MySQLATTACH 'conn_string' (TYPE MYSQL)生产业务数据库
PostgreSQLATTACH 'conn_string' (TYPE POSTGRES)分析型数据库
DuckDB 自身ATTACH 'data.duckdb'多 DuckDB 文件合并
Delta LakeATTACH './delta_dir' (TYPE DELTA)数据湖

关键认知:ATTACH 不是 ETL。它不会把数据复制到 DuckDB——它只是在 DuckDB 中创建一个外部表引用。查询时,DuckDB 会把过滤条件推下到源数据库执行,只拉取需要的数据。


三、第一步:安装必要的扩展

跨库查询需要先加载对应的扩展。DuckDB 的扩展管理非常智能——首次使用时自动下载,后续无需重复安装。

import duckdb

con = duckdb.connect(":memory:")

# 加载 PostgreSQL 连接扩展
con.execute("INSTALL postgres_scanner; LOAD postgres_scanner;")

# 加载 MySQL 连接扩展
con.execute("INSTALL mysql; LOAD mysql;")

# 加载 CSV 扫描扩展(通常内置,保险起见也加载一下)
con.execute("INSTALL csv; LOAD csv;")

print("✅ 所有扩展加载完成")

💡 提示:如果你使用的是 DuckDB v1.2+,很多扩展已经内置,INSTALL 命令可能会提示已安装——这是正常的,直接 LOAD 即可。


四、第二步:连接 PostgreSQL

4.1 完整连接字符串方式

# 方式 1:完整连接字符串(适合本地开发)
con.execute("""
    ATTACH 'postgresql://duckdb_user:***@localhost:5432/sales_db' 
    AS postgres (READ_ONLY)
""")

# 验证连接成功
tables = con.execute("SHOW TABLES FROM postgres").fetchall()
print(f"PostgreSQL 中的表: {tables}")
# 输出: [('orders',), ('users',), ('products',)]

4.2 生产环境推荐:从环境变量读取

import os

pg_url = os.environ.get("POSTGRES_URL")
con.execute(f"ATTACH '{pg_url}' AS postgres (READ_ONLY)")

4.3 高效查询:WHERE 条件下推

# ✅ 正确做法:在 PostgreSQL 端过滤,只拉取需要的数据
result = con.execute("""
    SELECT order_id, user_id, amount, order_date
    FROM postgres.orders
    WHERE order_date >= '2026-08-01'
      AND amount > 100
      AND status = 'completed'
""").fetchdf()

# ❌ 错误做法:拉全表再过滤(可能几百万行数据)
# orders_all = con.execute("SELECT * FROM postgres.orders").fetchdf()
# result = orders_all[orders_all['order_date'] >= '2026-08-01']

💡 关键:DuckDB 会自动把 WHERE 条件下推到 PostgreSQL 执行,只返回符合条件的行,通常能节省 90%+ 的网络传输量


五、第三步:连接 MySQL

con.execute("""
    ATTACH 'mysql://duckdb_user:***@localhost:3306/user_db'
    AS mysql (READ_ONLY, TYPE MYSQL)
""")

# 查看 MySQL 中的表
print(con.execute("SHOW TABLES FROM mysql").fetchall())

# 读取用户表(只取需要的列和行)
users = con.execute("""
    SELECT id, name, region, tier
    FROM mysql.users.active_users
    WHERE region IN ('华东', '华南', '华北')
""").fetchdf()

⚠️ MySQL 注意事项:

  • 确保已安装 pymysql:pip install pymysql
  • MySQL 8.0+ 密码认证方式改为 caching_sha2_password,建议升级 DuckDB 至 v1.5+ 或临时切换回 mysql_native_password
  • 部分 MySQL 5.6 以下版本可能不支持某些 SQL 特性

六、第四步:挂载 CSV / Excel 文件

6.1 方法 A:直接查询(推荐)

# read_csv_auto 自动推断类型、处理缺失值、识别日期格式
result = con.execute("""
    SELECT * 
    FROM read_csv_auto('~/data/users_extra.csv')
    WHERE signup_date >= '2026-01-01'
""").fetchdf()

6.2 方法 B:多文件批量读取(glob)

# 自动合并所有匹配的文件
result = con.execute("""
    SELECT * 
    FROM read_csv_auto('~/data/sales_2026_*.csv')
""").fetchdf()

💡 read_csv_auto 比 pandas 更聪明——它会自动推断列类型、处理混合类型、识别多种日期格式,而且在处理大文件时使用流式读取,内存占用极低。


七、第五步:跨库 JOIN —— 真正的高光时刻

现在,把三个数据源拼在一起,完成一个完整的分析任务:

# 场景:分析 2026 年 Q3 各区域销售表现
# 数据源:PostgreSQL(订单) + MySQL(用户) + CSV(地区系数)

result = con.execute("""
    WITH orders AS (
        -- 在 PostgreSQL 端完成过滤
        SELECT order_id, user_id, amount, order_date
        FROM postgres.orders
        WHERE order_date >= '2026-07-01'
          AND order_date < '2026-10-01'
          AND status = 'completed'
    ),
    users AS (
        -- 在 MySQL 端完成过滤
        SELECT id, name, region, tier
        FROM mysql.users.active_users
    ),
    regions AS (
        -- 直接从 CSV 读取
        SELECT region_code, region_name, growth_factor
        FROM read_csv_auto('~/data/region_coefficients.csv')
    )
    SELECT 
        r.region_name     AS 区域,
        u.tier            AS 用户等级,
        COUNT(*)          AS 订单数,
        ROUND(SUM(o.amount), 2)    AS 总销售额,
        ROUND(AVG(o.amount), 2)    AS 平均客单价,
        ROUND(SUM(o.amount) * r.growth_factor, 2) AS adjusted_revenue
    FROM orders o
    JOIN users u ON o.user_id = u.id
    JOIN regions r ON u.region = r.region_code
    GROUP BY r.region_name, u.tier, r.growth_factor
    ORDER BY 总销售额 DESC
""").fetchdf()

print(result)

输出示例:

    区域    用户等级   订单数    总销售额    平均客单价  adjusted_revenue
0  华东     VIP       3421   5289000.00   1546.00     5818900.00
1  华南     普通       2890   3124500.00   1081.00     3437950.00
2  华北     VIP       1980   2987600.00   1509.00     3286360.00
3  西南     普通       1245   1123400.00    902.00     1235740.00

🎉 这就是 ATTACH 的威力:三条 SQL 分别查询三个数据源,DuckDB 自动协调执行计划,最终输出一张整合报表。全程不到 20 行代码,没有导出、没有合并、没有 VLOOKUP。


八、性能优化:让跨库查询飞起来

跨库 JOIN 的性能瓶颈通常在数据传输。以下是关键优化技巧:

8.1 技巧 1:源端过滤,减少传输量

# ❌ 慢:把全表拉到 DuckDB 再过滤
orders_all = con.execute("SELECT * FROM postgres.orders").fetchdf()
result = orders_all[orders_all['order_date'] >= '2026-08-01']

# ✅ 快:WHERE 条件下推到源库
result = con.execute("""
    SELECT * FROM postgres.orders
    WHERE order_date >= '2026-08-01'
      AND amount > 50
""").fetchdf()

8.2 技巧 2:用 EXPLAIN 查看执行计划

# 查看 DuckDB 如何优化你的查询
explain_plan = con.execute("""
    EXPLAIN SELECT * FROM postgres.orders 
    WHERE amount > 100
""").fetchall()

for row in explain_plan:
    print(row[0])

输出中若包含 Remote ScanPostgreSQL 字样,说明查询正在源库执行。

8.3 技巧 3:大表用物化视图缓存

对于需要反复查询的数据,可以创建本地物化视图:

# 第一次查询:从远程库拉取并缓存
con.execute("""
    CREATE MATERIALIZED VIEW mv_q3_orders AS
    SELECT order_id, user_id, amount, order_date
    FROM postgres.orders
    WHERE order_date >= '2026-07-01'
      AND order_date < '2026-10-01'
""")

# 后续查询:直接读本地缓存,速度提升 10x+
result = con.execute("""
    SELECT region, SUM(amount) 
    FROM mv_q3_orders o
    JOIN mysql.users u ON o.user_id = u.id
    GROUP BY region
""").fetchdf()

8.4 技巧 4:先聚合再 JOIN

# ❌ 不推荐:大表 JOIN 大表
SELECT * FROM postgres.big_table 
JOIN mysql.another_big_table ON ...

# ✅ 推荐:先在源库聚合,再拉小结果 JOIN
WITH daily_stats AS (
    SELECT user_id, DATE(order_date) AS day, SUM(amount) AS daily_total
    FROM postgres.orders
    WHERE order_date >= '2026-08-01'
    GROUP BY user_id, DATE(order_date)
)
SELECT u.name, d.day, d.daily_total
FROM daily_stats d
JOIN mysql.users u ON d.user_id = u.id;

九、性能对比:跨库 JOIN vs 传统 ETL

方案准备时间每日查询时间维护成本代码行数
传统 ETL(Python 导数据)30 分钟5 秒高(脚本易碎)~200 行
传统 ETL(Airflow 调度)2 小时3 秒~500 行
DuckDB 跨库 JOIN5 分钟8 秒低(SQL 即代码)<20 行

测试场景:PostgreSQL 100 万行订单 + MySQL 50 万行用户 + CSV 1000 行地区数据 硬件:8 核 16GB MacBook Pro

DuckDB 的方案虽然单次查询稍慢(需要跨库传输),但准备和维护成本极低——一条 SQL 搞定,不用写 ETL 脚本,不用维护调度任务。


十、最佳实践:何时用跨库查询?

✅ 适合用 DuckDB 跨库的场景:

  1. 临时性数据分析(周报、月报、一次性探索)
  2. 数据量适中(每个源库筛选后 < 1000 万行)
  3. 不想搭建复杂 ETL 管道
  4. 需要快速验证假设
  5. 数据源不稳定(经常换表结构或添加字段)

❌ 不适合的场景:

  1. 超大数据量(> 1 亿行)→ 建议先导入 DuckDB 再分析
  2. 高频实时查询(< 1 秒要求)→ 建议用 ClickHouse 等 OLAP 引擎
  3. 跨库写入 → DuckDB 只支持 READ_ONLY 连接外部库
  4. 极端低延迟场景 → 网络往返会成为瓶颈

十一、变现建议

这个技能的市场价值极高,因为 99% 的公司都存在数据孤岛问题

目标客户

  • 中小企业:数据分散在多个系统,没有专门的数据团队
  • 电商公司:订单系统 + CRM + 财务系统各自独立
  • 连锁零售:各门店 + 总部 + 供应链多个数据源
  • 传统企业转型中:老旧数据库 + 新系统并存

报价方案

服务类型报价交付内容周期
一次性数据整合¥2,000-5,000跨库查询脚本 + 报表模板1-3 天
月报自动化¥500-1,500/月定时生成跨源经营报表每月更新
数据仓库搭建¥5,000-15,000完整的 ETL 管线 + 分析看板1-2 周
数据整合培训¥1,500-3,000/次教客户团队用 DuckDB 自己查半天

获客渠道

  1. 闲鱼/猪八戒:搜索"数据整合"、“跨库查询”、“报表自动化”,直接展示 DuckDB 方案对比
  2. 企业微信社群:加入电商/零售行业群,主动询问"你们报表怎么出的?"
  3. 技术博客引流:本文就是在帮你建立专业形象

竞品对比

方案价格优势劣势
传统 ETL 工具 (Kettle/DataX)免费但需运维功能全面配置复杂,学习成本高
商业 BI (Tableau/Power BI)¥500-2000/月可视化好价格贵,跨源能力弱
花钱请人手动做¥300-500/月不用动脑不稳定,离职就断
DuckDB 方案(你)¥2,000-5,000一次搞定永久使用需要客户有基本技术认知

变现话术模板

“张总,我看到你们公司的数据分散在好几个系统里,每次出报表都要手工合并对吧?我有一套方案,用一条 SQL 就能把你所有系统的数据串起来,以后点一下就跑出来完整报表。前期整合费用 ¥3,000,以后每个月自动出,¥800/月。感兴趣的话,我可以先免费帮你做一次数据探查。你看这周三方便不?”


十二、今晚行动

  1. 安装 DuckDB:pip install duckdb
  2. 找一个你手头的 CSV 文件和一个数据库(PostgreSQL / MySQL / SQLite 都行)
  3. 用 ATTACH 命令挂载它们
  4. 写一条跨库 JOIN 的 SQL,看看能不能直接跑出结果
  5. 对比:同样的需求用 Python 写要多少行代码?用 DuckDB 跨库查询要多少行?

记住:数据分析的效率瓶颈往往不是查询速度,而是数据准备时间。DuckDB 跨库查询把"准备"压缩到了零。


📌 收藏笔记,下次做跨源数据分析直接翻出来用。 🔍 duckdblab.org 系统学习 DuckDB 实战技巧。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。