建立DuckDB数据库连接

建立数据库连接

要使用 DuckDB,必须首先创建一个与数据库的连接。具体的语法因客户端 API 而异,但通常涉及传递一个参数来配置持久性。
import duckdb

# 创建一个内存数据库
con = duckdb.connect(database=':memory:')

# 创建一个持久化数据库
con = duckdb.connect(database='my_database.duckdb')

模式

DuckDB 可以在两种模式下运行:持久模式(Persistent Mode),其中数据保存到磁盘;以及内存模式(In-Memory Mode),其中整个数据集存储在主内存中。
  1. 持久模式(Persistent Mode)
    • 数据保存到磁盘:在这种模式下,数据会被保存到磁盘上的一个数据库文件中。这意味着即使程序关闭或系统重启,数据仍然会保留。
    • 适用场景:适用于需要长期存储和持久化数据的应用程序,例如数据分析、报表生成等。
  2. 内存模式(In-Memory Mode)
    • 数据存储在主内存:在这种模式下,数据完全存储在主内存中。这意味着数据在程序关闭或系统重启后会丢失。
    • 适用场景:适用于对性能要求较高且不需要持久化数据的应用程序,例如临时数据分析、快速查询等。

持久模式(Persistent Mode)

要创建或打开一个持久化数据库,需要在创建连接时设置数据库文件的路径,例如 `my_database.duckdb`。这个路径可以指向一个现有的数据库文件,也可以指向一个尚不存在的文件,DuckDB 会根据需要选择打开或创建数据库。数据库文件可以使用任意扩展名,但常见的扩展名包括 `.db`、`.duckdb` 和 `.ddb`。
  • 如果路径指向一个现有的数据库文件,DuckDB 将打开该文件。
  • 如果路径指向一个尚不存在的文件,DuckDB 将创建一个新的数据库文件。
import duckdb

# 创建一个持久化数据库
con = duckdb.connect(database='my_database.duckdb')

内存模式(In-Memory Mode)

DuckDB 可以在内存模式(In-Memory Mode)下运行。在大多数客户端中,可以**通过传递特殊值 **`**:memory:**`** 作为数据库文件或省略数据库文件参数来激活内存模式**。在内存模式下,所有数据都存储在主内存中,因此当进程结束时,所有数据都会丢失。
import duckdb

# 方式1:创建一个内存数据库
con = duckdb.connect(database=':memory:')


# 方式2:创建一个内存数据库
con = duckdb.connect()

溢出到磁盘

无论是持久模式还是内存模式,DuckDB 都使用“溢出到磁盘”(spilling to disk)机制来处理超出内存容量的工作负载。这种机制确保了即使数据集大小超过了可用内存,DuckDB 仍然能够高效地进行数据处理。

溢出到磁盘(Spilling to Disk):

  • 定义:溢出到磁盘是指当内存中的数据量超过可用内存时,DuckDB 将部分数据写入磁盘,以释放内存空间。
  • 目的:通过将部分数据存储在磁盘上,DuckDB 可以处理比可用内存更大的数据集,从而支持更大规模的数据分析任务。
  • 性能影响:虽然磁盘 I/O 操作通常比内存操作慢,但通过优化和缓存机制,DuckDB 尽量减少对性能的影响。

持久模式下的溢出机制

+ **数据存储**:数据已经存储在磁盘上的数据库文件中。 + **溢出机制**:当内存中的数据量超过可用内存时,DuckDB 可以继续使用磁盘上的空间来存储临时数据,而不需要额外的磁盘 I/O 操作来写入数据。

内存模式下的溢出机制

+ **数据存储**:数据完全存储在主内存中。 + **溢出机制**:当内存中的数据量超过可用内存时,DuckDB 会将部分数据写入磁盘上的临时文件,以释放内存空间。这些临时文件通常存储在系统临时目录中。

并发处理

DuckDB 提供了两种可配置的并发选项:
  1. 单进程读写
    • 一个进程可以同时读取和写入数据库。
  2. 多进程只读
    • 多个进程可以读取数据库,但没有进程可以写入(access_mode = 'READ_ONLY')。

单进程并发

