KingbaseES V9生产避坑指南
我最近接触金仓的项目比较多。所以总结了经常会遇见的坑,希望本最佳实践指南能给大家带来帮助。
一、空串与 NULL:兼容模式最经典的坑
Oracle 里 ‘’ 就是 NULL,PG 里 ‘’ 是长度为 0 的字符串。KES 用参数 ora_input_emptystr_isnull 切换这两种语义。
那么坑在哪?这个参数只影响写入时的转换,不影响已经存进去的数据。我们自己跑一遍就明白了:
-- 参数关闭时插入
set ora_input_emptystr_isnull = off;
create table t1 (id integer, name varchar(9));
insert into t1 values (1, ''); -- 落盘是空串
insert into t1 values (2, null); -- 落盘是 null
select * from t1 where name is null; -- 只有 id=2
select * from t1 where name = ''; -- 只有 id=1
-- 现在把参数打开,存量数据不变,但查询语义变了
set ora_input_emptystr_isnull = on;
select * from t1 where name = ''; -- 0 行!'' 在查询里也被转成 null 了
select length(name) from t1 where id = 1; -- 返回 0,证明空串还在那
select * from t1 where name is not null; -- id=1 还能查出来
id=1 那行数据变成了"用 = ''查不到、用 is null 也查不到",但是它又确实存在。现场遇到"数据时有时无",先把这四条查询跑一遍,再看两个环境参数:
show database_mode;
show ora_input_emptystr_isnull;
记住两条规则:
1、判空永远用 is null / is not null。NULL 和任何值做 = 或 <> 比较都不成立,这是 SQL 标准行为,任何模式下都一样。
2、 PG 模式下不要开这个参数,KES 会直接给 WARNING:configuration parameter “ora_input_emptystr_isnull” should not set in current database mode。包括JDBC 报 type “q” does not exist 也是它连带出来的。
这个参数要在建库前设置好,最好写进部署基线。上线后再改,语义就分裂了。
二、字符集:三层编码,一层没对齐就出乱码
KES 的编码链路有三层,排查乱码的固定动作就是把三个值摆出来对:
env | grep LANG # 操作系统层
show server_encoding; -- 数据库层,initdb 后不可改
show client_encoding; -- 连接层,可以会话内 set
\l -- 看每个库的 encoding 和 collate
这里最隐蔽的一条:initdb 时如果没显式指定编码,KES 跟随 LANG 环境变量;Docker 或精简系统里往往没设 LANG,初始化出来就是 SQL_ASCII。这种库里插中文,一个汉字按三个字节算:
-- SQL_ASCII 库,oracle 模式
set nls_length_semantics = 'char';
create table test_char (col char(4));
insert into test_char values ('一二');
-- ERROR: value too large for column ... (actual:6, maximum:4)
-- 两个汉字 6 字节,nls_length_semantics 在 ASCII 编码下救不了你x
所以初始化必须显式带编码:
initdb -E UTF8 --locale=zh_CN.UTF-8 -D /data/kingbase
库已经建好了想换编码?先要知道一件事:create database 本质是复制模板库。系统自带两个模板,template1 是默认模板,允许连接和修改,DBA 装过的扩展、建过的对象都会留在里面,所以复制它时不允许换编码——里面已有的对象是按旧编码组织的,换了就自相矛盾。template0 永远保持 initdb 刚完成的出厂状态,不可连接不可修改,复制它时指定什么编码都行。所以换编码建库必须显式指定 template0:
create database appdb encoding 'UTF8' lc_collate 'zh_CN.UTF-8'
lc_ctype 'zh_CN.UTF-8' template template0;
-- 不加 template template0 会报:
-- new encoding (xxx) is incompatible with encoding of the template database
UTF8 库里汉字占几个"字符",Oracle 模式下由 nls_length_semantics 决定:char 按字符算("一二"占 2),byte 按字节算(占 6)。PG 模式下这个参数无效,一律按字符。迁移前和开发确认列长度按什么语义定义的,别等数据导一半报错再去解决,那样会花很多时间。
跨库场景再补一条:oracle_fdw 查 GBK 的 Oracle 库会出乱码,可以在 wrapper 定义里指定 NLS_LANG,然后重建插件:
CREATE FOREIGN DATA WRAPPER oracle_fdw
HANDLER oracle_fdw_handler
VALIDATOR oracle_fdw_validator
options (nls_lang 'AMERICAN_AMERICA.ZHS16GBK');
三、参数管理:alter system 背后的优先级陷阱
KES 有两个参数文件:kingbase.conf 和 kingbase.auto.conf。alter system set 的改动写进后者。启动时先读前者再读后者,同名条目以 auto.conf 为准。
这里要注意的一点:你在 kingbase.conf 里改了参数,重启后没生效,因为几个月前有人 alter system 过同一个参数,auto.conf 里的旧值把你的配置覆盖了。排查很简单,直接看文件:
grep -v '^#' /data/kingbase/kingbase.auto.conf
grep -i shared_buffers /data/kingbase/kingbase.conf
-- 或者问数据库:参数当前值到底从哪来的
select name, setting, sourcefile, sourceline
from pg_settings
where name = 'shared_buffers';
参数改完怎么生效,查 context 字段,不用背表:
select name, context from pg_settings
where name in ('block_size', 'archive_mode', 'archive_command',
'log_connections', 'bytea_output');
查出来的 context 值对照下表,就知道这个参数怎么改才生效:
| context | 怎么生效 | 例子 |
|---|---|---|
| internal | 改不了,initdb 时定死 | block_size |
| kingbase | 重启数据库 | archive_mode |
| sighup | sys_ctl reload -D /data/kingbase |
archive_command |
| backend | reload 后,新连上来的会话生效 | log_connections |
| user | 会话里直接 set |
bytea_output |
记住一个使用习惯就行:改参数前先查 context,别上来就重启。很多参数 reload 就能生效,生产环境能不重启就不重启。
这里有一个典型案例:有用户把 max_locks_per_transaction 设成了 215927809。这个参数的共享内存开销有公式:
锁表内存 = max_locks_per_transaction × 270 字节 × (max_connections + max_prepared_transactions)
= 215927809 × 270 × 1000 ≈ 54 TB
数据库直接起不来,报"内存段超过可用内存"。同样报错还有四种可能:物理内存真不够、shared_buffers 远大于 swap、内核参数过小、huge_pages = on 但操作系统大页没配对。所以大家改内存类参数之前先计算好。
四、SQL 层的几个"为什么"
序列跳号不是 bug。 每个会话按序列的 cache 值在私有内存缓存一段序列值,会话退出或 sys_ctl stop -m fast 时缓存直接丢弃。如果你不能接受跳号就把 cache 关掉:
alter sequence seq_order cache 1; -- 或建序列时指定 cache 0
代价是每次取值都要访问序列对象,高并发写入场景会变成热点。至于事务回滚后序列值不回收,任何数据库都这样,序列不能当连续流水号用。
now() 在同一个事务里永远返回同一个值。 它返回事务开始时间,这是 PG 的设计。自己验证:
begin;
select now(), clock_timestamp();
select pg_sleep(2);
select now(), clock_timestamp(); -- now() 没变,clock_timestamp() 走了 2 秒
commit;
批量任务里用 now() 打时间戳判断先后顺序的代码,都检查一下,要语句级时间就换 clock_timestamp() 或 statement_timestamp()。
关键字撞名。 先确认到底是不是关键字,以及是哪一级:
select word, catcode, catdesc from pg_get_keywords() where word = 'level';
撞了就用双引号,但会引发另一个问题——双引号标识符大小写敏感,“User” 和 “user” 是两个对象,ORM 生成的 SQL 和手写 SQL 大小写不一致就找不到表。另一条路 exclude_reserved_words 则风险更大:
-- 有人这么配过:
set exclude_reserved_words = 'level';
show transaction isolation level; -- 直接语法错误
-- 排除一个关键字,所有含它的语法全部失效
业务表和系统视图重名。 客户建了张 sys_user,直接查到的是系统视图。根本原因是search_path 里不含 sys_catalog 时,KES 自动把它插到最前面。解决方法是显式配置,把系统目录挪到最后:
# kingbase.conf,改完重启
search_path = '"$USER",PUBLIC,SYS_CATALOG'
copy 导入报文件找不到。 copy 读服务器上的文件,客户端本地文件用 ksql 的 \copy:
\copy t1 from '/home/user/data.csv' with (format csv);
导对象 DDL。 KES 带了类似 Oracle 的 dbms_metadata,梳理存量对象很好用:
create extension dbms_metadata;
select dbms_metadata.get_ddl('table', 't1');
select dbms_metadata.get_index_ddl('ind_t1', 'public');
忘了数据库密码。 密码不可逆但可以重置,全程不用重启(变更操作,trust 窗口期内本机免密进库,动作要快):
# 1. 编辑 data/sys_hba.conf,把 local 认证临时改成 trust
# 2. reload 生效
sys_ctl reload -D /data/kingbase
# 3. 连进去改密码
ksql -U system -d test -c "alter role system password 'NewPass#2026';"
# 4. 把 sys_hba.conf 改回原认证方式,再 reload
sys_ctl reload -D /data/kingbase
数据库 core 了怎么留证据。 生产上 core 文件默认经常生成不了,两个原因:ulimit 限制和 abrt 拦截。提前配好:
ulimit -c # 确认是 unlimited
# CentOS 7 系:修改 /etc/abrt/abrt-action-save-package-data.conf
# OpenGPGCheck = no
# ProcessUnpackaged = yes
systemctl restart abrtd # core 会落在 /var/spool/abrt/ccpp*
# 分析:
gdb --core=/var/spool/abrt/ccpp-xxx/coredump --exec=/opt/Kingbase/ES/V9/Server/bin/kingbase
五、高可用集群:几条不可逆的红线
1、集群的 max_connections 只能调大,不能调小。 调小后备库无法启动,唯一办法是重做备机。上线前把连接数规划好,宁可留富余!
2、 VIP 的子网掩码必须和主备库一致。 比如:VIP 配 /24,主库 /16,备库 /15,主库看到的备库来源 IP 变成了 VIP,备库反复重启加不进集群。数据库日志只会告诉你"备库连不上主库",真正的原因要在网络层看:
ip addr | grep -E 'inet .*(eth|bond|ens)' # 逐台对掩码,VIP 和主备必须同网段同掩码
其他常见现象,对号入座:
备库查询被杀,报 conflict with recovery。主库 vacuum 回放到备库时和备库长查询冲突,超过 max_standby_streaming_delay 就终止查询,备库跑 sys_dump 报错也是它。要在备库做大查询或备份,把参数调大(sighup 级,reload 生效):
alter system set max_standby_streaming_delay = '600s';
sys_ctl reload -D /data/kingbase
读写分离所有查询都压在主库。大概率是连接用户权限不够,JDBC 检测线程查不到在线备机。用业务同一个用户验证:
select client_addr, state, sync_state from sys_stat_replication;
-- 查不到备机记录 = 权限问题,JDBC 会把所有 SQL 发主库
读语句总发往同一台备机。同一个 Statement 底层绑定同一个备连接,PreparedStatement 反复 execute 也一样,想分散就新建 Statement。失败重发机制也顺带记住了:备机报错的 SQL 自动转发主机重跑,主机只有 IO 错误才重发,RETRYTIMES 默认 10。想现场演练备机故障转移:
-- 把 HOSTLOADRATE 设为 0,跑个长查询,然后杀备机进程
select sys_sleep(20);
集群部署阶段的三个检查项,一次做完:
# root ssh 互信要求 PermitRootLogin yes
grep PermitRootLogin /etc/ssh/sshd_config && systemctl restart sshd
# open files,es_client 检测的是非交互 shell 的值,写进 /etc/profile 才算数
echo 'ulimit -HSn 102400' >> /etc/profile && source /etc/profile
ulimit -n
sshd 只在集群启停时用到(远程改 crontab、跑 sys_ctl),平时停掉不影响数据库运行。
六、备份恢复:一致性和性能各有一个关键点
sys_dump 默认导出的不是同一时间点的数据。 它对每张表分别发 copy,各表快照时间不同。表间有关联、又在业务高峰导出,恢复出来可能对不上账。时间点要严格一致,用快照导出:
-- 会话 A:开事务导出快照,导出期间保持事务不提交
begin;
select sys_export_snapshot(); -- 返回快照 ID,例如 00000806-1
# 会话 B:带快照 ID 导出,导的是 A 执行函数那一刻已提交的数据
sys_dump --snapshot=00000806-1 -Fc -f backup.dmp -d appdb -U system -p 54321
# 导完后会话 A 再 commit
大对象表备份卡死。 blob/clob 走默认 copy 文本模式要转码,加上默认压缩,CPU 全耗在这上面。两个开关:
sys_dump -d appdb -Fc --copy-binary -Z 0 -f backup.dmp
# --copy-binary 二进制导出免转码(只能出 dmp 格式)
# -Z 0 关压缩,空间换时间
# 个别分支没有 --copy-binary,用 --insert 替代,大对象场景也比 copy 文本快
还原侧两个容易碰到的问题:
还原 RANGE 分区报"指定的下限 (‘Z’) 大于或等于上限 (‘a’)"。源库和目标库排序规则不一致,‘Z’ 和 ‘a’ 谁大谁小两边答案不同。还原前对一下:
show lc_collate; -- 源、目标各查一次,不一致就用 template0 重建目标库
指定表的语法两边不对称,这个设计确实反直觉:
sys_dump -d test -Fc -f test.dmp -t s1.t1 # 备份:-t 模式.表名
sys_restore -d test -n s1 -t t1 test.dmp # 还原:-n 模式 -t 表名
# 还原侧用错写法不报错,静默匹配不到,什么都没还原——比报错更坑
sys_rman 两个环境问题:报 current time may be rewound 是系统时间被回调过,库里存在"未来"对象,物理备份救不了,只能 sys_dump 逻辑重建,生产的 NTP 别乱动;UOS/Debian 上报 libssl_conf.so: cannot open shared object file,补一个环境变量:
echo 'export OPENSSL_CONF=/etc/ssl/' >> /etc/profile
# 归档命令里也可能要带:archive_command = 'export OPENSSL_CONF=/etc/ssl/ && ...'
七、License:别让授权把生产搞停
国产库特有的一环。日常巡检两条 SQL:
select get_license_validdays(); -- 剩余天数,-2 表示永久授权
select get_license_info(); -- 完整授权信息:版本、MAC 绑定、功能开关
替换 license 不用重启,两种方式(变更操作,先备份旧 license.dat):
# 方式一:sys_ctl
chown kingbase:kingbase /path/new_license.dat
su - kingbase
sys_ctl -D /data/kingbase reload_license -L /path/new_license.dat
# data 路径不确定就 ps -ef | grep kingbase 找 -D 参数
-- 方式二:库内执行,重新登录后生效
select sys_reload_license('/path/new_license.dat');
换了新 license 还提示过期,按顺序排查:产品版本号和授权版本号是否匹配(不同版本 license 不通用);MAC 地址、CPU 核数等绑定项是否一致;到期日怎么算——"浮动基准日期"禁用时是生产日期加有效期,启用时是 license 更换日期加有效期。
两个部署形态:Docker 没有物理 MAC,要申请不绑硬件的专用 license;一个 license 最多绑三个 MAC,主备迁移场景提前把几台机器的 MAC 都报上去。
最后把有效期接进监控,剩 30 天告警:
# crontab,每天 9 点检查
0 9 * * * days=$(ksql -U system -d test -t -A -c 'select get_license_validdays();'); \
[ "$days" != "-2" ] && [ "$days" -lt 30 ] && echo "KES license 剩 $days 天" | mail -s 'license 告警' dba@example.com
license 过期导致数据库拉不起来,属于最没技术含量但最真实的生产事故。
总结
回头看以上内容,真正的高频根因就四个:
- 兼容模式的语义分裂。空串、汉字长度、now() 行为,都是 Oracle 语义和 PG 语义打架。应对方法是建库前把 database_mode、ora_input_emptystr_isnull、nls_length_semantics`定死,写进部署基线。
- 编码链路不对齐。LANG、server_encoding、client_encoding 三层,initdb 显式指定编码能消除一大半问题。
- 参数的隐蔽优先级。auto.conf 覆盖 kingbase.conf,集群 max_connections 不可逆调小,内存参数有乘法公式。改参数前先查 pg_settings 的 context 和 sourcefile。
- 备份的一致性假设。sys_dump 默认不是单一时间点,排序规则影响还原。换句话说,没有做过恢复演练的备份很多时候是无效的。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)