大家好,这里是公众号 DBA学习之路,分享一些学习国产数据库路上的知识和经验。

@TOC

前言

最近,在学习 KingbaseES 数据库时,遇到两道 KCP 考题,分别为:

【单选题】当 KES 实例正在运行时,控制文件丢失,此时执行 checkpoint 操作,会有怎样的结果?

  • checkpoint 执行正常
  • 实例崩溃
  • checkpoint 执行失败,但是实例继续运行
  • 不能确定

【单选题】使用什么工具可以重建控制文件?

  • sys_resetwal
  • sys_controlbuild
  • sys_resetcontrol
  • sys_rebuild_control

对于上面两道题的详细解析,大家感兴趣可以查看:

这两道题考验了对 KingbaseES 数据库控制文件维护和恢复的知识,经过研究和梳理之后,我写了一篇关于控制文件的维护和恢复文章,详细梳理了 KingbaseES 数据库控制文件的知识,这里分享给大家一起学习。

控制文件介绍

KingbaseES 数据库的控制文件是记录数据库内部信息的重要文件,一但控制文件损坏,数据库将会宕机,且无法启动。

控制文件默认存放在数据文件目录下的 global 目录下:

1
2
3
[kingbase@kes:/home/kingbase]$ cd /data/global/
[kingbase@kes:/data/global]$ ll sys_control
-rw------- 1 kingbase kingbase 8192 10月 8 16:00 sys_control

通过 sys_controldata 命令可以查看控制文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
[kingbase@kes:/home/kingbase]$ export KINGBASE_DATE=/data
[kingbase@kes:/home/kingbase]$ sys_controldata
sys_control 版本: 1201
Catalog 版本: 202211151
数据库系统标识符: 7418197114018412488
数据库簇状态: 在运行中
sys_control 最后修改: 2024年10月08日 星期二 16时00分11秒
最新检查点位置: 0/1CF23F0
最新检查点的 REDO 位置: 0/1CF23C0
最新检查点的重做日志文件: 000000010000000000000001
最新检查点的 TimeLineID: 1
最新检查点的PrevTimeLineID: 1
最新检查点的full_page_writes: 开启
最新检查点的NextXID: 0:1064
最新检查点的 NextOID: 24576
最新检查点的NextMultiXactId: 1
最新检查点的NextMultiOffsetD: 0
最新检查点的oldestXID: 1032
最新检查点的oldestXID所在的数据库:1
最新检查点的oldestActiveXID: 1064
最新检查点的oldestMultiXid: 1
最新检查点的oldestMulti所在的数据库:1
最新检查点的oldestCommitTsXid:0
最新检查点的newestCommitTsXid:0
最新检查点的时间: 2024年10月08日 星期二 16时00分07秒
不带日志的关系: 0/3E8使用虚假的LSN计数器
最小恢复结束位置: 0/0
最小恢复结束位置时间表: 0
开始进行备份的点位置: 0/0
备份的最终位置: 0/0
需要终止备份的记录: 否
wal_level设置: replica
wal_log_hints设置: 关闭
max_connections设置: 100
max_worker_processes设置: 8
max_wal_senders设置: 10
max_prepared_xacts设置: 0
max_locks_per_xact设置: 64
track_commit_timestamp设置: 关闭
最大数据校准: 8
数据库块大小: 8192
大关系的每段块数: 131072
WAL的块大小: 8192
每一个 WAL 段字节数: 16777216
标识符的最大长度: 64
在索引中可允许使用最大的列数: 32
TOAST区块的最大长度: 1988
大对象区块的大小: 2048
日期/时间 类型存储: 64位整数
正在传递Flloat4类型的参数: 由值
正在传递Flloat8类型的参数: 由值
数据页校验和版本: 0
当前身份验证: c4e1fd6b06f9a8aed87af6ecd6fe351980a0c2d5377215695d959f1af2a2b0a5
数据库模式: 1
身份验证方法模式: 0