当使用单进程读写选项时,DuckDB 支持多线程写入,结合使用多版本并发控制MVCC[ (Multi-Version Concurrency Control)](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)和乐观并发控制(Optimistic Concurrency Control)来管理并发操作。具体规则如下:
  • 多线程写入
    • 如果没有写冲突,多个并发写操作将成功。
    • 追加操作(INSERT)不会冲突,即使在同一个表上。
    • 多个线程可以同时更新不同的表或同一个表的不同子集。
  • 乐观并发控制
    • 当两个线程尝试同时编辑(更新或删除)同一行时,乐观并发控制会介入。
    • 第二个尝试编辑的线程将失败并抛出冲突错误。

多进程写入

DuckDB 不支持从多个进程自动写入同一个数据库文件,并且这也不是其主要设计目标。如果多个进程必须写入同一个文件,可以考虑以下设计模式:

1、跨进程互斥锁

  • 每个进程获取一个跨进程互斥锁,然后以读写模式打开数据库,查询完成后关闭数据库。
import duckdb
import fcntl

# 获取文件锁
with open('lock_file', 'w') as lock_file:
    fcntl.flock(lock_file, fcntl.LOCK_EX)
    con = duckdb.connect(database='my_database.duckdb')
    con.execute("INSERT INTO users VALUES (1, 'Alice')")
    con.close()
    fcntl.flock(lock_file, fcntl.LOCK_UN)

2、重试连接

  • 如果另一个进程已经连接到数据库,当前进程可以重试连接。
import duckdb
import time

while True:
    try:
        con = duckdb.connect(database='my_database.duckdb')
        con.execute("INSERT INTO users VALUES (1, 'Alice')")
        con.close()
        break
    except duckdb.IOException:
        time.sleep(1)  # 等待一段时间后重试

3、使用外部数据库

  • 使用 MySQL、PostgreSQL 或 SQLite 进行多进程事务管理,然后使用 DuckDB 的扩展(如 MySQL、PostgreSQL 或 SQLite 扩展)定期执行分析查询。
import duckdb
import psycopg2

# 连接到外部数据库
conn = psycopg2.connect(database="mydb", user="user", password="password", host="127.0.0.1", port="5432")
cur = conn.cursor()
cur.execute("INSERT INTO users VALUES (1, 'Alice')")
conn.commit()
cur.close()
conn.close()

# 使用 DuckDB 进行分析查询
con = duckdb.connect(database=':memory:')
con.execute("CREATE TABLE users AS SELECT * FROM read_parquet('path_to_parquet_file')")
result = con.execute("SELECT * FROM users").fetchall()
print(result)

4、写入 Parquet 文件

  • 将数据写入 Parquet 文件,然后使用 DuckDB 读取多个 Parquet 文件。
import duckdb
import pandas as pd

# 写入 Parquet 文件
df = pd.DataFrame({'id': [1], 'name': ['Alice']})
df.to_parquet('users.parquet')

# 使用 DuckDB 读取 Parquet 文件
con = duckdb.connect(database=':memory:')
con.execute("CREATE TABLE users AS SELECT * FROM read_parquet('users.parquet')")
result = con.execute("SELECT * FROM users").fetchall()
print(result)

5、Web 服务器

  • 创建一个 Web 服务器来接收请求并管理对 DuckDB 的读写操作。
from flask import Flask, request, jsonify
import duckdb

app = Flask(__name__)
con = duckdb.connect(database='my_database.duckdb')

@app.route('/insert', methods=['POST'])
def insert():
    data = request.json
    con.execute("INSERT INTO users VALUES (?, ?)", (data['id'], data['name']))
    return jsonify({"status": "success"})

@app.route('/select', methods=['GET'])
def select():
    result = con.execute("SELECT * FROM users").fetchall()
    return jsonify(result)

if __name__ == '__main__':
    app.run(debug=True)

乐观并发控制

DuckDB 使用乐观并发控制(Optimistic Concurrency Control),这种方法通常被认为是读密集型分析数据库系统的最佳选择,因为它可以加快读查询的处理速度。然而,任何在同一时间修改同一行的事务都会导致事务冲突错误:

Transaction conflict: cannot update a table that has been altered!

总结

  • 单进程并发:支持多线程写入,使用 MVCC 和乐观并发控制。
  • 多进程并发:不支持自动多进程写入,需要应用层实现同步机制。
  • 乐观并发控制:适用于读密集型分析查询,但写操作冲突时会抛出错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值