• DuckDB 是什么及适用场景

    DuckDB 是什么

    DuckDB 是一款集轻量级、高性能于一体的嵌入式分析型数据库,以其卓越的数据查询和分析能力在数据库领域崭露头角。这款基于C++ 编写的数据库,以其先进的设计理念和高效的存储引擎,为大规模数据处理提供了强有力的支持。

    DuckDB 采用了独特的列式存储方式,相较于传统的行式存储,这种存储方式能更加高效地读取和分析数据。通过将相同列的数据集中存储在连续的物理位置,列式存储显著提升了数据的访问速度。此外,DuckDB 还利用压缩算法进一步减少了存储空间需求,降低了 I/O 开销,让数据处理变得更加轻松高效。

    在查询优化和执行方面,DuckDB 同样表现出色。它支持标准的 SQL 查询语言,允许用户通过简洁明了的 SQL 语句实现复杂的数据查询和分析任务。其优化的执行引擎充分利用内存优势,提供高并发和低延迟的查询体验。同时,DuckDB 还内置了多种查询优化技术,通过智能地选择查询路径和减少数据扫描量,进一步提高查询效率。

    作为一款嵌入式数据库,DuckDB 还具备良好的集成性。它提供了丰富的 API 接口,使开发者能够轻松地将 DuckDB 嵌入到各种应用程序中,实现无缝的数据管理和查询功能。这种高度的灵活性使得 DuckDB 成为各类应用系统中的理想选择。

    DuckDB 的适用场景

    实时分析

    在追求速度和响应能力的实时分析领域,DuckDB 以其高性能和低延迟的特性成为不可或缺的工具。无论是监控系统、实时报表还是预警系统,DuckDB 都能快速响应大量的数据查询请求,提供准确而及时的分析结果。这使得企业能够实时掌握业务动态,做出快速而明智的决策。

    此外,DuckDB 的内存优化特性进一步提升了实时分析的效率。通过将尽可能多的数据加载到内存中,DuckDB 能够显著减少磁盘 I/O 操作,从而加快查询速度。这种优势在处理大规模数据集时尤为明显,让实时分析变得更加轻松高效。

    数据仓库

    DuckDB 同样适用于构建和管理数据仓库系统。作为轻量级的数据仓库解决方案,它能够轻松应对数据整合、清洗和转换等复杂任务。通过 ETL 过程,DuckDB 能够将来自不同数据源的数据整合到一起,形成统一的数据视图。然后,用户可以利用其强大的查询和分析能力,深入挖掘数据的价值,发现潜在的商业机会。

    相较于传统的大型关系型数据库,DuckDB 在数据仓库场景中具有更低的部署和维护成本。其轻量级特性使得数据仓库的构建和管理变得更加简单高效,无需投入大量的人力和物力资源。同时,DuckDB 的高性能和可扩展性也能够满足大型数据仓库的需求,确保数据的快速处理和查询。

    嵌入式应用

    DuckDB 的嵌入式特性使其成为嵌入式应用的理想选择。无论是物联网设备还是移动应用程序,都可以通过集成 DuckDB 实现本地数据分析能力。这意味着设备或应用程序可以直接在本地处理和分析数据,无需依赖远程服务器或云端资源。

    在物联网设备中,DuckDB 可以帮助实现实时的数据采集和分析,提升设备的智能化水平。在移动应用程序中,它可以用于存储和分析用户数据,提供个性化的服务和推荐,提升用户体验。

    大数据预处理

    大数据预处理是数据挖掘和机器学习流程中不可或缺的一环。在这一环节中,DuckDB 同样可以发挥重要作用。它能够高效地对数据进行清洗、转换和聚合等操作,为后续的机器学习或数据挖掘任务提供高质量的输入数据。

    DuckDB 支持各种复杂的 SQL 查询和聚合操作,使得预处理工作变得更加简单高效。用户可以通过编写SQL语句来定义数据转换规则和处理逻辑,然后让 DuckDB 自动完成这些任务。这大大减轻了数据科学家的工作负担,让他们能够更专注于数据分析和模型构建等核心任务。

    接下来我们就理论联系实际地使用一下 DuckDB,看看它有什么惊艳的地方。

    安装及简单使用

    # MacOS 安装
    $ brew install duckdb
    
    # Python 安装
    pip install duckdb
    
    # NodeJS 安装
    npm install duckdb
    
    # 或者从源码安装,愿意折腾的同学可以自行去安装

    安装完成后,怎么使用呢?可以打开命令行直接输入 “duckdb” 即可进入其命令行界面:

    (venv311) ➜  mypostgres git:(dev) duckdb
    v1.0.0 1f98600c2c
    Enter ".help" for usage hints.
    Connected to a transient in-memory database.
    Use ".open FILENAME" to reopen on a persistent database.
    
    -- 查看版本号
    D SELECT version();
    ┌───────────┐
    │ version()
    │  varchar
    ├───────────┤
    │ v1.0.0    │
    └───────────┘
    
    -- 下面的是官方的 Live Demo 中的一个示例,从 S3 存储上面下载了一个数据文件,并导入到 DuckDB 中
    D INSTALL httpfs;
    100% ▕████████████████████████████████████████████████████████████▏ 
    D CREATE TABLE train_services AS FROM 's3://duckdb-blobs/train_services.parquet';
    100% ▕████████████████████████████████████████████████████████████▏ 
    D show tables;
    ┌────────────────┐
    │      name      │
    │    varchar     │
    ├────────────────┤
    │ train_services │
    └────────────────┘
    -- 或者执行 .tables 也可以查看有哪些表
    D .tables
    train_services

    如何查看表结构呢?

    D DESCRIBE train_services;
    +----------------+-------------+------+-----+---------+-------+
    |  column_name   | column_type | null | key | default | extra |
    +----------------+-------------+------+-----+---------+-------+
    | service_id     | BIGINT      | YES  |     |         |       |
    | date           | DATE        | YES  |     |         |       |
    | type           | VARCHAR     | YES  |     |         |       |
    | train_number   | BIGINT      | YES  |     |         |       |
    | station_code   | VARCHAR     | YES  |     |         |       |
    | station_name   | VARCHAR     | YES  |     |         |       |
    | departure_time | TIMESTAMP   | YES  |     |         |       |
    | arrival_time   | TIMESTAMP   | YES  |     |         |       |
    +----------------+-------------+------+-----+---------+-------+
    changes: 380959   total_changes: 380959

    查询 train_services 表的前 10 条数据:

    D SELECT * from train_services LIMIT 10;
    ┌────────────┬────────────┬───────────┬──────────────┬──────────────┬──────────────────────┬─────────────────────┬─────────────────────┐
    │ service_id │    date    │   type    │ train_number │ station_code │     station_name     │   departure_time    │    arrival_time     │
    │   int64    │    date    │  varchar  │    int64     │   varchar    │       varchar        │      timestamp      │      timestamp      │
    ├────────────┼────────────┼───────────┼──────────────┼──────────────┼──────────────────────┼─────────────────────┼─────────────────────┤
    │   111961172023-05-15 │ Intercity │         1410 │ RTD          │ Rotterdam Centraal   │ 2023-05-15 00:00:00 │                     │
    │   111961172023-05-15 │ Intercity │         1410 │ DT           │ Delft                │ 2023-05-15 00:13:002023-05-15 00:13:00
    │   111961172023-05-15 │ Intercity │         1410 │ GV           │ Den Haag HS          │ 2023-05-15 00:29:002023-05-15 00:21:00
    │   111961172023-05-15 │ Intercity │         1410 │ LEDN         │ Leiden Centraal      │ 2023-05-15 00:45:002023-05-15 00:44:00
    │   111961172023-05-15 │ Intercity │         1410 │ SHL          │ Schiphol Airport     │ 2023-05-15 01:03:002023-05-15 01:01:00
    │   111961172023-05-15 │ Intercity │         1410 │ ASD          │ Amsterdam Centraal   │ 2023-05-15 01:19:002023-05-15 01:17:00
    │   111961172023-05-15 │ Intercity │         1410 │ UT           │ Utrecht Centraal     │                     │ 2023-05-15 01:48:00
    │   111961182023-05-15 │ Nightjet  │          420 │ NURNB        │ Nürnberg Hbf         │ 2023-05-15 00:01:00 │                     │
    │   111961182023-05-15 │ Nightjet  │          420 │ FFS          │ Frankfurt (Main) Süd │ 2023-05-15 01:47:002023-05-15 01:45:00
    │   111961182023-05-15 │ Nightjet  │          420 │ FFFM         │ Frankfurt (M) Hbf    │                     │                     │
    ├────────────┴────────────┴───────────┴──────────────┴──────────────┴──────────────────────┴─────────────────────┴─────────────────────┤
    10 rows                                                                                                                    8 columns
    └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

    还可以设置输出样式,默认是 duckbox 样式:

    D .help mode
    .mode MODE ?TABLE?       Set output mode
       MODE is one of:
         ascii     Columns/rows delimited by 0x1F and 0x1E
         box       Tables using unicode box-drawing characters
         csv       Comma-separated values
         column    Output in columns.  (See .width)
         duckbox   Tables with extensive features
         html      HTML <table> code
         insert    SQL insert statements for TABLE
         json      Results in a JSON array
         jsonlines Results in a NDJSON
         latex     LaTeX tabular environment code
         line      One value per line
         list      Values delimited by "|"
         markdown  Markdown table format
         quote     Escape answers as for SQL
         table     ASCII-art table
         tabs      Tab-separated values
         tcl       TCL list elements
         trash     No output
    
    D .mode table
    
    D SELECT * from train_services LIMIT 10;
    +------------+------------+-----------+--------------+--------------+----------------------+---------------------+---------------------+
    | service_id |    date    |   type    | train_number | station_code |     station_name     |   departure_time    |    arrival_time     |
    +------------+------------+-----------+--------------+--------------+----------------------+---------------------+---------------------+
    | 11196117   | 2023-05-15 | Intercity | 1410         | RTD          | Rotterdam Centraal   | 2023-05-15 00:00:00 |                     |
    | 11196117   | 2023-05-15 | Intercity | 1410         | DT           | Delft                | 2023-05-15 00:13:00 | 2023-05-15 00:13:00 |
    | 11196117   | 2023-05-15 | Intercity | 1410         | GV           | Den Haag HS          | 2023-05-15 00:29:00 | 2023-05-15 00:21:00 |
    | 11196117   | 2023-05-15 | Intercity | 1410         | LEDN         | Leiden Centraal      | 2023-05-15 00:45:00 | 2023-05-15 00:44:00 |
    | 11196117   | 2023-05-15 | Intercity | 1410         | SHL          | Schiphol Airport     | 2023-05-15 01:03:00 | 2023-05-15 01:01:00 |
    | 11196117   | 2023-05-15 | Intercity | 1410         | ASD          | Amsterdam Centraal   | 2023-05-15 01:19:00 | 2023-05-15 01:17:00 |
    | 11196117   | 2023-05-15 | Intercity | 1410         | UT           | Utrecht Centraal     |                     | 2023-05-15 01:48:00 |
    | 11196118   | 2023-05-15 | Nightjet  | 420          | NURNB        | Nürnberg Hbf         | 2023-05-15 00:01:00 |                     |
    | 11196118   | 2023-05-15 | Nightjet  | 420          | FFS          | Frankfurt (Main) Süd | 2023-05-15 01:47:00 | 2023-05-15 01:45:00 |
    | 11196118   | 2023-05-15 | Nightjet  | 420          | FFFM         | Frankfurt (M) Hbf    |                     |                     |
    +------------+------------+-----------+--------------+--------------+----------------------+---------------------+---------------------+

    还可以获取命令的帮助,进入 DuckDB 后,输入 .help 即可查看帮助手信息:

    D .help
    .bail on|off             Stop after hitting an error.  Default OFF
    .binary on|off           Turn binary output on or off.  Default OFF
    .cd DIRECTORY            Change the working directory to DIRECTORY
    .changes on|off          Show number of rows changed by SQL
    .check GLOB              Fail if output since .testcase does not match
    .columns                 Column-wise rendering of query results
    .constant ?COLOR?        Sets the syntax highlighting color used for constant values
    .constantcode ?CODE?     Sets the syntax highlighting terminal code used for constant values
    ......
    .sha3sum ...             Compute a SHA3 hash of database content
    .shell CMD ARGS...       Run CMD ARGS... in a system shell
    .show                    Show the current values for various settings
    .system CMD ARGS...      Run CMD ARGS... in a system shell
    .tables ?TABLE?          List names of tables matching LIKE pattern TABLE
    .testcase NAME           Begin redirecting output to 'testcase-out.txt'
    .timer on|off            Turn SQL timer on or off
    .width NUM1 NUM2 ...     Set minimum column widths for columnar output

    导出 CSV 文件

    可以轻松地把数据导出到 CSV 文件:

    D COPY train_services TO 'output.csv' (HEADER, DELIMITER ',');
    
    -- 查看文件前 10 行内容
    D .shell head /tmp/output.csv
    service_id,date,type,train_number,station_code,station_name,departure_time,arrival_time
    11196117,2023-05-15,Intercity,1410,RTD,Rotterdam Centraal,2023-05-15 00:00:00,
    11196117,2023-05-15,Intercity,1410,DT,Delft,2023-05-15 00:13:00,2023-05-15 00:13:00
    11196117,2023-05-15,Intercity,1410,GV,Den Haag HS,2023-05-15 00:29:00,2023-05-15 00:21:00
    11196117,2023-05-15,Intercity,1410,LEDN,Leiden Centraal,2023-05-15 00:45:00,2023-05-15 00:44:00
    11196117,2023-05-15,Intercity,1410,SHL,Schiphol Airport,2023-05-15 01:03:00,2023-05-15 01:01:00
    11196117,2023-05-15,Intercity,1410,ASD,Amsterdam Centraal,2023-05-15 01:19:00,2023-05-15 01:17:00
    11196117,2023-05-15,Intercity,1410,UT,Utrecht Centraal,,2023-05-15 01:48:00
    11196118,2023-05-15,Nightjet,420,NURNB,Nürnberg Hbf,2023-05-15 00:01:00,
    11196118,2023-05-15,Nightjet,420,FFS,Frankfurt (Main) Süd,2023-05-15 01:47:00,2023-05-15 01:45:00

    查询的数据也可以导出到 CSV 文件:

    COPY (SELECT * FROM tbl) TO 'output.csv' (HEADER, DELIMITER ',');

    导入 CSV 文件

    导入 CSV 文件有多种方式:

    -- 通过 read_csv() 函数直接读取 CSV 文件
    D SELECT * FROM read_csv("/tmp/output.csv");
    ┌────────────┬────────────┬──────────────────────┬──────────────┬──────────────┬───────────────────────────┬─────────────────────┬─────────────────────┐
    │ service_id │    date    │         type         │ train_number │ station_code │       station_name        │   departure_time    │    arrival_time     │
    │   int64    │    date    │       varchar        │    int64     │   varchar    │          varchar          │      timestamp      │      timestamp      │
    ├────────────┼────────────┼──────────────────────┼──────────────┼──────────────┼───────────────────────────┼─────────────────────┼─────────────────────┤
    │   111961172023-05-15 │ Intercity            │         1410 │ RTD          │ Rotterdam Centraal        │ 2023-05-15 00:00:00 │                     │
    │   111961172023-05-15 │ Intercity            │         1410 │ DT           │ Delft                     │ 2023-05-15 00:13:002023-05-15 00:13:00
    │   111961172023-05-15 │ Intercity            │         1410 │ GV           │ Den Haag HS               │ 2023-05-15 00:29:002023-05-15 00:21:00
    │   111961172023-05-15 │ Intercity            │         1410 │ LEDN         │ Leiden Centraal           │ 2023-05-15 00:45:002023-05-15 00:44:00
    │   111961172023-05-15 │ Intercity            │         1410 │ SHL          │ Schiphol Airport          │ 2023-05-15 01:03:002023-05-15 01:01:00
    ......
    ......
    │   112391152023-05-21 │ Stoptrein            │        20397 │ RHEINE       │ Rheine                    │ 2023-05-21 11:03:00 │                     │
    │   112391152023-05-21 │ Stoptrein            │        20397 │ BH           │ Bad Bentheim              │ 2023-05-21 11:16:002023-05-21 11:14:00
    │   112391152023-05-21 │ Stoptrein            │        20397 │ HGL          │ Hengelo                   │                     │ 2023-05-21 11:32:00
    ├────────────┴────────────┴──────────────────────┴──────────────┴──────────────┴───────────────────────────┴─────────────────────┴─────────────────────┤
    380959 rows (40 shown)                                                                                                                     8 columns
    └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
    
    -- 也可以不显式使用 read_csv() 函数
    D SELECT * FROM "/tmp/output.csv";
    ┌────────────┬────────────┬──────────────────────┬──────────────┬──────────────┬───────────────────────────┬─────────────────────┬─────────────────────┐
    │ service_id │    date    │         type         │ train_number │ station_code │       station_name        │   departure_time    │    arrival_time     │
    │   int64    │    date    │       varchar        │    int64     │   varchar    │          varchar          │      timestamp      │      timestamp      │
    ├────────────┼────────────┼──────────────────────┼──────────────┼──────────────┼───────────────────────────┼─────────────────────┼─────────────────────┤
    │   111961172023-05-15 │ Intercity            │         1410 │ RTD          │ Rotterdam Centraal        │ 2023-05-15 00:00:00 │                     │
    │   111961172023-05-15 │ Intercity            │         1410 │ DT           │ Delft                     │ 2023-05-15 00:13:002023-05-15 00:13:00
    ......
    ......
    │   112391152023-05-21 │ Stoptrein            │        20397 │ HGL          │ Hengelo                   │                     │ 2023-05-21 11:32:00
    ├────────────┴────────────┴──────────────────────┴──────────────┴──────────────┴───────────────────────────┴─────────────────────┴─────────────────────┤
    380959 rows (40 shown)                                                                                                                     8 columns
    └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

    关于数据导入可以参阅官方文档:https://duckdb.org/docs/data/overview。DuckDB 支持非常多的数据库格式,为我们快速做数据分析提供各种便捷功能。

    连接或创建数据库

    前面的操作都是在内存中进行的,也就意味着当我们退出当前的 DuckDB 会话时,数据也会随之释放。DuckDB 也支持持久模式运行,这样数据可以存放在硬盘上面。我们可以看下 DuckDB 程序的命令行帮助:

    ➜  knowledge git:(liucc) ✗ duckdb --help
    Usage: duckdb [OPTIONS] FILENAME [SQL]
    FILENAME is the name of an DuckDB database. A new database is created
    if the file does not previously exist.
    OPTIONS include:
       -append              append the database to the end of the file
       -ascii               set output mode to 'ascii'
       -bail                stop after hitting an error
       -batch               force batch I/O
       -box                 set output mode to 'box'
       -column              set output mode to 'column'
       -cmd COMMAND         run "COMMAND" before reading stdin
       -c COMMAND           run "COMMAND" and exit
       -csv                 set output mode to 'csv'
       -echo                print commands before execution
       -init FILENAME       read/process named file
       -[no]header          turn headers on or off
       -help                show this message
       -html                set output mode to HTML
       -interactive         force interactive I/O
       -json                set output mode to 'json'
       -line                set output mode to 'line'
       -list                set output mode to 'list'
       -markdown            set output mode to 'markdown'
       -newline SEP         set output row separator. Default: '\n'
       -nofollow            refuse to open symbolic links to database files
       -no-stdin            exit after processing options instead of reading stdin
       -nullvalue TEXT      set text string for NULL values. Default ''
       -quote               set output mode to 'quote'
       -readonly            open the database read-only
       -s COMMAND           run "COMMAND" and exit
       -separator SEP       set output column separator. Default: '|'
       -stats               print memory stats before each finalize
       -table               set output mode to 'table'
       -unredacted          allow printing unredacted secrets
       -unsigned            allow loading of unsigned extensions
       -version             show DuckDB version

    通过命令行的帮助信息可以知道,我们可以在 duckdb 程序后面提供一个 FILENAME,这个 FILENAME 就是一个数据库,如果存在就打开(连接)该库,如果该库不存在则创建。

    接下来我们演示一下,首先我们准备一些测试数据:

    $ wget -c -O ~/Downloads/services-2023-10.csv.gz https://opendata.rijdendetreinen.nl/public/services/services-2023-10.csv.gz

    下载完成数据之后,我们通过 DuckDB 创建一个空的库,然后导入该示例数据。如下:

    $ duckdb railway.db

    [!tip]

    该文件可以为任意扩展名,但 .db.duckdb 是两种常见选择,有时也会使用 .ddb

    进入数据库后,我们导入前面已下载的压缩数据:

    D CREATE TABLE services AS FROM '~/Downloads/services-2023.csv.gz';
    100% ▕████████████████████████████████████████████████████████████▏ 
    
    -- 查看一下有哪些表
    D .tables
    services
    
    -- 查看一下当前的库是什么,以下三种方式都可以
    D .data
    railway: railway.db
    D .database
    railway: railway.db
    D .databases
    railway: railway.db

    上面都发生了什么?我们简单解释一下:

    • 我们无需显式定义我们的 services 表的结构,也不需要使用 COPY FROM 语句。DuckDB 会自动检测到 ‘services-2023.csv.gz’ 是一个 gzip 压缩的 CSV 文件,因此它调用 read_csv() 函数,解压文件并根据其内容使用 CSV 嗅探器推断其数据结构。

    • 查询利用了 DuckDB 的 FROM-first 语法,允许用户省略 SELECT * 子句。因此,SQL 语句 FROM 'services-2023.csv.gz';SELECT * FROM 'services-2023.csv.gz'; 的简写。

      D FROM services LIMIT 10;
      ┌────────────────┬──────────────┬──────────────┬─────────────────┬───┬────────────────────┬──────────────────────┬─────────────────────┬──────────────────────┬──────────────────────┐
      │ Service:RDT-ID │ Service:Date │ Service:Type │ Service:Company │ … │ Stop:Arrival delay │ Stop:Arrival cance…  │ Stop:Departure time │ Stop:Departure delay │ Stop:Departure can…  │
      │     int64      │     date     │   varchar    │     varchar     │   │       int64        │       boolean        │      timestamp      │        int64         │       boolean        │
      ├────────────────┼──────────────┼──────────────┼─────────────────┼───┼────────────────────┼──────────────────────┼─────────────────────┼──────────────────────┼──────────────────────┤
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                    │                      │ 2023-01-01 01:00:00 │                    1false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  0false                │ 2023-01-01 01:13:00 │                    0false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  0false                │ 2023-01-01 01:22:00 │                    0false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  0false                │ 2023-01-01 01:40:00 │                    0false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  0false                │ 2023-01-01 02:03:00 │                    0false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  2false                │ 2023-01-01 02:12:00 │                    2false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  1false                │ 2023-01-01 02:19:00 │                    3false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  3false                │ 2023-01-01 02:30:00 │                    3false                │
      │       103677022023-01-01   │ Intercity    │ NS              │ … │                  2false                │                     │                      │                      │
      │       103677032023-01-01   │ Nightjet     │ NS Int          │ … │                    │                      │ 2023-01-01 01:01:00 │                    0false                │
      ├────────────────┴──────────────┴──────────────┴─────────────────┴───┴────────────────────┴──────────────────────┴─────────────────────┴──────────────────────┴──────────────────────┤
      10 rows                                                                                                                                                       17 columns (9 shown)
      └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
    • 查询创建一个名为 services 的表,并使用 CSV 读取器的结果写入该表。使用 CREATE TABLE ...AS 语句。

    我们看看 services 表里面有多少行数据:

    D SELECT count(*) AS count_services FROM services;
    ┌────────────────┐
    │ count_services │
    │     int64      │
    ├────────────────┤
    │       21239393
    └────────────────┘
    
    D SELECT format('{:,}', count(*)) AS count_services FROM services;
    ┌────────────────┐
    │ count_services │
    │    varchar     │
    ├────────────────┤
    21,239,393     │
    └────────────────┘

    我们现在看下 railway.db 目前占用了多少硬盘空间:

    ➜  softwares ll -h railway.db 
    -rw-r--r--  1 liuchuan  staff   394M Jun 17 14:40 railway.db

    与其他数据库集成

    既然 DuckDB 有 ETL 功能,那么它就可以与多个数据库引擎进行交互。接下来我们看下 DuckDB 与其他数据库引擎的集成。

    与 MySQL 集成

    要直接在 MySQL 数据库上运行查询,需要 mysql 扩展。可以使用 INSTALL SQL 命令安装扩展。只需要运行一次即可:

    D INSTALL mysql;
    100% ▕████████████████████████████████████████████████████████████

    要想使用 mysql 扩展,需要使用 LOAD SQL 命令:

    D LOAD mysql;

    mysql 扩展加载完成后,我们可以使用 ATTACH SQL 命令来映射到一个 MySQL 实例上面。语法如下:

    ATTACH 'host=localhost user=root port=0 database=mysqlscanner' AS mysql_db (TYPE mysql_scanner, READ_ONLY);
    USE mysql_db;

    ATTACH 使用的字符串是 PostgreSQL 风格的连接字符串(不是 MySQL 连接字符串!)。这是一个以 {key}={value} 格式提供的连接参数列表。以下是有效参数列表。任何未提供的选项都会被其默认值所取代。

    参数 默认值
    database NULL
    host localhost
    password
    port 0
    socket NULL
    user current user

    接下来是一个实际的例子,让我们连接到本地的一个 MySQL 数据库:

    D ATTACH 'host=localhost user=root password=123456 port=3306 database=world' AS mysql_world_db (TYPE mysql_scanner, READ_ONLY);
    
    -- 接下来就像是使用 MySQL 那样来使用数据库了
    D use mysql_world_db;
    D show tables;
    ┌─────────────────┐
    │      name       │
    │     varchar     │
    ├─────────────────┤
    │ city            │
    │ country         │
    │ countrylanguage │
    └─────────────────┘
    
    -- 查询一下 country 表里面的数据
    D SELECT Code, Name, Region FROM country WHERE Code LIKE 'C%%';
    ┌─────────┬───────────────────────────────────────┬───────────────────────────┐
    │  Code   │                 Name                  │          Region           │
    varchar │                varchar                │          varchar          │
    ├─────────┼───────────────────────────────────────┼───────────────────────────┤
    │ CAF     │ Central African Republic              │ Central Africa            │
    │ CAN     │ Canada                                │ North America             │
    │ CCK     │ Cocos (Keeling) Islands               │ Australia and New Zealand │
    │ CHE     │ Switzerland                           │ Western Europe            │
    │ CHL     │ Chile                                 │ South America             │
    │ CHN     │ China                                 │ Eastern Asia              │
    │ CIV     │ Côte d’Ivoire                         │ Western Africa            │
    │ CMR     │ Cameroon                              │ Central Africa            │
    │ COD     │ Congo, The Democratic Republic of the │ Central Africa            │
    │ COG     │ Congo                                 │ Central Africa            │
    │ COK     │ Cook Islands                          │ Polynesia                 │
    │ COL     │ Colombia                              │ South America             │
    │ COM     │ Comoros                               │ Eastern Africa            │
    │ CPV     │ Cape Verde                            │ Western Africa            │
    │ CRI     │ Costa Rica                            │ Central America           │
    │ CUB     │ Cuba                                  │ Caribbean                 │
    │ CXR     │ Christmas Island                      │ Australia and New Zealand │
    │ CYM     │ Cayman Islands                        │ Caribbean                 │
    │ CYP     │ Cyprus                                │ Middle East               │
    │ CZE     │ Czech Republic                        │ Eastern Europe            │
    ├─────────┴───────────────────────────────────────┴───────────────────────────┤
    20 rows                                                           3 columns
    └─────────────────────────────────────────────────────────────────────────────┘

    由于我们连接时指定了 READ_ONLY 选项,那么我们是以只读模式连接过来的,无法进行增删改的操作。如下:

    -- 创建一张表
    D CREATE TABLE tbl (id INTEGER, name VARCHAR);
    Invalid Input Error: Cannot execute statement of type "CREATE" on database "mysql_world_db" which is attached in read-only mode!
    
    -- 删除一条数据
    D DELETE FROM country WHERE Code = 'USA';
    Invalid Input Error: Cannot execute statement of type "DELETE" on database "mysql_world_db" which is attached in read-only mode!

    [!tip]

    以读写模式连接数据库:只需设置 (TYPE MYSQL) 即可,去掉 READ_ONLY。

    关于与 MySQL 集成相关的更详细信息,还请参阅官方文档:https://duckdb.org/docs/extensions/mysql.html#supported-operations

    与 PostgreSQL 集成

    与集成 MySQL 一样,需要先安装扩展,然后加载。

    D INSTALL postgres;
    100% ▕████████████████████████████████████████████████████████████
    
    D LOAD postgres;

    安装 postgres 扩展后,可以使用 postgres_scan 函数从 PostgreSQL 查询表:

    -- scan the table "mytable" from the schema "public" in the database "mydb"
    SELECT * FROM postgres_scan('host=localhost port=5432 dbname=mydb', 'public', 'mytable');

    “postgres_scan” 函数的第一个参数是 [PostgreSQL 连接字符串](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING),这是以 “{key}={value}” 格式提供的连接参数列表。下面是详细的参数列表。

    名称 描述 默认值
    host 指定要连接的主机名 localhost
    hostaddr 指定要连接的 IP 地址 localhost
    port 指定端口号 5432
    user 指定用户名 [OS user name]
    password 指定密码
    dbname 需要连接的数据库名 [user]
    passfile 指定存储密码的文件 ~/.pgpass

    接下来我们连接到本地的一个 PostgreSQL 数据库上面进行一个查询:

    D SELECT * FROM postgres_scan('host=localhost port=5432 user=forum password=forum dbname=forumdb', 'forum', 'categories');
    ┌───────┬───────────────────────┬──────────────────────────────────┐
    │  pk   │         title         │           description            │
    │ int32 │        varchar        │             varchar              │
    ├───────┼───────────────────────┼──────────────────────────────────┤
    │     3 │ Programming Languages │ All about programming languages  │
    │     5 │ Software engineering  │ Software engineering discussions │
    │     1Database Discussions  │ Database discussions             │
    │     2 │ Unix/Linux discussion │ Unix and Linux discussions       │
    │     4 │ A.I                   │ Machine Learning discussions     │
    └───────┴───────────────────────┴──────────────────────────────────┘

    或者,可以使用 ATTACH 命令连接整个数据库。这允许我们像查询常规数据库一样查询存储在 PostgreSQL 数据库中的所有表。语法如下:

    -- Attach the Postgres database using the given connection string
    ATTACH 'host=localhost port=5432 dbname=mydb' AS test (TYPE postgres);
    -- The table "tbl_name" can now be queried as if it is a regular table
    SELECT * FROM test.tbl_name;
    -- Switch the active database to "test"
    USE test;
    
    -- List all tables in the file
    SHOW TABLES;

    一个实际的例子:

    D ATTACH 'host=localhost port=5432 user=forum password=forum dbname=forumdb' AS pg_forum_db (TYPE postgres);
    D use pg_forum_db;
    
    -- 查询数据
    D SELECT * from forum.categories;
    ┌───────┬───────────────────────┬──────────────────────────────────┐
    │  pk   │         title         │           description            │
    │ int32 │        varchar        │             varchar              │
    ├───────┼───────────────────────┼──────────────────────────────────┤
    │     3 │ Programming Languages │ All about programming languages  │
    │     5 │ Software engineering  │ Software engineering discussions │
    │     1Database Discussions  │ Database discussions             │
    │     2 │ Unix/Linux discussion │ Unix and Linux discussions       │
    │     4 │ A.I                   │ Machine Learning discussions     │
    └───────┴───────────────────────┴──────────────────────────────────┘

    由于我们在映射数据库时并没有使用 READ_ONLY 选项,因此可以对 pg_forum_db 进行读写操作。如下:

    D CREATE TABLE forum.demo (id INTEGER, name VARCHAR);
    D DESCRIBE forum.demo;
    ┌─────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
    │ column_name │ column_type │  null   │   key   │ default │  extra  │
    │   varchar   │   varchar   │ varcharvarcharvarcharvarchar
    ├─────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
    │ id          │ INTEGER     │ YES     │         │         │         │
    │ name        │ VARCHAR     │ YES     │         │         │         │
    └─────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘
    
    -- 插入一条数据
    D INSERT INTO forum.demo VALUES (42, 'DuckDB');
    
    -- 查看该数据
    D FROM forum.demo;
    ┌───────┬─────────┐
    │  id   │  name   │
    │ int32 │ varchar
    ├───────┼─────────┤
    │    42 │ DuckDB  │
    └───────┴─────────┘
    
    -- 修改该数据
    D UPDATE forum.demo set id=9 WHERE name='DuckDB';
    D FROM forum.demo;
    ┌───────┬─────────┐
    │  id   │  name   │
    │ int32 │ varchar
    ├───────┼─────────┤
    │     9 │ DuckDB  │
    └───────┴─────────┘
    
    -- 删除该数据
    D DELETE FROM forum.demo WHERE id=9;
    D FROM forum.demo;
    ┌───────┬─────────┐
    │  id   │  name   │
    │ int32 │ varchar
    ├───────┴─────────┤
    │     0 rows      │
    └─────────────────┘
    
    -- 删除该表
    D DROP TABLE forum.demo;
    D DESCRIBE forum.demo;
    Catalog Error: Table with name demo does not exist!
    Did you mean "mysql_world_db.mysql.db"?

    可以检查下我们目前都连接了哪些数据库:

    D show databases;
    ┌────────────────┐
    │ database_name  │
    │    varchar     │
    ├────────────────┤
    │ mysql_world_db │
    │ pg_forum_db    │
    │ railway        │
    └────────────────┘
    
    D .databases
    railway: railway.db
    mysql_world_db: host=localhost user=root password=123456 port=3306 database=world
    pg_forumd_db: host=localhost port=5432 user=forum password=forum dbname=forumdb

    关于与 PostgreSQL 集成更详细的信息可以参阅官方文档:https://duckdb.org/docs/extensions/postgres.html

    DuckDB 还能与 SQLite 进行集成,这里就不再演示了,可以参阅官方文档:https://duckdb.org/docs/guides/database_integration/sqlite

    扩展支持

    一个产品强或系统不强,关键还是要看下扩展多不多。正所谓:“功能不够,扩展来凑”。我们看下缺省状态下 DuckDB 支持的扩展:

    不同的 DuckDB 客户端都附带了一组不同的扩展。我们在下表中进行一个比对:

    扩展名称 CLI (duckdb.org) CLI (Homebrew) Python R Java Node.js
    autocomplete yes yes
    excel yes
    fts yes yes
    httpfs yes
    icu yes yes yes yes yes
    json yes yes yes yes yes
    parquet yes yes yes yes yes yes
    tpcds yes
    tpch yes yes

    关于官方提供的扩展,可以参阅官方文档:https://duckdb.org/docs/extensions/official_extensions

    可以查看当前 DuckDB 已安装的扩展:

    D .mode line
    D SELECT * FROM duckdb_extensions();
       extension_name = arrow
               loaded = false
            installed = false
         install_path = 
          description = A zero-copy data integration between Apache Arrow and DuckDB
              aliases = []
    extension_version = 
         install_mode = 
       installed_from = 
    
       extension_name = autocomplete
               loaded = true
            installed = true
         install_path = (BUILT-IN)
          description = Adds support for autocomplete in the shell
              aliases = []
    extension_version = 
         install_mode = STATICALLY_LINKED
       installed_from = 
    
    ......
    ......
    
       extension_name = vss
               loaded = false
            installed = false
         install_path = 
          description = Adds indexing support to accelerate Vector Similarity Search
              aliases = []
    extension_version = 
         install_mode = 
       installed_from = 

    客户端 API

    DuckDB 还支持丰富的客户端 API,允许我们通过程序来使用 DuckDB。接下来我们以 Golang 为例进行一个演示。代码如下:

    package main
    
    import (
        "database/sql"
        "errors"
        "fmt"
        "log"
    
        _ "github.com/marcboeker/go-duckdb"
    )
    
    func main() {
        db, err := sql.Open("duckdb", "")
        if err != nil {
            log.Fatal(err)
        }
        defer db.Close()
    
        _, err = db.Exec(`CREATE TABLE people (id INTEGER, name VARCHAR)`)
        if err != nil {
            log.Fatal(err)
        }
        _, err = db.Exec(`INSERT INTO people VALUES (42, 'John')`)
        if err != nil {
            log.Fatal(err)
        }
    
        var (
            id   int
            name string
        )
        row := db.QueryRow(`SELECT id, name FROM people`)
        err = row.Scan(&id, &name)
        if errors.Is(err, sql.ErrNoRows) {
            log.Println("no rows")
        } else if err != nil {
            log.Fatal(err)
        }
    
        fmt.Printf("id: %d, name: %s\n", id, name)
    }

    接下来我们进行编译:

    $ mkdir myduck
    $ mv main.go myduck/
    $ go mod init
    $ go mod tidy
    go: finding module for package github.com/marcboeker/go-duckdb
    go: downloading github.com/marcboeker/go-duckdb v1.7.0
    go: found github.com/marcboeker/go-duckdb in github.com/marcboeker/go-duckdb v1.7.0
    go: downloading github.com/apache/arrow/go/v14 v14.0.2
    go: downloading github.com/google/uuid v1.3.1
    go: downloading golang.org/x/tools v0.14.0
    go: downloading github.com/google/flatbuffers v23.5.26+incompatible
    go: downloading github.com/klauspost/compress v1.16.7
    go: downloading github.com/zeebo/xxh3 v1.0.2
    go: downloading github.com/pierrec/lz4/v4 v4.1.18
    go: downloading golang.org/x/mod v0.13.0
    go: downloading github.com/klauspost/cpuid/v2 v2.2.5
    go: downloading golang.org/x/exp v0.0.0-20231006140011-7918f672742d
    go: downloading gonum.org/v1/gonum v0.12.0
    go: downloading github.com/zeebo/assert v1.3.0
    
    $ go build main.go 
    $ ll -h
    total 81496
    -rw-r--r--  1 liuchuan  staff   678B Jun 17 16:53 go.mod
    -rw-r--r--  1 liuchuan  staff   3.8K Jun 17 16:53 go.sum
    -rwxr-xr-x  1 liuchuan  staff    40M Jun 17 16:54 main   # 编译出来的文件还是挺大的
    -rw-r--r--  1 liuchuan  staff   678B Jun 17 16:52 main.go

    我们运行一下代码:

    $ ./main 
    id: 42, name: John

    关于更详细的客户端 API 信息,可以参阅官方文档:https://duckdb.org/docs/api/overview

    总结

    另外,官方文档就是一个宝库,有着非常丰富的说明及示例,网址为:https://duckdb.org/docs/index。要想深入理解及掌握 DuckDB,需要多读读官方文档。

    综上所述,DuckDB 作为一款集轻量级、高性能于一体的嵌入式分析型数据库,在实时分析、数据仓库、嵌入式应用和大数据预处理等领域都展现出了其独特的优势。其内存优化特性、低延迟性能和灵活的集成方式使得它成为各类应用系统中的理想选择。随着数据分析和处理需求的不断增长,相信 DuckDB 将在未来继续发挥重要作用,为各行各业提供更高效、更可靠的数据支持。

    «
    »
以专业成就每一位客户,让企业IT只为效果和安全买单

以专业成就每一位客户,让企业IT只为效果和安全买单