控制文件中记录内容如下:

  • 建库时生成的静态信息:初始化数据库时产生,固定不变,无需手动维护。
  • kingbase.conf 中的配置信息:conf 中的相关参数被修改,会自动更新控制文件,无需手动维护。
  • wal 以及 checkpoint 的动态信息:当发生检查点、日志切换等操作,则会自动更新控制文件,无需手动维护。

注意:KES 中的控制文件路径不能改变。

控制文件冗余

控制文件无法手动修改,无法单独备份,只能在使用 sys_rman、sys_basebackup 备份数据库时一起备份控制文件。

启动、关闭和恢复数据库时需要读取控制文件中的相关信息。

通过参数 control_file_copy 可以实现控制文件冗余(多路复用):

1
2
3
4
5
6
-- 参数 control_file_copy 默认不启用
test=# show control_file_copy;
control_file_copy
-------------------

(1 行记录)

启用 control_file_copy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
## 使用 root 用户创建目录 /cf_copy 并设置目录权限
[root@kes ~]# mkdir -p /cf_copy
[root@kes ~]# chown kingbase:kingbase /cf_copy
[root@kes ~]# chmod 700 /cf_copy
[root@kes ~]# su - kingbase

## 查看配置参数示例
[kingbase@kes:/data]$ cat kingbase.conf | grep control_file_copy
# control_file_copy = '' #example: control_file_copy = 'filepath/filename'
#control_file_copy = 'filepath1/filename1;filepath2/filename2'

## 配置 kingbase.conf 文件,这里复制 2 份
[kingbase@kes:/data]$ cat<<-EOF>>/data/kingbase.conf
control_file_copy='/cf_copy/sys_control_1;/cf_copy/sys_control_2'
EOF

## 查看配置后的参数值
[kingbase@kes:/data]$ grep -v "^\s*\(#\|$\)" /data/kingbase.conf | grep control_file_copy
control_file_copy='/cf_copy/sys_control_1;/cf_copy/sys_control_2'

## 重启数据库
[kingbase@kes:/data]$ sys_ctl restart
等待服务器进程关闭 .... 完成
服务器进程已经关闭
等待服务器进程启动 ....2024-10-08 17:10:40.808 CST [9023] 日志: sepapower extension initialized
2024-10-08 17:10:40.812 CST [9023] 日志: 正在启动 KingbaseES V008R006C007B0024 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
2024-10-08 17:10:40.812 CST [9023] 日志: 正在监听IPv4地址"0.0.0.0",端口 54321
2024-10-08 17:10:40.812 CST [9023] 日志: 正在监听IPv6地址"::",端口 54321
2024-10-08 17:10:40.813 CST [9023] 日志: 在Unix套接字 "/tmp/.s.KINGBASE.54321"上侦听
2024-10-08 17:10:40.914 CST [9023] 日志: 日志输出重定向到日志收集进程
2024-10-08 17:10:40.914 CST [9023] 提示: 后续的日志输出将出现在目录 "sys_log"中.
完成
服务器进程已经启动

验证参数生效:

1
2
3
4
5
test=# show control_file_copy;
control_file_copy
-----------------------------------------------
/cf_copy/sys_control_1;/cf_copy/sys_control_2
(1 行记录)

查看控制文件:

