绿色安装包换成自己的数据库:MySQL、PostgreSQL 与国产库怎么配
绿色安装包默认带一个 SQLite 库,文件就是 data/uvp.db。装完就能用,备份就是拷一个文件,单机小规模部署没有比这更省事的选择。
适用版本:换成 MySQL / PostgreSQL / SQL Server 的做法适用于 1.1.17 及以上;金仓、PolarDB、达梦这三种国产库是 1.1.18 起才支持的,1.1.17 及更早版本不认识这些类型,照第五节配会启动即退。另外,独立包里只带 SQLite 的建库脚本,外部库的全量基线 SQL 要另外取,见第三节。
但下面几条里中了一条,就该换成自己的数据库:
- 平台要跑两个以上实例(SQLite 是单文件,多进程写会锁)
- 单位有 DBA 和备份体系,数据必须落在受管的库里
- 审计或信创要求数据库在国产化清单内
- 库已经很大,单文件备份和恢复开始难做
换库的操作本身没什么门槛。麻烦在于改错之后提示很含糊:后端启动直接退出,日志里可能只有一行 third-party log message omitted(连原因都被吞了),也可能只有 phase=database 或 phase=migration。三种日志对应三种完全不同的错法,第六节有对照表。
下面按「先做后懂」的顺序写:想直接动手,从第三节开始,每一步都写了怎么算成功;想先弄明白为什么,就看第一节。

一、先记住两句话
gormv2 段里有两个名字很像、作用完全不同的东西:
isinitglobalgormmysql是电源开关:启动时去不去连 MySQL;usedbtype是选择开关:业务读写走哪个库。
电源没开,选择开关拧到 MySQL 也没用——后端拿不到连接,直接退出。
配置在包根的 config/config.yml,gormv2 段下,绿色包默认长这样:
gormv2:
usedbtype: "sqlite" # 业务读写用哪个库
mysql:
isinitglobalgormmysql: 0 # 启动时是否去连 MySQL
postgresql:
isinitglobalgormpostgresql: 0
sqlserver:
isinitglobalgormsqlserver: 0
sqlite:
isinitglobalgormsqlite: 1规则只有两条:
- 两个值必须指向同一个库。 三种组合,结果完全不同:
- 开关开了、
usedbtype也指着它:正常启动。 - 开关开了、但地址或账号错:启动退出,日志里有
phase=database。 usedbtype指着 MySQL、开关却是 0:连接根本没建,取到空值就退出,日志只剩third-party log message omitted。最常见,也最容易被误判成「MySQL 连不上」。
- 开关开了、
- 开关最好只开一个。 升级预检要求「有且仅有一个 1,且与
usedbtype一致」;普通start不校验。忘关 SQLite 开关不会导致启动失败,但两个库都会被连接、被迁移,容易留下数据分叉。
出包脚本把这些开关写死了。deploy/standalone/make-config.py 要求「除目标库外全部置 0」,注释里把踩过的坑记了几处,其中就包括「开关没开时后端启动即退、日志里看不出原因」。
二、支持哪些库
usedbtype | 说明 | 全量基线脚本 | 默认端口 |
|---|---|---|---|
sqlite | 绿色包默认,单文件 | baseline.sql(随包) | — |
mysql | 也认 mariadb / tidb | uvp-gb28181.sql | 3306 |
postgresql | 也认 postgres / postgre | postgresql_converted.sql | 5432 |
kingbase | 人大金仓,协议兼容 PG | 复用 postgresql_converted.sql | 54321 |
polardb | 阿里 PolarDB MySQL 版 | 复用 uvp-gb28181.sql | 3306 |
dm | 达梦 DM8,独立方言 | dameng_converted.sql | 5236 |
sqlserver | 能启动,但当前版本不自动做它的备份和在线升级 | sqlserver_converted.sql | 1433 |
表里有四处跟直觉不太一样:
- 这张表按 1.1.18 写。1.1.17 只认
sqlite/mysql/postgresql/sqlserver四个规范名:国产库三种,以及mariadb/tidb/postgres/postgre/mssql等别名都不认识,配上去会启动失败。用旧版本一律写规范名。 - 金仓和 PolarDB 不需要单独的 SQL 脚本。它们的方言分别落到 PG 和 MySQL,复用同一套。真正独立的只有达梦。
- PolarDB 要看版本。配
polardb只适用 MySQL 版;PolarDB for PostgreSQL 得配postgresql,配成polardb在连接阶段就会失败。 - 脚本里带发布基线数据(用户、菜单、权限、字典等),一共 117 张表,但不含
CREATE DATABASE。库要你先建。
三、照着做:MySQL 换库 7 步
以 MySQL 8.0、库名 uvp_gb28181 为例。每一步都写了怎么算成功,没看到成功标志就先别往下走。

