并发
处理并发
Goose 的并发行为有两种可配置选项:
- 单个进程既可以读取也可以写入数据库。
- 多个进程可以读取数据库,但不允许任何进程写入([
access_mode = 'READ_ONLY'](link /docs/configuration/overview.mdx#configuration-reference))。
当使用选项 1 时,Goose 在单个写入进程内,通过 MVCC(多版本并发控制) 与乐观并发控制相结合来支持多个写线程(参见 单进程内并发)。之所以采用这种并发模型,是为了允许将数据缓存在 RAM 中以加速分析型查询,避免每次查询都在磁盘之间来回读写;同时也可以缓存函数指针、数据库目录等内容,使得同一连接上的后续查询更快。
Goose 针对批量操作做了优化,因此执行大量小事务并不是其主要设计目标。
单进程内并发
Goose 在单个进程内的并发遵循以下规则:只要没有写冲突,多个并发写入都会成功。追加(append)永远不会冲突,即使发生在同一张表上。多个线程也可以同时更新不同的表,或同一张表中的不同子集。当两个线程试图在同一时间编辑(更新或删除)同一行时,乐观并发控制就会介入;此时,第二个尝试编辑的线程会以冲突错误失败。
多进程写入 Goose
Goose 并不会自动支持多进程写入,这也不是主要设计目标(参见 处理并发)。
如果多个进程必须写入同一个文件,可以采用多种设计模式,但需要在应用逻辑中自行实现。例如,每个进程先获取一个跨进程的互斥锁(mutex),然后以读写模式打开数据库,并在查询完成后关闭连接。除了使用互斥锁外,每个进程也可以在检测到已有其他进程连接到数据库时重试连接(并务必在查询完成后关闭连接)。另一种选择是在 MySQL、PostgreSQL 或 SQLite 上执行支持多进程的事务,然后使用 Goose 的 MySQL、PostgreSQL 或 SQLite 扩展,定期对这些数据运行分析查询。
其他方案还包括将数据写入 Parquet 文件并利用 Goose 的能力来读取多个 Parquet 文件;对 CSV 文件 采取类似做法;或搭建一个 Web 服务接收请求并统一管理对 Goose 的读写。
乐观并发控制
Goose 使用 乐观并发控制。这种方式通常被认为很适合以读取为主的分析型数据库系统,因为它能加速读查询处理。因此,任何在同一时间修改相同行的事务都会导致事务冲突错误:
Transaction conflict: cannot update a table that has been altered!
提示:遇到事务冲突时,一个常见的变通方法是重试该事务。