AB 表切换的使用场景应该说还是很广泛的,比如历史表归档、批量抽数的时候都可以采用 AB 表切换的思路来实现。
比如有这样一个场景,test_a 是面向终端查询的数据表,数据每天定点全量更新。为了减少数据更新对查询的影响,这里准备用 AB 表切换的思路进行。
在数据更新时,首先写入一张按日期编号的 B 表,例如 test_a_2021_04_29。当 B 表数据写完以后,将 AB 两张表切换。
那么在 ClickHouse 中怎样实现 AB 两张表的快速切换呢? 这里介绍两种主要的方法。
- 第一种是利用 RENAME 语法
首先新建 AB 两张表
代码语言:javascript复制 CREATE TABLE test_a_2021_04_29(
id UInt32,
name String,
value UInt32
)ENGINE = MergeTree()
ORDER BY id
CREATE TABLE test_a(
id UInt32,
name String,
value UInt32
)ENGINE = MergeTree()
ORDER BY id
首先向 test_a_2021_04_29 写入一千万测试数据:
代码语言:javascript复制INSERT INTO TABLE test_a_2021_04_29(id,name,value)
WITH(
SELECT ['A','B','C','D','E','F','G']
)AS dict
SELECT number AS id, dict[number%7 1],id FROM numbers(10000000)
写完之后进行 AB 切换:
代码语言:javascript复制RENAME TABLE test_a_2021_04_29 TO tmp, test_a TO test_a_2021_04_29, tmp TO test_a
Query id: d23eda37-b494-48ea-8a6f-64f319b172f6
Ok.
0 rows in set. Elapsed: 0.013 sec.
可以发现,这里利用了一张临时表 tmp,实现了 AB 表名的切换,是不是很方便呢?
现在验证一下效果,首先查询 test_a,可以看到一千万数据已经在这张表了:
代码语言:javascript复制SELECT COUNT()
FROM test_a
Query id: 51335286-55f6-4e79-8207-d39afffbd18c
┌──count()─┐
│ 10000000 │
└──────────┘
而 test_a_2021_04_29 已经没有数据了:
代码语言:javascript复制SELECT COUNT()
FROM test_a_2021_04_29
Query id: 7ad19597-0ad8-4544-878b-701a8fa6c0f6
┌─count()─┐
│ 0 │
└─────────┘
1 rows in set. Elapsed: 0.003 sec.
RENAME TABLE A TO C, B TO A, C TO B 虽然很方便,但也存在一定的风险,因为利用到了中间临时表,所以整个过程不是原子的,如果出现意外可能会导致不一致的情况发生,于是 ClickHouse 提供了第二种方式。
- 第二种是利用 EXCHANGE TABLES 语法
在新版本中,ClickHouse 提供了一种新的 Atomic 数据库引擎,在这个引擎下创建的数据表,能够支持无锁的 CREATE/DROP/RENAME 操作,并且支持
EXCHANGE TABLES A and B 直接交换两张表。
继续用例,首先新建一个 Atomic 数据库:
代码语言:javascript复制CREATE DATABASE test_atom ENGINE = Atomic
查看它的元数据,就会发现它和 Ordinary 数据库的不同:
代码语言:javascript复制% cat ./data/metadata/test_atom.sql
ATTACH DATABASE _ UUID 'fa22ace8-05a9-4cba-9366-97e625fad12f'
ENGINE = Atomic
元数据中,Atomic 数据库没有名字,取而代之的是一个 UUID。
进一步查看数据库文件,也能发现不同。Ordinary 数据库是一个目录,而 Ordinary 数据库则是一个文件链接,指向了 store 目录下,以 UUID 为名称的目录。
代码语言:javascript复制% ls -l
total 24
drwxr-xr-x 27 nauu staff 864 4 29 21:17 default
-rw-r----- 1 nauu staff 42 8 10 2020 default.sql
drwxr-xr-x 6 nauu staff 192 1 28 21:52 system
-rw-r----- 1 nauu staff 41 1 28 21:51 system.sql
lrwxr-xr-x 1 nauu staff 71 4 29 21:41 test_atom -> /media/psf/ch9-data/data/store/fa2/fa22ace8-05a9-4cba-9366-97e625fad12f
-rw-r-----@ 1 nauu staff 78 4 29 21:41 test_atom.sql
在 Atomic 数据库下新建 AB 表:
代码语言:javascript复制 CREATE TABLE test_atom.test_a_2021_04_29(
id UInt32,
name String,
value UInt32
)ENGINE = MergeTree()
ORDER BY id
CREATE TABLE test_atom.test_a(
id UInt32,
name String,
value UInt32
)ENGINE = MergeTree()
ORDER BY id
在以 test_atom 数据库 UUID 命名的目录下,发现了两张表的元数据:
代码语言:javascript复制% pwd
/data/store/fa2/fa22ace8-05a9-4cba-9366-97e625fad12f
% ls -l
total 16
-rw-r----- 1 nauu staff 183 4 29 21:52 test_a.sql
-rw-r----- 1 nauu staff 183 4 29 21:52 test_a_2021_04_29.sql
同样的,这些表的元数据也没有表名,用唯一的 UUID 取而代之:
代码语言:javascript复制% cat ./test_a_2021_04_29.sql
ATTACH TABLE _ UUID 'ae429533-a558-4e59-8232-950f5dc970be'
(
`id` UInt32,
`name` String,
`value` UInt32
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 8192
% cat ./test_a.sql
ATTACH TABLE _ UUID '2839166f-4c7c-4d57-ad21-8615a79d71f6'
(
`id` UInt32,
`name` String,
`value` UInt32
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 8192
从系统表也能看出两种数据库的表差异,非 Atomic 是没有 UUID 的:
代码语言:javascript复制SELECT database ,name,uuid ,data_paths ,metadata_path FROM `system`.tables t WHERE name = 'test_a'
现在可以直接用 EXCHANGE TABLES 交换两张表,这个操作是原子的。
代码语言:javascript复制EXCHANGE TABLES test_atom.test_a AND test_atom.test_a_2021_04_29;
不过这里有一点值得注意,EXCHANGE TABLES 的原子性是利用了底层系统的 renameat2 命令。它只有至少在 linux kernel 3.15 的操作系统上才能支持。
所以如果你的 linux kernel 版本不够,是不能使用 EXCHANGE TABLES 的,会得到 RENAME EXCHANGE is not supported 的错误。