1
2
3
[kingbase@kes:/home/kingbase]$ ll /cf_copy/*
-rw------- 1 kingbase kingbase 8192 10月 8 17:10 /cf_copy/sys_control_1
-rw------- 1 kingbase kingbase 8192 10月 8 17:10 /cf_copy/sys_control_2

可以看到数据库配置已经生效,现在有 3 份控制文件。

控制文件恢复

模拟破坏控制文件

登录数据库,对一张测试表 test 插入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
test=# \c test system 
您现在已经连接到数据库 "test",用户 "system".
test=# \d test
数据表 "public.test"
栏位 | 类型 | 校对规则 | 可空的 | 预设
------+---------+----------+--------+------
id | integer | | |
索引:
"idx_test_id" btree (id)
-- 插入一条数据
test=# insert into test values (1);
INSERT 0 1
-- 人为删除控制文件 /data/global/sys_control
test=# \! rm /data/global/sys_control
-- 插入第二条数据,可以成功插入
test=# insert into test values (2);
INSERT 0 1
-- 可以成功查询,说明删除之后不影响正常的 DML 操作,必须要触发检查点才会报错
test=# select * from test;
id
----
1
2
(2 行记录)
-- 但是手动触发检查点之后,数据库宕机了
test=# checkpoint;
警告: 中断联接, 因为其它服务器进程崩溃
描述: The kingbase has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
提示: 一会儿你将可以重联接数据库并且重复你的命令.
服务器意外地关闭了联接
这种现象通常意味着服务器在处理请求之前
或者正在处理请求的时候意外中止

此时尝试启动数据库,会提示无法找到控制文件,启动失败:

1
2
3
4
5
6
7
8
[kingbase@kes:/home/kingbase]$ sys_ctl start
sys_ctl: 其他服务器进程可能正在运行; 尝试启动服务器进程
等待服务器进程启动 ....kingbase: 无法找到数据库系统
预期在目录 "/data" 找到,
但是无法打开文件 "/data/global/sys_control": 没有那个文件或目录
已停止等待
sys_ctl: 无法启动服务器进程
检查日志输出.

当控制文件被破坏后,手动触发检查点,实例会直接崩溃。

control_file_copy 方式(简单推荐)

这时,如果我们配置了控制文件冗余,也就是 control_file_copy 参数,那就很简单了,直接拷贝 /cf_copy 目录下的控制文件到 /data/global 目录下,并且重命名为 sys_control 即可:

1
[kingbase@kes:/home/kingbase]$ cp /cf_copy/sys_control_1 /data/global/sys_control

再次启动数据库:

1
2
3
4
5
6
7
8
9
10
11
[kingbase@kes:/home/kingbase]$ sys_ctl start
sys_ctl: 其他服务器进程可能正在运行; 尝试启动服务器进程
等待服务器进程启动 ....2024-10-08 17:21:02.689 CST [9321] 日志: sepapower extension initialized
2024-10-08 17:21:02.693 CST [9321] 日志: 正在启动 KingbaseES V008R006C007B0024 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
2024-10-08 17:21:02.694 CST [9321] 日志: 正在监听IPv4地址"0.0.0.0",端口 54321
2024-10-08 17:21:02.694 CST [9321] 日志: 正在监听IPv6地址"::",端口 54321
2024-10-08 17:21:02.695 CST [9321] 日志: 在Unix套接字 "/tmp/.s.KINGBASE.54321"上侦听
2024-10-08 17:21:02.797 CST [9321] 日志: 日志输出重定向到日志收集进程
2024-10-08 17:21:02.797 CST [9321] 提示: 后续的日志输出将出现在目录 "sys_log"中.
完成
服务器进程已经启动

suprise,数据库成功启动,此时查看 test 表数据库:

1
2
3
4
5
6
7
8
test=# \c test system
您现在已经连接到数据库 "test",用户 "system".
test=# select * from test;
id
----
1
2
(2 行记录)

数据也没有丢失。

sys_resetwal 方式

这种方式是基于没有使用控制文件冗余的情况,需要使用 sys_resetwal 来重建控制文件,相对比较复杂一些。

sys_resetwal 会重置一个 KingbaseES 数据库集簇的预写式日志和控制信息。出于安全原因,使用时必须在命令行中指定数据目录,sys_resetwal 不会使用环境变量 $KINGBASE_DATA。

查看 sys_resetwal 的帮助命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[kingbase@kes:/home/kingbase]$ sys_resetwal --help
sys_resetwal 重置一个Kingbase数据库集簇的预写式日志.
用法:
sys_resetwal [选项]... 数据目录
选项:
-c, --commit-timestamp-ids=XID,XID
设置提交时间可以检索到的最老的和最新的事务ID
(0意味着没有变化)
[-D, --kingbase-data=]DATADIR data directory
-e, --epoch=XIDEPOCH 设置下一个事务ID的epoch
-f, --force 强制更新完成
-g, --dbmode 设置数据库模式
-l, --next-wal-file=WALFILE 设置新的WAL最小起始位置
-m, --multixact-ids=MXID,MXID 设置下一个和最旧的多事务ID
-n, --dry-run 不更新,只显示将要执行的操作
-o, --next-oid=OID 设置下一个OID
-O, --multixact-offset=OFFSET 设置下一个多事务偏移量
-V, --version 输出版本信息,然后退出
-x, --next-transaction-id=XID 设置下一个事务ID
--wal-segsize=SIZE WAL段的大小(兆字节)
-?, --help 显示本帮助,然后退出

Report bugs to <kingbase-bugs@kingbase.com.cn>.

首先,我们需要取消 control_file_copy 参数的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 从 kingbase.conf 文件中注释 control_file_copy 参数配置,vi 修改即可
[kingbase@kes:/data]$ cat kingbase.conf | grep control_file_copy
# control_file_copy = '' #example: control_file_copy = 'filepath/filename'
#control_file_copy = 'filepath1/filename1;filepath2/filename2'
#control_file_copy='/cf_copy/sys_control_1;/cf_copy/sys_control_2'

## 重启数据库生效
[kingbase@kes:/data]$ sys_ctl restart
等待服务器进程关闭 .... 完成
服务器进程已经关闭
等待服务器进程启动 ....2024-10-08 17:25:49.495 CST [9475] 日志: sepapower extension initialized
2024-10-08 17:25:49.499 CST [9475] 日志: 正在启动 KingbaseES V008R006C007B0024 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
2024-10-08 17:25:49.499 CST [9475] 日志: 正在监听IPv4地址"0.0.0.0",端口 54321
2024-10-08 17:25:49.499 CST [9475] 日志: 正在监听IPv6地址"::",端口 54321
2024-10-08 17:25:49.500 CST [9475] 日志: 在Unix套接字 "/tmp/.s.KINGBASE.54321"上侦听
2024-10-08 17:25:49.596 CST [9475] 日志: 日志输出重定向到日志收集进程
2024-10-08 17:25:49.596 CST [9475] 提示: 后续的日志输出将出现在目录 "sys_log"中.
完成
服务器进程已经启动

## 删除 /cf_copy 目录下的冗余文件
[kingbase@kes:/data]$ rm -rf /cf_copy/sys_control_*

使用以上同样方式模拟控制文件损坏后,数据库无法成功启动:

1
2
3
4
5
6
7
8
[kingbase@kes:/data]$ sys_ctl start
sys_ctl: 其他服务器进程可能正在运行; 尝试启动服务器进程
等待服务器进程启动 ....kingbase: 无法找到数据库系统
预期在目录 "/data" 找到,
但是无法打开文件 "/data/global/sys_control": 没有那个文件或目录
已停止等待
sys_ctl: 无法启动服务器进程
检查日志输出.

此时没有冗余控制文件,只能使用 sys_resetwal 来重建控制文件,这个过程比较复杂,下面进行详细演示。

一般恢复控制文件只需要以下几个参数即可:

参数 简介
-l 设置新的 WAL 最小起始位置
-x 设置下一个事务 ID (NextXID)
-m 设置下一个多事务 ID (NextMultiXactId) 和最旧的多事务 ID (oldestMultiXid)
-O 设置下一个多事务偏移量 (NextMultiOffset)
-f 强制更新完成
-D 指定数据库主数据目录
-g 设置数据库模式

下面节开始演示 sys_resetwal 如何重建控制文件。

首先需要讲解每一个参数的值是如何获取。

参数 -l

wal 命名格式文件名称为 16 进制的 24 个字符组成,每 8 个字符一组,每组的意义如下:

  • 时间线:英文为timeline,是以1开始的递增数字,如1,2,3…
  • LogId:32bit长的一个数字,是以0开始递增的数字,如0,1,2,3…
  • LogSeg:32bit长的一个数字,是以0开始递增的数字,如0,1,2,3..
时间线 LogId LogSeg
00000001 00000000 00000002

参数 -l 的值是设置新的 WAL 最小起始位置,需要查找 $KINGBASE_DATA/sys_wal 目录下最大的日志文件编号,然后加 1,就可以得到 -l 参数的值:

1
2
3
4
5
6
## 最大文件编号为 000000010000000000000002
[kingbase@kes:/home/kingbase]$ ll /data/sys_wal/*
-rw------- 1 kingbase kingbase 16777216 10月 8 17:28 /data/sys_wal/000000010000000000000001
-rw------- 1 kingbase kingbase 16777216 9月 24 21:13 /data/sys_wal/000000010000000000000002
## 加 1 也就是 000000010000000000000003
000000010000000000000002 + 1 = 000000010000000000000003

这个结果是如何计算的?通过获取的最大文件编号 wal 的十六进制数:000000010000000000000002,进行加 1 的计算过程如下:

  1. 识别最右边的位:最右边的位是 2,它小于 15(F),所以可以直接加 1。
  2. 执行加法:将最右边的位 2 加 1 得到 3。
  3. 更新数值:所有其他位保持不变。

因此,计算过程是:$000000010000000000000002_{16} + 1_{16} = 000000010000000000000003_{16}$,这是因为在十六进制中,每个位的值是 16 的幂,最右边的位是 $16^0$,即 1。当我们将 2 加 1 时,我们得到 3,这在十六进制中是有效的,不需要进位到下一个位。

这里也可以使用 python 计算,比较方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import os
import glob
import re

# 获取 WAL 目录路径
wal_directory = os.path.join(os.getenv("KINGBASE_DATA"), "sys_wal")

# 正则表达式匹配十六进制字符串
hex_pattern = re.compile(r'^[0-9A-Fa-f]+$')

# 查找所有符合十六进制格式的文件名并获取最大编号
max_number = max(
(os.path.basename(f) for f in glob.glob(os.path.join(wal_directory, "*"))
if hex_pattern.match(os.path.basename(f))), # 确保文件名是十六进制
key=lambda x: int(x, 16)
)

# 计算下一个编号
next_hex_number = f"{int(max_number, 16) + 1:024X}"

# 打印结果
print(f"最大日志文件编号: {max_number.zfill(24)}") # 确保格式为 24 位
print(f"最大日志文件编号加 1 后的日志: {next_hex_number}")


## 执行后的输出如下
最大日志文件编号: 000000010000000000000002
最大日志文件编号加 1 后的日志: 000000010000000000000003

这里清晰的介绍了如何计算得出参数 -l 的值。

参数 -x

参数 -x 的值是设置下一个事务 ID,需要查找 $KINGBASE_DATA/sys_xact 目录下的最大编号,然后加 1,再在末尾补位 00000,就可以得到 -x 参数的值:

1
2
3
4
5
6
## 最大编号为 0000
[kingbase@kes:/home/kingbase]$ ll /data/sys_xact/*
-rw------- 1 kingbase kingbase 262144 10月 8 17:28 /data/sys_xact/0000

## 加 1 后,末尾补位 00000
0x000100000

这同样也是一个 16 进制数,通过 python 计算可以得到值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import os
import glob
import re

# 获取 sys_xact 目录路径
xact_directory = os.path.join(os.getenv("KINGBASE_DATA"), "sys_xact")

# 正则表达式匹配十六进制字符串
hex_pattern = re.compile(r'^[0-9A-Fa-f]+$')

# 查找所有符合十六进制格式的文件名并获取最大编号
max_number = max(
(os.path.basename(f) for f in glob.glob(os.path.join(xact_directory, "*"))
if hex_pattern.match(os.path.basename(f))), # 确保文件名是十六进制
key=lambda x: int(x, 16)
)

# 计算下一个编号并在末尾补位 00000
next_id = (int(max_number, 16) + 1) * 0x100000
next_hex_id = f"0x{next_id:09X}"

# 打印结果
print(f"最大事务编号列: {max_number}")
print(f"最大事务编号加 1 末尾补 00000 后的事务 ID: {next_hex_id}")

## 执行后的输出如下
最大事务编号列: 0000
最大事务编号加 1 末尾补 00000 后的事务 ID: 0x000100000

这里需要补位 00000,所以 print 打印出来需要 9 位。

参数 -m

参数 -x 的值有两个,需要查找 $KINGBASE_DATA/sys_multixact/offsets 目录下的最大编号和最小编号:

  • 设置下一个多事务 ID:最大编号 + 1,再在末尾补位 0000
  • 最旧的多事务 ID:最小编号末尾补位 0000

📢 注意:最旧的多事务 ID 不能为 0,如果为 0 也需要进行加 1 处理。

1
2
3
4
5
6
7
8
9
## 最大和最小均为 0000
[kingbase@kes:/home/kingbase]$ ll /data//sys_multixact/offsets
-rw------- 1 kingbase kingbase 8192 10月 8 17:28 0000

## 最大编号 +1,末尾补位 0000
0x00010000

## 最小编号末尾补位 0000,因为是 0,所以 +1
0x00000001

这同样也是 16 进制数,通过 python 计算可以得到值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import os
import glob
import re

# 获取 offsets 目录路径
offsets_directory = os.path.join(os.getenv("KINGBASE_DATA"), "sys_multixact", "offsets")

# 正则表达式匹配十六进制字符串
hex_pattern = re.compile(r'^[0-9A-Fa-f]+$')

# 查找以数字开头的文件,并提取编号
log_files = [os.path.basename(f) for f in glob.glob(os.path.join(offsets_directory, "*"))
if hex_pattern.match(os.path.basename(f))]

# 设置最大和最小编号,处理没有文件的情况
max_number = max(log_files, key=lambda x: int(x, 16))
min_number = min(log_files, key=lambda x: int(x, 16))

# 计算下一个多事务 ID 和最旧的多事务 ID
def calculate_ids(max_hex, min_hex):
max_id = (int(max_hex, 16) + 1) * 0x10000
min_id = (int(min_hex, 16) * 0x10000) + (1 if int(min_hex, 16) == 0 else 0)
return f"0x{max_id:08X}", f"0x{min_id:08X}"

# 计算 ID
max_hex_result, min_hex_result = calculate_ids(max_number, min_number)

# 打印结果
print(f"最大多事务编号: {max_number}")
print(f"最小多事务编号: {min_number}")
print(f"下一个多事务 ID: {max_hex_result}")
print(f"下一个最旧的多事务 ID: {min_hex_result}")

## 执行后的输出如下
最大多事务编号: 0000
最小多事务编号: 0000
下一个多事务 ID: 0x00010000
下一个最旧的多事务 ID: 0x00000001

参数 -O

参数 -O 的值是设置下一个多事务偏移量,需要查找 $KINGBASE_DATA/sys_multixact/members 目录下的最大编号,加 1,然后乘以 0xCC80

1
2
3
4
5
6
## 最大编号为 0000
[kingbase@kes:/home/kingbase]$ ll /data/sys_multixact/members
-rw------- 1 kingbase kingbase 8192 9月 24 21:13 0000

## 加 1,乘以 0xCC80
0xCC80

这同样也是 16 进制数,通过 python 计算可以得到值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import os
import glob
import re

# 获取多事务成员目录路径
members_directory = os.path.join(os.getenv("KINGBASE_DATA"), "sys_multixact", "members")

# 正则表达式匹配十六进制字符串
hex_pattern = re.compile(r'^[0-9A-Fa-f]+$')

# 查找所有符合十六进制格式的文件名并获取最大编号
max_number = max(
(os.path.basename(f) for f in glob.glob(os.path.join(members_directory, "*"))
if hex_pattern.match(os.path.basename(f))), # 确保文件名是十六进制
key=lambda x: int(x, 16)
)

# 计算下一个编号和下一个事务偏移量
next_number = int(max_number, 16) + 1
next_hex_number = f"0x{next_number:08X}"
next_offset = next_number * 0xCC80
next_hex_offset = f"0x{next_offset:X}"

# 打印结果
print(f"最大多事务成员编号: {max_number}")
print(f"最大编号加1的编号: {next_hex_number}")
print(f"下一个事务偏移量: {next_hex_offset}")

## 执行后的输出如下
最大多事务成员编号: 0000
最大编号加1的编号: 0x00000001
下一个事务偏移量: 0xCC80

参数 -g

参数 -g 的值是数据库兼容模式:

  • 1:oracle 风格;
  • 0:pg 风格;

获取完参数值后,也就获得了恢复控制文件的命令:

1
2
3
4
5
6
sys_resetwal -f -l 000000010000000000000003 \
-x 0x000100000 \
-m 0x00010000,0x00000001 \
-O 0xCC80 \
-D /data \
-g 1

但是,在执行以上命令之前,需要先手工创建一个空的控制文件和删除 kingbase.pid 文件:

1
2
[kingbase@kes:/home/kingbase]$ touch /data/global/sys_control
[kingbase@kes:/home/kingbase]$ rm -rf /data/kingbase.pid

执行 sys_resetwal 恢复控制文件:

1
2
3
4
5
6
7
8
[kingbase@kes:/home/kingbase]$ sys_resetwal -f -l 000000010000000000000003 \
-x 0x000100000 \
-m 0x00010000,0x00000001 \
-O 0xCC80 \
-D /data \
-g 1
sys_resetwal: 警告: sys_control存在,但已损坏或版本错误;忽略它
重置预写日志

启动数据库:

1
2
3
4
5
6
7
8
9
10
[kingbase@kes:/home/kingbase]$ sys_ctl start
等待服务器进程启动 ....2024-10-08 22:17:41.248 CST [16714] 日志: sepapower extension initialized
2024-10-08 22:17:41.252 CST [16714] 日志: 正在启动 KingbaseES V008R006C007B0024 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
2024-10-08 22:17:41.253 CST [16714] 日志: 正在监听IPv4地址"0.0.0.0",端口 54321
2024-10-08 22:17:41.253 CST [16714] 日志: 正在监听IPv6地址"::",端口 54321
2024-10-08 22:17:41.254 CST [16714] 日志: 在Unix套接字 "/tmp/.s.KINGBASE.54321"上侦听
2024-10-08 22:17:41.356 CST [16714] 日志: 日志输出重定向到日志收集进程
2024-10-08 22:17:41.356 CST [16714] 提示: 后续的日志输出将出现在目录 "sys_log"中.
完成
服务器进程已经启动

数据库已经正常启动,访问数据是否正常:

1
2
3
4
5
6
7
8
9
10
test=# \c test system
您现在已经连接到数据库 "test",用户 "system".
test=# table test;
id
----
1
2
3
4
(4 行记录)

访问正常,控制文件已恢复。

脚本一键恢复控制文件

基于 sys_resetwal 命令,对于每个参数的值如何脚本话获取,上面我已经列出了,所以通过脚本的方式一键恢复控制文件也就不难了,我写了一个 python 脚本实现,脚本支持传入 2 个参数:

1
2
3
4
5
6
7
8
9
10
11
[kingbase@kes:/home/kingbase]$ python3 recover_controlfile.py --help
usage: recover_controlfile.py [-h] [-cs {1,2}] [-kd KINGBASE_DATA]

KingbaseES 一键恢复控制文件

optional arguments:
-h, --help show this help message and exit
-cs {1,2}, --compatible_style {1,2}
兼容样式: 1-Oracle, 2-PostgreSQL(可选,默认为 1)
-kd KINGBASE_DATA, --kingbase_data KINGBASE_DATA
KINGBASE_DATA 参数(可选)

下面演示一下一键恢复脚本的使用过程,首先模拟控制文件丢失:

1
2
3
4
5
6
7
8
9
test=# \! rm -rf /data/global/sys_control
test=# checkpoint ;

警告: 中断联接, 因为其它服务器进程崩溃
描述: The kingbase has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory.
提示: 一会儿你将可以重联接数据库并且重复你的命令.
服务器意外地关闭了联接
这种现象通常意味着服务器在处理请求之前
或者正在处理请求的时候意外中止

然后执行一键恢复控制文件脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
[kingbase@kes:/home/kingbase]$ python3 recover_controlfile.py

==================================================
WAL 信息:
最大日志文件编号: 00000001000000000000000F
最大日志文件编号加 1 后的日志: 000000010000000000000010
==================================================


==================================================
XACT 事务信息:
最大事务编号列: 0000
最大事务编号加 1 末尾补 00000 后的事务 ID: 0x000100000
==================================================


==================================================
multixact 多事务信息:
最大多事务编号: 0000
最小多事务编号: 0000
下一个多事务 ID: 0x00010000
下一个最旧的多事务 ID: 0x00000001
==================================================


==================================================
多事务成员信息:
最大多事务成员编号: 0000
最大编号加 1 的编号: 0x00000001
下一个事务偏移量: 0xCC80
==================================================


==================================================
生成 sys_resetwal 恢复命令:
sys_resetwal -f -l 000000010000000000000010 \
-x 0x000100000 \
-m 0x00010000,0x00000001 \
-O 0xCC80 \
-D /data \
-g 1
==================================================


==================================================
创建一个空控制文件:
b'-rw-r--r-- 1 kingbase kingbase 0 10\xe6\x9c\x88 9 17:03 /data/global/sys_control'
==================================================


==================================================
正在删除 kingbase.pid 文件:
/data/kingbase.pid
==================================================


==================================================
正在执行 sys_resetwal 恢复控制文件:
重置预写日志
sys_resetwal: 警告: sys_control存在,但已损坏或版本错误;忽略它
==================================================


==================================================
恢复的控制文件所在路径及大小如下:
b'-rw-r--r-- 1 kingbase kingbase 8192 10\xe6\x9c\x88 9 17:03 /data/global/sys_control'
==================================================

正在启动数据库:

等待服务器进程启动 ....2024-10-09 17:03:00.573 CST [46332] 日志: sepapower extension initialized
2024-10-09 17:03:00.577 CST [46332] 日志: 正在启动 KingbaseES V008R006C007B0024 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
2024-10-09 17:03:00.578 CST [46332] 日志: 正在监听IPv4地址"0.0.0.0",端口 54321
2024-10-09 17:03:00.578 CST [46332] 日志: 正在监听IPv6地址"::",端口 54321
2024-10-09 17:03:00.584 CST [46332] 日志: 在Unix套接字 "/tmp/.s.KINGBASE.54321"上侦听
2024-10-09 17:03:00.702 CST [46332] 日志: 日志输出重定向到日志收集进程
2024-10-09 17:03:00.702 CST [46332] 提示: 后续的日志输出将出现在目录 "sys_log"中.
完成
服务器进程已经启动

恭喜!控制文件还原成功!!!

一键恢复完成,数据库成功开启:

1
2
3
4
5
6
7
8
9
10
test=# \c test system 
您现在已经连接到数据库 "test",用户 "system".
test=# table test;
id
----
1
2
3
4
(4 行记录)

数据库可以正常访问。

写在最后

控制文件不仅记录着数据库的重要信息,还直接影响着数据库的运行状态,所以我们不仅要做好数据库的日常备份,还应该使用 control_file_copy 参数将控制文件的镜像存放在不同的存储设备上,避免发生单点故障。

最后,希望大家都永不宕机!!!