DuckDB是一个用c++编写的进程内OLAP DBMS,太复杂了。我们从简单的开始,好吗?DuckDB是用于分析的SQLite。它没有依赖关系,非常容易设置,并且经过优化可以对数据执行查询。本文将介绍什么是DuckDB,如何使用它,以及为什么它对你很重要。
DuckDB应用场景
为什么DuckDB ?作为数据分析师或数据科学家,典型的工作流程是从CSV文件或S3桶加载数据,执行预处理步骤,然后运行分析。要实现这些功能,您需要打开一个Jupyter notebook,导入Numpy和Pandas,然后执行查询。有趣的是,如果回顾这些操作,通常包括类似数据库中的操作,如连接、聚合、筛选等。但是,不是使用关系数据库管理系统(RDBMS),而是使用Pandas和Numpy。
问题是,为什么?设置数据库并在其中,加载数据可能是一种痛苦、缓慢和令人沮丧的体验。如果这很容易,那么使用RDBMS就可以获得一切。这就是DuckDB要解决的需求场景,DuckDB是使用数据库分析数据的最简单和最快的方法。
DuckDB介绍
DuckDB优势
- 本地化
首先,DuckDB是进程内单文件数据库,没有外部依赖关系。这是什么意思?与Postgres不同,它不需要设置客户端/服务器,也不需要安装外部依赖项。此外,客户端与服务端之间的数据传输速度比正常情况下要慢。另一方面,DuckDB嵌入到主机应用程序进程中,并管理存储在单个文件中的数据库。没有客户机/服务器,安装起来很简单。如果你需要运行DB来进行本地数据分析,那么它就是你的选择!
- 性能
其次,DuckDB针对分析查询工作负载(OLAP)进行了高度优化。因为它是面向列的数据库(以及其他优化),所以聚合、连接或读取数据的复杂长时间查询变得非常快!可以很容易地说,90%的大数据工作负载可以在一台机器上处理,而不需要DuckDB进行任何设置或计算。Pandas不是这样的。Pandas不能利用CPU内核来并行计算,这使得它们完成起来很慢。它完全在内存中操作,如果数据集太大,就会导致内存不足错误。
- SQL
无论您喜欢与否,SQL比以往任何时候都更加活跃。DuckDB支持相当高级的SQL命令集,例如窗口函数和聚合。它提供事务性保证(ACID属性)并使用Postgres API。忘记丑陋和缓慢的pandas操作。你可以用优雅的SQL替换它们(它在SQL之上提供了一些附加功能,使其更友好,如下所示)。最后,DuckDB有一个Python客户端,当与Arrow和Parquet混合使用时,它会很出色。
总之,DuckDB是开源的、可嵌入的、进程内的、面向列的SQL OLAP RDBMS。非常适合想要轻松快速地执行本地分析工作负载的数据从业者。
DuckDB限制
DuckDB被设计为在单台机器上运行。这意味着你的数据必须适合那台机器;否则,它不起作用。实际上,您可以使用功能强大的计算机来处理99%的工作负载。这应该不是问题,除非你每天处理数tb的数据。通过查看AWS产品,您可以拥有一个拥有1952gb内存和128个vcpu的实例。
另一个限制是DuckDB不是一个多租户数据库。拥有不同团队的不同人员、开发模型和共享数据库上的数据将是非常具有挑战性的。但是,当你将数据库与其他工具(如Airflow、S3、Parquet和dbt)集成时,可以获得一个健壮的数据生态系统,团队可以使用该生态系统高效地工作。
这本身并不是限制,但是DuckDB不是事务性数据库,不应该这样使用。
DuckDB vs SQLite
虽然SQLite是一个通用的数据库引擎,但它主要是为快速在线事务处理(OLTP)而设计的。
SQLite是:
- 跨平台:SQLite数据库存储在单个文件中。
- 紧凑和独立:可作为一个小文件。它不需要安装或配置,也没有外部依赖。
- 可靠:经常用于关键任务应用程序,如飞行软件。
- 快:每秒可以支持数万个事务。
- ACID保证:事务是原子的、一致的、隔离的和持久的
上述特性与DuckDB相同。这就是为什么它声称是“用于分析的SQLite”。那么有什么不同呢?
DuckDB是为进程内OLAP从头开始构建的,采用列式存储、向量化查询处理和针对ETL操作优化的多版本并发控制。另一方面,SQLite使用面向行的存储格式,这意味着SQL查询对单个行而不是成批行进行操作,就像在向量化查询处理中那样,这会给OLAP查询带来较差的性能。
总之,他们都有许多共同的特点。但是,DuckDB针对跨大量行应用聚合计算的查询进行了优化,而SQLite针对单个数据行的快速扫描和查找进行了优化。如果您需要执行分析查询,请使用DuckDB;否则,使用SQLite。
Python操作DuckDB
是时候使用DuckDB了。首先,创建一个duckdb目录,以CSV数据文件为例,这里没有实际业务处理,实际内容不重要。下面数据集包含测试数据:销售数据,列为订单ID、产品、数量等。
Order_ID Product Quantity Price Order_Date
176558 USB-C Charging Cable 2 11.95 19-04-2019
176559 Bose SoundSport Headphones 1 99.99 07-04-2019
176560 Google Phone 1 600 12-04-2019
176560 Wired Headphones 1 11.99 12-04-2019
176561 Wired Headphones 1 11.99 30-04-2019
176562 USB-C Charging Cable 1 11.95 29-04-2019
176563 Bose SoundSport Headphones 1 99.99 02-04-2019
176564 USB-C Charging Cable 1 11.95 12-04-2019
176565 Macbook Pro Laptop 1 1,700.00 24-04-2019
要使用DuckDB,必须安装Python包。为了避免与机器上已经安装的包冲突,最好的做法是创建一个Python虚拟环境。为此,打开终端并键入命令python3 -m venv venv。通过执行source venv/bin/activate
来激活环境,并在VS Code(或任何IDE)中运行一个单元。
在文件中输入# %%
,创建VS Code类型的单元格,可以实现类似Notebook功能:
按shift+enter组合键,可以运行单元格内代码。
安装依赖
准备好Python虚拟环境后,创建一个新文件requirements.txt。该文件包含DuckDB工作所需的Python包以及DuckDB本身。
duckdb==1.1.3
pandas==2.1.1
seaborn==0.13.0
matplotlib==3.8.0
保存该文件并运行命令pip install -r requirements.txt来安装依赖项。
回到笔记本并在第一个单元格中添加以下导入:
创建数据库
默认情况下,不带参数的connect会创建一个全局存储在Python模块中的内存数据库。如果关闭启动连接的程序,就会丢失数据库和数据。如果希望持久化数据,则传递文件名进行连接,以便它创建与数据库对应的文件。
conn = duckdb.connect()
# conn = duckdb.connect("my_db.db")
最后,如果你想查询现有的数据库,而不向它写入数据,传递read_only参数,如下所示:
conn = duckdb.connect("my_db.db", read_only=True)
现在,采用第一个选项,创建内存数据库作为示例。
使用DuckDB查询数据
在新单元格中,增加并执行下面代码:
conn.sql("""SELECT *FROM 'data/*.csv'LIMIT 10
""")
SQL对数据库执行SQL查询。在上面的示例中,DuckDB加载数据集目录中的所有CSV文件并打印前10行。在底层,DuckDB使用CSVLoader自动推断列和类型。如你所见,这很简单。为CSV文件提供路径,就可以像查询SQL表一样查询它们。当然DuckDB支持很多文件类型,如Parquet、Hive分区、JSON等等。
能够查询数据固然很好,但是从查询中检索数据就更好了!DuckDB提供了多种方法,可以使用这些方法来有效地检索数据,例如:
- fetchnumpy()将数据作为Numpy数组的字典
- df()将数据作为Pandas数据框
- arrow()将数据作为arrow表
- pl()将数据作为polar数据框
最终根据需求选择合适的方法。作为最佳实践,不要使用fetch_one或fetch_all,因为它们会为查询返回的每个值创建一个Python对象。如果有很多数据,可能会出现内存错误或性能问题。
让我们通过像这样更改前面的单元格来检索数据作为Pandas Dataframe:
# %%
conn = duckdb.connect("my_db.db")
df = conn.sql("""SELECT *FROM 'data/*.csv'LIMIT 10
""").df()
df
执行结果如下:
从Dataframe创建表
现在,你已经使用DuckDB直接查询了这些文件。另一种方法是将前一个查询的输出注册为一个虚拟表,这样你就可以使用通常的SQL表特性查询数据。如何?简单!
# %%
conn.register("df_view", df)
print(conn.execute("DESCRIBE df_view").df())print(conn.execute("DESCRIBE df_view").df())
这将从数据帧df创建虚拟表df_view。重新运行单元将得到预期的输出:
总结
DuckDB通过易于使用和高效地查询任何数据来节省您的时间。不需要通过经典的RDBMS来加载和查询数据,只需使用DuckDB即可。对于大多数场景,可能不需要100个cpu和100Gb内存的大型设置。此外,DuckDB还提供了与Pandas、Arrow、Polars、Parquet和Numpy等许多知名包的兼容性,这使得在它们之间进行操作变得方便。希望大家喜欢,下次教程再见!