⚠️ 全量基线脚本不在独立包里。 包内
resource/baseline/baseline.sql是 SQLite 用的,不能拿它导 MySQL。外部库的基线要另外取与平台版本对应的那一份:仓库里是server/resource/database/uvp-gb28181.sql,没有仓库就找交付方要对应版本的脚本,别从别的版本凑。PG / SQL Server / 达梦分别是postgresql_converted.sql/sqlserver_converted.sql/dameng_converted.sql,同样要另取。
第 0 步:确认版本,拿到脚本
两样东西先备齐,后面才不会白做:
- 平台版本:看包目录名,例如
uvp-gb28181-linux-amd64-1.1.17。 - 与这个版本配套的
uvp-gb28181.sql(117 张表 + 初始数据)。
✅ 算成功:版本号能从目录名读出来,手里的 SQL 也是同一版本。
第 1 步:建库
mysql -uroot -p -e "CREATE DATABASE uvp_gb28181 \
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"✅ 算成功:执行 SHOW DATABASES;,列表里有 uvp_gb28181。
⚠️ 字符集必须是 utf8mb4。库建好之后再改字符集,旧数据要重建,代价很大。
第 2 步:导入全量基线
mysql -uroot -p --default-character-set=utf8mb4 uvp_gb28181 < uvp-gb28181.sql✅ 算成功:下面这句输出 117。
mysql -uroot -p -N -e "SELECT COUNT(*) FROM information_schema.tables \
WHERE table_schema='uvp_gb28181';"⚠️ 导入命令最后那个 uvp_gb28181 是库名,别写成 uvp_gb28181_test 之类的另一个库——数据导错库,平台连的那个库还是空的。
第 3 步:停服
./uvp-gb28181-ctl.sh stop✅ 算成功:命令返回后,页面打不开,包内各组件端口都不再监听。
第 4 步:改 config.yml 这 4 行
打开包根的 config/config.yml,找到 gormv2: 段,按图 1 改:
gormv2:
usedbtype: "mysql" # ① 业务用 MySQL
mysql:
isinitglobalgormmysql: 1 # ② 0 → 1,最容易漏
slowthreshold: 30 # 裸数字,单位是秒;写成 "30s" 会溢出成负数
loglevel: "warn"
timezone: "Local"
write: # ③ 换成你自己的库
host: "192.168.10.220"
database: "uvp_gb28181"
port: 3306
user: "uvp"
pass: "改成一个真实口令"
charset: "utf8mb4"
setmaxidleconns: 10
setmaxopenconns: 128
setconnmaxlifetime: 60
setconnmaxidletime: 30
isopenreaddb: 0 # 保持 0;开了才会去读 read 段
postgresql:
isinitglobalgormpostgresql: 0
sqlserver:
isinitglobalgormsqlserver: 0
sqlite:
isinitglobalgormsqlite: 0 # ④ 1 → 0,保证只有一个开关是 1✅ 算成功:执行下面这句,只输出一行(就是 isinitglobalgormmysql: 1)。
grep -nE 'isinitglobalgorm[a-z]+: *1' config/config.yml⚠️ 缩进别动。usedbtype 和各个方言段都缩在 gormv2: 下面,顶格就跑到段外了,后端读不到。
第 5 步:启动
./uvp-gb28181-ctl.sh start✅ 算成功:ZLM、nginx、Redis、后端依次启动成功,后端开始监听端口。
⚠️ 启动时还会打印一行「数据库已就绪(.../data/uvp.db),跳过初始化」——那说的是本机 SQLite 文件,跟 MySQL 没关系,别当成 MySQL 也初始化好了。
如果启动时报端口被占用(尤其是「本机一条出站连接正把它当源端口用」):这不是配置错,是默认端口段落在 Linux 的临时端口范围里,处理办法见第六节「启动报端口被占用」。要换端口就改 config/config.env 最上面那段 # [uvp-ports]:
./uvp-gb28181-ctl.sh stop
sed -i 's/^UVP_NGINX_HTTP_PORT=51000$/UVP_NGINX_HTTP_PORT=24500/' config/config.env
./uvp-gb28181-ctl.sh start⚠️ 版本差异(很重要):1.1.19 起,全新包解压后、第一次启动前改端口就能生效。1.1.18 及更早的包有个洞——全新包没有端口基线文件,改了会被静默忽略、回退默认段(实测改成 53000 段,启动时预检报的仍是 51008,一句提示都没有)。老版本要么先 start 一次再改,要么在 config.env 里同时写一行 UVP_USER_SET_<键名>=1。
第 6 步:登录验证
打开页面登录,看设备、通道、用户列表是否都在。
✅ 算成功:能登录;随便新建一条数据,回 MySQL 里能查到。
⚠️ 新库用的是基线自带的管理员账号,原来 SQLite 里改过的密码不会跟过来。登不上就按部署手册重置管理员密码,别怀疑库没切成功。
⚠️ 首次登录会被要求改初始密码(1.1.18 起)。没改之前,除了「个人信息」以外的接口全部返回「请先修改初始密码」——照着提示改一次就行,别误判成换库把接口弄坏了。用命令行验证时尤其容易撞上:
# 没改密前,这个列表接口会报「请先修改初始密码」,不是 500 也不是连不上库
curl -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:51000/api/users/list?pageNum=1&pageSize=5"第 7 步:出问题怎么退
停服,把 usedbtype 改回 sqlite、isinitglobalgormsqlite 改回 1、isinitglobalgormmysql 改回 0,再启动。详见第九节。
现场可以打印这份清单逐项勾:
- □ 版本和脚本对得上
- □ 库建好,字符集是 utf8mb4
- □ 表数是 117
- □ 只有 1 个开关是 1,且与
usedbtype一致 - □ 启动日志里没有
startup.failed - □ 能登录,设备 / 通道 / 用户都在
- □ 新建一条数据,新库里能查到
四、PostgreSQL
# 1) 建库(PG 的 database 层级相当于 MySQL 的 schema + database)
createdb -h 127.0.0.1 -U postgres uvp_gb28181
# 2) 导入
psql -h 127.0.0.1 -U postgres -d uvp_gb28181 -f postgresql_converted.sql配置片段(同样是选库 + 开开关 + 填连接参数,三处一起改):
gormv2:
usedbtype: "postgresql" # ① 选 PG
postgresql:
isinitglobalgormpostgresql: 1 # ② 打开 PG 开关
slowthreshold: 30
loglevel: "warn"
timezone: "Asia/Shanghai"
write:
host: "127.0.0.1"
database: "uvp_gb28181"
port: 5432
user: "uvp"
pass: "改成一个真实口令"
setmaxidleconns: 10
setmaxopenconns: 128
setconnmaxlifetime: 60
setconnmaxidletime: 30PG 上没有 MySQL 的 charset 键,别硬加。服务端编码在建库的时候就定了。
五、国产化三兄弟
⚠️ 本节三种库 1.1.18 起才支持。1.1.17 及更早版本不认识
kingbase/polardb/dm,照下面配会启动即退,先升级平台。
金仓 KingbaseES
协议兼容 PostgreSQL,驱动和迁移脚本都能复用。跟 PG 不一样的就三处:端口默认 54321,管理账号默认 system,导入用 ksql(用法跟 psql 一样)。
ksql -h 127.0.0.1 -p 54321 -U system -d uvp_gb28181 -f postgresql_converted.sqlgormv2:
usedbtype: "kingbase" # ① 选金仓
kingbase:
isinitglobalgormkingbase: 1 # ② 打开金仓开关
timezone: "Asia/Shanghai"
write:
host: "127.0.0.1"
database: "uvp_gb28181"
port: 54321
user: "system"
pass: "改成一个真实口令"阿里 PolarDB(MySQL 版)
配置名是 polardb,脚本用 MySQL 那份,端口 3306。库类型判断落在 MySQL 方言上,所以 MySQL 那套注意事项原样适用。
gormv2:
usedbtype: "polardb" # ① 选 PolarDB
polardb:
isinitglobalgormpolardb: 1 # ② 打开 PolarDB 开关
write:
host: "你的 PolarDB 连接地址"
database: "uvp_gb28181"
port: 3306
user: "uvp"
pass: "改成一个真实口令"
charset: "utf8mb4"PolarDB for PostgreSQL 不适用这段,按上一节配 postgresql。
达梦 DM8
达梦是唯一要单独一套脚本的,坑也最多。有几个参数建实例的时候就得定死,事后改代价很大:
# ⛔ CASE_SENSITIVE=N 是硬要求,不是建议
# ⛔ 两个口令都必须给,只给 SYSDBA 会 fail to init db
dminit PATH=/opt/dm/data DB_NAME=UVP PAGE_SIZE=16 CASE_SENSITIVE=N \
SYSDBA_PWD='改成口令' SYSAUDITOR_PWD='改成口令'为什么必须 CASE_SENSITIVE=N:平台里还残留少量不带引号的裸 SQL。达梦默认大小写敏感,GORM 发出去的小写带引号标识符,和裸 SQL 折叠成的大写标识符,会指向两个不同的对象,结果是建表成功、查不到表。设成 N 之后全部归一到大小写不敏感,两边才对得上。
LENGTH_IN_CHAR 不是 dminit 的参数,传了会报 Invalid parameter,事后改 dm.ini 又会让实例起不来。基线生成器把字符串列长度统一乘了 3,GB18030 和 UTF-8 两种汉字编码都够用,现场保持默认就行。
导入用 disql 的 start,或者用 DM 管理工具执行脚本:
disql SYSDBA/'口令'@127.0.0.1:5236
SQL> start /opt/uvp/dameng_converted.sql配置里 schema 必填:
gormv2:
usedbtype: "dm" # ① 选达梦
dm:
isinitglobalgormdm: 1 # ② 打开达梦开关
slowthreshold: 30
loglevel: "warn"
schema: "SYSDBA" # 达梦按模式隔离表,不写就落到用户默认模式
write:
host: "127.0.0.1"
port: 5236
user: "SYSDBA"
pass: "改成一个真实口令"
schema: "SYSDBA"达梦是「用户名即模式名」。建库的模式和登录用户对不上,症状同样是建表成功但查不到表。留空时代码会退回用 user 当 schema,别依赖这个兜底。
另一个跟数据库本身无关的现象:达梦驱动会把批量插入改写成逐行 MERGE,跨机房高延迟链路上单条语句能到 200 ms 量级。我们在一台上实测平均 273 ms,一次 OpenAPI 目录发布跑了 2 分 12 秒。别当故障查,链路延迟叠加驱动的改写方式就是这个数;批量操作按这个量级估时。
六、误判怎么排除
先看日志里出现的是哪一种,这三种别混:
| 日志里看到的 | 真实原因 | 怎么确认 |
|---|---|---|
只有 third-party log message omitted(text_omitted=true),没有任何 phase | usedbtype 指向的库没开 isinitglobalgorm*,连接压根没创建,取到空值就退出了;「数据库连接失败」这句原文被日志桥接吞掉 | 数一遍 isinitglobalgorm* 里有几个 1,把目标库那个置 1、其它置 0 |
phase=database(event=startup.failed) | 开关开了,但连不上:地址 / 端口 / 账号 / 口令错,或库不存在 | 拿 write 段同一套参数,用 mysql / psql 手工连一次 |
phase=migration,文案含「检测到空迁移版本表且缺少 XXX 表」 | 库连上了,但全量基线没导、或导进了另一个库 / 另一个 schema | SELECT COUNT(*) 数表(应为 117);确认连的是哪个 host、哪个 database / schema |
| 达梦报「表已存在但查不到」 | schema 没写,或实例建的时候 CASE_SENSITIVE=Y | SELECT owner, table_name 看表落在哪个模式 |
| 连接串看起来对,却一直认证失败 | 用的是 read 段参数(读写分离没开时这一段完全不生效),或连到了同名的另一个实例 | 确认 isopenreaddb 为 0,只留 write 段;用同一个账号手工连一次 |
页面能开,登录报 no such table | 这是 SQLite 场景:data/uvp.db 是空文件或上次建库中断。外部库空库走不到这一步,上面第三行就退出了 | 停服后删掉 data/uvp.db 再 start,让脚本重建 |
表里「用的是 read 段参数」那一行值得多说一句。./uvp-gb28181-ctl.sh start 每次都会把 config.env 里的几个端口同步进 config.yml,但只改 HTTP、Redis、ZLM 与 media.hookport 这 5 个键,数据库的端口不在同步范围内。所以数据库地址和端口可以放心写在 config.yml,重启不会被改回去。
还有一种:启动报端口被占用(跟数据库无关)
尤其是那行写着「本机一条出站连接正把它当源端口用」——停不掉、重试也没用,很容易被当成「数据库连不上导致起不来」。
原因是端口段:Linux 版默认用 51000-51064,这整段落在 Linux 的临时端口范围(默认 32768-60999)里。内核会把这段端口临时分给出站连接做源端口——后端连 Redis、连 MySQL、DNS 查询都算。被占的那一刻 ss -lnt 里看不到监听,但 ZLM / nginx 就是绑不上:偶发、重试无效、过一会儿自己又好。
实测过一台:53000 段上一条「本地 53004 → :6379」的出站连接把 ZLM 的 HTTP 端口顶掉,连试两次都失败。
两种处置,细节在包的 PORTS.md:
# ① 不动端口,把它们从临时池里预留出来(推荐,重启不丢,对所有现场都适用)
sudo sysctl -w net.ipv4.ip_local_reserved_ports="51000-51064"
# 持久化:追加,别覆盖已有的保留段
echo 'net.ipv4.ip_local_reserved_ports = 51000-51064' \
| sudo tee -a /etc/sysctl.d/99-uvp-ports.conf# ② 整段换到 32768 以下,比如 24000 段
./uvp-gb28181-ctl.sh stop
vi config/config.env # 把 51000–51064 整体换成 24000–24064(16 个键都要改)
./uvp-gb28181-ctl.sh start② 手工改 16 行太容易漏,建议直接照 config.env 顶部那段注释逐行改:主端口是 51000–51014 这些单值,UVP_ZLM_RTP_RANGE 是 51014-51063 这种区间(两处段号都要换)。改完 start,预检会打印新范围,确认是 24000-24064 就对了。
⚠️ 版本差异:1.1.19 起,start 的端口预检会自己把这类占用报出来,并打印本机临时端口范围和处置命令。1.1.18 及更早只查「有没有人监听」,所以会出现「预检通过、ZLM 却绑失败」,当时只能靠 ss -tan | grep <端口> 自己找。
⚠️ 为什么不把默认段直接改掉:改默认值等于所有存量现场升级后端口全变(访问地址、防火墙规则、设备侧媒体配置都要重配),比偶发绑失败破坏大得多。所以默认段保持不变,用上面两种办法处理;新部署可以直接选 32768 以下的段,省掉这一步。
七、换成外部库之后,升级和备份要注意什么
绿色包的在线升级会对外置数据库做备份,但不会把口令写进注册文件。升级前必须在 config/config.env 或 systemd 的 EnvironmentFile 里给出口令:
UVP_UPGRADE_DB_PASSWORD=改成数据库备份账号的口令也可以只给变量名,由环境注入:
UVP_UPGRADE_DB_PASSWORD_ENV=MY_UVP_DB_PASSWORD口令缺失时,升级会在停服之前就阻断。宁可升不了,也别停完服才发现备份做不了。外置库备份默认上限 8 GiB,库更大时显式提高:
UVP_UPGRADE_EXTERNAL_BACKUP_MAX_BYTES=21474836480SQLite 不需要这两个变量。
八、已有的数据怎么办
平台没有内置「库类型互转」的工具。改个配置不会把数据带过去,两条路:
推荐:按新部署处理。 先配好外部库、导基线、启动,然后重新录入设备、通道、用户。设备侧只要把 SIP 参数重新指向平台,工作量取决于设备数量,好处是不留数据一致性的尾巴。
确实要搬已有数据,就用第三方数据库工具(DBeaver 的数据传输、Navicat 的数据同步之类)按表搬。搬之前先做三件事:
- 停平台。运行中搬必然丢数据,写入一直在发生。
- 按父表先、子表后的顺序搬,或者搬完统一处理外键。设备表、通道表、用户表之间都有引用。
- 自增主键、时间列、布尔列这三种类型最容易被工具改语义,搬完必须对账。
对账不用 117 张表全对,四个数对得上就基本能说明问题:
SELECT COUNT(*) FROM sys_users; -- 用户数
SELECT COUNT(*) FROM gb_device; -- 设备数
SELECT COUNT(*) FROM gb_channel; -- 通道数
SELECT COUNT(*) FROM sys_casbin_rule; -- 权限规则数四个数都和原库一致,再抽查两台设备点播,才算搬完。
九、回退
data/uvp.db 只要没删,回退就是改三行、重启一次:
usedbtype改回"sqlite"isinitglobalgormsqlite改回1isinitglobalgormmysql改回0
切换期间产生的新数据在外部库里,不会自动回来,回退意味着丢掉这段时间的变更,动手前想清楚。
还有个容易误会的地方:切到外部库以后,ctl.sh start 仍然会在本机建/检查 data/uvp.db,并打印「数据库已就绪,跳过初始化」。那是脚本的固定动作,只看本机这个 SQLite 文件,完全不检查外部库有没有表,也不影响业务用的外部库。别把这行当成「MySQL 也初始化好了」。判断切没切成功,看登录页和 logs/app/ 里的数据库连接日志。
数据库换完,接下来常问的是缓存。绿色包自带的 Redis 也能换成自己的,步骤比换库少,但有个「改了不生效」的坑,见《换成自己的 Redis》。