30 KiB
11 既有数据库结构分析(KingbaseES)
文档性质:现状实证分析(Analysis Only)。本文所有结论来自对
db/gygl.sql的结构解析与对已导入实例的只读查询(全部为SELECT),不含数据库变更、DDL 执行、配置修改。改造建议仅为设计输入,需评审后另行排期。
0. 文档说明
表 0-1 采集信息
| 项 | 内容 |
|---|---|
| 数据来源 | db/gygl.sql(Navicat 导出,源库 10.33.165.41:54321/gygl,导出时间 2026-11-07 14:34) |
| 校验实例 | 本机 KingbaseES 导入库,jdbc:kingbase8://127.0.0.1:54321/gygl |
| 采集方式 | JDBC 只读查询(information_schema / pg_catalog / pg_stat_user_tables)+ dump 文本解析 |
| 库与 schema | 数据库 gygl,业务 schema gygl,表 owner 均为 system |
| 规模 | 40 张表、524 个字段、schema 总容量 117 MB、495,772 行数据 |
| 关联文档 | 05-数据模型.md(表语义与状态码)、09-迭代指引与风险清单.md(技术债)、10-架构优化方案-三端拆分.md(目标归属) |
样本口径提醒:本机实例的数据是导出快照,业务数据只覆盖 2 个公寓(见 3.9)。行数、分布用于判断结构缺陷,不作为生产容量基线;容量测算需在生产库重跑附录 A 的 SQL。
1. 实例与库级特征
表 1-1 库级参数(实测)
| 参数 | 实测值 | 结构影响 |
|---|---|---|
| 版本 | KingbaseES V009R001C010(server_version 报 12.1) |
兼容 PG 12 协议;JDBC 9.0.0 驱动可直连 |
| 兼容模式 | database_mode = oracle |
出现 sys.date、sys_guid()、sysdate()、dual 表等 Oracle 兼容对象 |
| 空串语义 | ora_input_emptystr_isnull = on |
写入 "" 存成 NULL,读取恒为 NULL;见 3.4 |
| 字符集 | UTF8 |
中文数据无乱码风险 |
| MAC 扩展 | schema sysmac(8 张表,248 kB) |
金仓安全版强制访问控制扩展;上线前需确认是否启用策略 |
| 对象种类 | 视图 0、触发器 0、函数 0、分区表 0 | 业务逻辑全部在 Java 侧,库内无任何约束性逻辑 |
表 1-2 schema 分布
| schema | 表数 | 容量 | 说明 |
|---|---|---|---|
gygl |
40 | 117 MB | 全部业务表 |
sysmac |
8 | 248 kB | 金仓安全版 MAC 元数据(空表) |
sys_hm |
3 | 128 kB | 金仓自带 |
sys |
1 | 40 kB | 金仓自带 |
结论:这是一个"纯存储库"——没有主键、没有索引、没有外键、没有触发器、没有存储过程。数据完整性、唯一性、顺序性、并发控制全部由应用代码承担,这也是下一章几乎所有缺陷的共同根因。
2. 表清单与规模
图 2-1 数据域与表簇
graph LR
A[公寓主数据<br/>gy_jbxx / gy_gyzd / gy_sjgz / gy_jcsj / gy_jrb] --> B[房间<br/>gy_rooms]
B --> C[叫乘执行<br/>gy_jcxx / gy_jcxx_his<br/>gy_bjxx / gy_bjcstj]
D[交路同步<br/>gy_syn_jcxx / _his / _status] --> C
E[人员与卡<br/>gy_ykt_ry / gy_ykt_ry_zp] --> C
F[账务<br/>gy_ykt_remainder / _allowance / _consume / _btbj / _btjs] --> E
G[设备<br/>gy_sb / gy_device / gy_sb_xt / gy_sb_zl / _his] --> C
H[权限<br/>gl_yh / gl_js / gl_qx / gl_cd / gl_yh_js / gl_js_qx]
I[日志<br/>gy_logs / gy_operation_log]
表 2-1 业务表清单(按数据域)
| 表 | 行数 | 大小 | 列数 | 含 gyid | 现职责 |
|---|---|---|---|---|---|
gy_jbxx |
9 | 16 kB | 26 | ✔ | 公寓基础信息(面积、床位、负责人、热水/燃气方式) |
gy_gyzd |
37 | 16 kB | 8 | ✔ | 公寓字典(网关地址、机器号、IP) |
gy_gzzd |
17 | 16 kB | 3 | — | 工种字典 + 客货类型 |
gy_sjgz |
17 | 16 kB | 10 | ✔ | 时间规则(入住/离寓/叫乘基准) |
gy_jcsj |
7 | 16 kB | 3 | — | 叫乘时刻表(cb/kssj/jssj,均为字符) |
gy_jrb |
2 | 16 kB | 6 | — | 节假日 |
gy_rooms |
197 | 64 kB | 22 | ✔ | 房间基线 + 房态 + 分机 + 报警起点(三类语义混装) |
gy_jcxx |
3,541 | 1.5 MB | 70 | ✔ | 叫乘执行主表(上帝表,见 3.5) |
gy_jcxx_his |
32,505 | 12 MB | 70 | ✔ | 叫乘归档表 |
gy_bjxx |
2,490 | 304 kB | 7 | ✔ | 报警记录(开始/结束时间) |
gy_bjcstj |
0 | 8 kB | 12 | — | 补叫统计(未使用) |
gy_jdqkhz |
1 | 16 kB | 9 | ✔ | 房间年度/月度汇总(近似废表) |
gy_rtjb |
0 | 8 kB | 7 | ✔ | 日报(未使用) |
gy_call_duty_match_lock |
38 | 8 kB | 1 | ✔ | 单列行锁表,用 gyid 做悲观锁 |
gy_syn_jcxx |
1,139 | 384 kB | 15 | — | 运安交路快照 |
gy_syn_jcxx_his |
2,291 | 376 kB | 12 | — | 交路归档 |
gy_syn_jcxx_status |
1 | 8 kB | 2 | — | 同步状态/水位(单行开关) |
gy_ykt_ry |
1,349 | 336 kB | 27 | — | 人员与卡主数据 |
gy_ykt_ry_zp |
100 | 27 MB | 2 | — | 照片(BLOB 存库) |
gy_ykt_remainder |
1,253 | 224 kB | 12 | — | 余额(补助/现金/消费/补助总额) |
gy_ykt_allowance |
34,182 | 4.2 MB | 11 | ✔ | 补助发放流水 |
gy_ykt_consume |
35 | 16 kB | 19 | ✔ | 消费流水 |
gy_ykt_btbj |
5 | 16 kB | 8 | ✔ | 补助标准(早/正/节假日) |
gy_ykt_btjs |
5 | 16 kB | 7 | — | 补助计算规则 |
gy_sb |
148 | 48 kB | 8 | ✔ | 设备台账 v1(devicecode/lx) |
gy_device |
92 | 48 kB | 18 | ✔ | 设备台账 v2(device_code/device_type,RuoYi 风格) |
gy_sb_xt |
246 | 64 kB | 9 | ✔ | 设备心跳与版本状态 |
gy_sb_zl |
129,665 | 11 MB | 6 | — | 设备指令队列(与历史同表) |
gy_sb_zl_his |
0 | 8 kB | 6 | — | 指令历史(从未使用) |
gy_logs |
285,914 | 56 MB | 7 | ✔ | 运行日志(无清理策略) |
gy_operation_log |
0 | 8 kB | 6 | — | 操作审计(从未写入) |
gy_file |
1 | 16 kB | 6 | — | 文件登记 |
表 2-2 支撑表清单
| 表 | 行数 | 列数 | 说明 |
|---|---|---|---|
gl_yh / gl_js / gl_qx / gl_cd / gl_yh_js / gl_js_qx |
17 / 4 / 48 / 45 / 16 / 58 | 18 / 6 / 9 / 9 / 2 / 2 | 自研 RBAC(用户/角色/权限/菜单及关联) |
gen_table / gen_table_column |
18 / 279 | 21 / 22 | RuoYi 代码生成器元数据残留 |
容量结构:gy_logs 占 48%、gy_ykt_ry_zp 占 23%、gy_jcxx_his 占 10%、gy_sb_zl 占 9%——四张表吃掉全库 90%。其中两张(日志、指令)是纯追加、无清理的表。
3. 结构缺陷诊断
表 3-1 约束与索引现状(全库实测)
| 指标 | 数量 | 说明 |
|---|---|---|
| 主键 | 0 | 40 张表无一有主键 |
| 唯一约束 | 0 | 唯一性完全依赖应用层判断 |
| 外键 | 0 | 关联完整性靠代码,无 DB 兜底 |
| 索引 | 0 | 含隐式主键索引在内,pg_indexes 计数为 0 |
| 序列 | 64 | 仅 4 个被列默认值引用,其余 60 个为孤儿序列 |
| 表注释 | 3/40 | 仅 gen_table、gen_table_column、gy_device 有 |
| 列注释 | 60/524 | 其中仅 5 条属于业务表(gy_jcxx 3 条、gy_syn_jcxx 1 条、gy_gzzd 1 条) |
| 非空约束 | 5 处 | 仅 gy_ykt_ry.ryid、gy_call_duty_match_lock.gyid、gy_syn_jcxx_status.id/sync_status、gy_device.device_code |
3.1 无主键、无索引的连锁后果
- 每次按
devicecode/ryid/jcid查询都是全表扫描。gy_sb_zl12.9 万行、gy_logs28.6 万行,心跳与指令链路是高频路径,随运行时间线性劣化。 - 重复数据无法阻断。实测
gy_jcxx3,541 行中jcid只有 3,540 个不同值——已存在 1 组重复jcid,且没有任何机制能阻止再次发生。 - 更新语义不可靠。
update ... where jcid=?命中多行时,MyBatis 返回的影响行数与业务预期不符,容易被误判为"成功"。 - 分页与排序无稳定基准。无主键导致同一秒内多行顺序不确定,翻页可能重复或漏行。
- 归档与对账无法定位单行。
gy_jcxx→gy_jcxx_his的搬迁只能按业务键或不带条件批量搬,出错后难以精确回滚。
3.2 类型体系混乱
表 3-2 字段类型分布(524 列)
| 类型 | 列数 | 占比 | 风险 |
|---|---|---|---|
varcharbyte(金仓特有) |
276 | 53% | 全部未指定长度(character_maximum_length 为 NULL),任何超长/脏数据都能写入 |
numeric |
82 | 16% | 未指定精度标度,金额列与计数列同型,无精度约束 |
varchar |
78 | 15% | 有长度,但风格与 varcharbyte 混用 |
timestamp |
68 | 13% | 含 Oracle DATE(sys.date)映射,秒级精度 |
bpchar |
11 | 2% | 定长字符('0'/'1' 标记) |
text / int8 / int4 / bytea |
9 | 2% | LOB(nclob/blob)、自增主键 |
同一字段名在不同表里类型不一致,共 12 组:
| 字段 | 类型变体 | 涉及表数 | 直接后果 |
|---|---|---|---|
id |
numeric / varchar / varcharbyte |
14 | 跨表 join、union 需隐式转换;索引即便补上也难生效 |
deleted |
numeric / varcharbyte |
4 | 逻辑删除写法不统一(=0 与 ='0' 混用) |
gyid |
varchar(64) / varcharbyte |
17 | 长度与比较语义不一致,gyid='TLXC' 在不同表可能命中/不命中 |
kssj / jssj |
timestamp / varcharbyte |
3 / 3 | 同一含义的时间一半能比较、一半是字符串 |
sort |
int4 / numeric |
4 | — |
lx / cb / name / dwmc / room_no / table_id |
各 2 种 | — | — |
3.3 时间字段三种存法并存
| 存法 | 示例 | 问题 |
|---|---|---|
timestamp(含 sys.date) |
kcsj、jcsj、fjsj、sjrysj、scbzsj |
正常,但秒级精度、无时区字段 |
字符串 varcharbyte |
gy_jcxx.yrysj(预期入住时间)、gy_jcxx.ddsj、gy_jcsj.kssj/jssj、gy_jbxx.zrsj/sjsj |
无法做范围比较、无法排序、格式靠约定;共 13 处 |
| 冗余列并存 | jcsj 与 jcsj_s、fjsj 与 fjsj_s、jcbjsj 与 jcbjsj_s |
同一时刻两列,靠代码保证同步,无约束 |
3.4 Oracle 空串语义(高危)
实例参数 ora_input_emptystr_isnull = on,实测验证:
select count(*) from gy_jcxx where bz = ''→ 0select count(*) from gy_jcxx where bz is null→ 3541(全表)
含义:业务代码写 "" 入库后变成 NULL;反过来读出的 NULL 在 Java 侧可能是 null 也可能是 "",取决于取值方式。表现为:前端显示undefined、StringUtils.isBlank 与 != null 判断在不同层得到不同结论、where col <> '' 恒不命中。三端拆分后跨库/跨端传输时这个语义差异会被放大。
3.5 gy_jcxx 上帝表
单表 70 列,同时承载 7 类语义:
| 语义组 | 代表列 |
|---|---|
| 车次与交路 | jch、cc、cdw、kcsj、jlid、cclx、qd、ddcc、depotid |
| 叫乘状态机 | ryzt、jcsj、fjsj、jcbjsj、sjrysj、lybjsj、scbzsj |
| 入住/离寓 | yrysj、sjrysj、ylysj、sjlysj、flag_send、tbsj_gytofcd、tbsj_fcdtogy |
| 补助与餐饮 | cblx、cbje、sfffcb、dayallowance、is_meal3、isoverday |
| 服务评分 | kffwpf、stfwpf、jcfwpf、zm |
| 报警与提醒去重 | is_yaxx、is_jcbj、is_lybj、route_notice_key、call_timeout_notice_key、missed_call_notice_key |
| 设备与录音 | finish_sb、recordfile、hjxysj、fjhold |
后 8 列(business_id、ly_type、ly_event_id、ly_remark、ly_snapshot_url、route_notice_key、call_timeout_notice_key、missed_call_notice_key)明显是后期追加,且 ly_* 属于"离寓事件"子域、*_notice_key 属于"通知去重"子域——表已演化为多子域的物理合并点。gy_jcxx_his 同步复制了这 70 列(但 yappsj 类型从 sys.date 漂移为 timestamp),归档表与主表已出现结构漂移。
3.6 设备台账双轨
表 3-4 两套设备表
| 项 | gy_sb(v1) |
gy_device(v2) |
|---|---|---|
| 行数 | 148 | 92 |
| 命名风格 | 全小写无下划线:devicecode/lx/fjh |
下划线 + RuoYi 审计列:device_code/device_type/create_by |
| 类型编码 | lx:3=134、1=4、5=3、6=3、2=2、空=2 |
device_type:3=80、1=3、5=3、6=3、99=2、2=1 |
| 附加字段 | gxsj、gymc |
online_status、last_heartbeat、firmware_version、del_flag |
| 关系 | — | device_code 与 gy_sb.devicecode 交集 92 行 = 完全子集 |
即:v2 的 92 个 device_code 全部能在 v1 中找到(交集恰为 92),而 v1 仍有 148 行未被 v2 覆盖。两张表同时在线、同时被读,无外键、无同步机制。设备在线率、版本、故障统计存在两个数据源,"当前是否在线"取决于读哪张表。
3.7 指令队列与历史同表,且积压
表 3-5 gy_sb_zl 实测分布
deleted |
行数 | 涉及设备数 | 时间范围 |
|---|---|---|---|
0(未消费/未删除) |
72,088 | 96 | 2025-08-26 ~ 2026-11-07 |
1(已消费) |
57,577 | 172 | 2025-08-26 ~ 2026-11-07 |
其中 deleted='0' 且生成时间超过 1 天的记录有 71,723 条——占未消费量的 99.5%。这说明:"取最新一条 → 置 deleted=1"的消费方式(见 HeartBeatService/设备服务)在多数情况下没有闭环:要么设备从未取走,要么取了没回写成功。同时该表 12.9 万行、零索引,每次取指令都是全表扫描 + 排序。
3.8 日志表无清理
gy_logs 28.6 万行 / 56 MB,eventtime 跨度 2025-05 ~ 2026-11。按 lx 分布:
lx |
行数 |
|---|---|
| 运安叫乘同步汇总 | 238,386 |
| 刷脸离寓事件号缺失 / 请求 / 完成 / 抓拍缺失 | 7,655 / 7,655 / 7,649 / 7,649 |
| 运安叫乘同步跳过 | 4,472 |
| 运安定时抓取完成 | 3,470 |
| 自动叫乘任务未创建 | 2,369 |
| 叫乘时间延后 | 2,258 |
| 注册机心跳异常 | 1,797 |
| 超时未叫乘提醒 | 857 |
| 其余(匹配异常、登录、启动恢复等) | ~1,400 |
按月看,"运安叫乘同步汇总"稳定在 每月约 4.4 万条(2026-05 ~ 09 均在 40,980 ~ 44,640 之间)。这是"每 5 分钟 × 每公寓写一条汇总日志"的直接结果——一条业务无异常的流水日志,占了全库 87% 的日志行数。该表无归档、无分区、无索引,年增约 53 万行。
3.9 主数据与租户键(gyid)不一致
17 张表带 gyid,但各表的取值域互不匹配:
| 观测点 | 实测值 |
|---|---|
gy_jbxx(公寓基础信息) |
9 个:SYXC、SYNDC、SJTXC、SYXXC、DDXC、DSQXC、BYQXC、DLXC、DLBDC |
gy_call_duty_match_lock(行锁表) |
37 个业务 gyid + 1 个伪 gyid __SYNC_JCXX__ |
gy_rooms(房间) |
6 个:TLXC 102、THXC 56、PJXC 35、TLNXC 2、CCXDC 1、空 1 |
gy_jcxx(叫乘) |
2 个:THXC 2,110、TLXC 1,431 |
gy_sb.lx 为空 / gy_rooms.gyid 为空 |
各存在 1~2 行 |
三个结论:
- 主数据严重缺失:系统实际服务的 gyid 至少 37 个,但只有 9 个在
gy_jbxx有基础信息,31 个公寓缺面积/床位/负责人/时间规则来源。 - 租户键可空:
gy_rooms有 1 行gyid为NULL(很可能是代码写入""后被 Oracle 空串语义转成NULL),这类行在按租户过滤时会被全局漏掉或全局命中。 - 行锁表用
gyid当锁键并塞入__SYNC_JCXX__这种伪租户,说明"公寓"与"同步任务"两个概念共用了一张锁表——三端拆分后必须把锁的粒度从"字符串键"改为"业务对象 + 版本"。
样本库只覆盖 THXC / TLXC 两个公寓的叫乘数据,属于导出时的数据裁剪(或该公寓数据被迁移过),不代表生产分布;但
gyid的取值域不一致是结构性问题,与数据量无关。
3.10 RuoYi 残留与孤儿序列
gen_table(18 行)、gen_table_column(279 行)是 RuoYi 代码生成器的元数据表,业务上完全不用,却占据 40 张表中的 2 张。- 64 个序列中只有 4 个被列默认值引用:
gen_table_table_id_seq、gen_table_column_column_id_seq、gy_device_device_id_seq、seq_gy_allowance。其余 60 个指向已不存在的表:sys_user_*、sys_role_*、sys_menu_*、sys_dept_*、sys_job_*、t_oa_*、t_role_*、ykt_account_id_seq、ykt_consume_flow_id_seq、gygl_consumer_terminal_device_id_seq等。
这组序列是"库曾经跑过一版 RuoYi/其他业务系统、后来表被删但序列没删"的直接证据。对三端拆分的意义:新公寓端选 RuoYi 并不是引入新范式,而是回到这条被中断过的路线上,因此必须明确"沿用还是重建"权限表(现状 gl_* 是自研 RBAC,与 RuoYi sys_* 完全不同构)。
3.11 照片以 BLOB 入库
gy_ykt_ry_zp:100 行 / 27 MB / 平均 281.3 KB / 最大 547,778 字节,列类型 blob(bytea)。同时 gy_ykt_ry.photourl 又存了一份路径,代码里还有按 serverOS 硬编码的文件目录(C:\project_doc\resouce\photo\ 与 /home/gygl/resouce/photo/)。同一张照片有"库内 BLOB + 本地磁盘路径"两条真相,且备份策略完全不同(逻辑备份含 BLOB,磁盘目录不在备份范围)。
3.12 审计与统计表空置
gy_operation_log(操作审计)0 行、gy_bjcstj(补叫统计)0 行、gy_rtjb(日报)0 行、gy_sb_zl_his(指令历史)0 行。四张表有结构无数据——设计了但没有接线。其中操作审计的缺失,与"值班员可改叫乘状态但无留痕"的风险直接相关。
4. 数据质量实证
表 4-1 抽样核对结果
| 检查项 | 结果 | 判读 |
|---|---|---|
gy_jcxx 主键候选 jcid |
3,541 行 / 3,540 个不同值 | 存在 1 组重复,无约束可阻断 |
gy_jcxx.kcsj 为空 |
367 / 3,541(10.4%) | 开车时间缺失仍可入库,下游按时间排序/提醒会漏 |
gy_jcxx.ryzt 分布 |
3=2,373、0=582、5=225、4=171、2=134、1=56 | -1 未出现;状态分布偏向"已完成/在寓",缺失败态样本 |
gy_jcxx.jllx 分布 |
1=2,224、0=1,317 | 两值区分,但字段无注释、无字典表 |
gy_ykt_remainder |
1,253 行 / 1,253 个 ryid,无重复 |
当前无重复,但无唯一约束保障 |
gy_rooms 房态分布 |
3=68、1=58、4=32、2=32、5=7 | room_status=6 从未出现(字典有 6 值,实际只用 5 值) |
gy_sb_xt 心跳 |
246 行,updatestate 全为 0 |
更新状态位从未被置位,与 3.7 的指令积压互相印证 |
gy_sb_zl.id / gy_logs.id |
12.9 万 / 28.6 万,全部唯一 | 应用层 UUID 生成正确,纯靠代码保证 |
账务三表口径无法自洽(实测合计):
| 表 | 口径 | 合计 |
|---|---|---|
gy_ykt_allowance |
补助发放流水 | 552,295.00 元 |
gy_ykt_consume |
消费流水 | 265.10 元 |
gy_ykt_remainder |
当前余额(补助/现金/消费/补助总额) | 补助余额 1,228.00 元,现金余额 0.00 元 |
补助累计 55.2 万元的规模,与仅 265 元的消费流水、1,228 元的当前余额之间,缺少任何可对账的中间口径(无期初、无结转、无冲正、无对账批次)。这不能简单判定为"数据错误",但可以确定:当前结构下无法证明账是对的。
5. 表归属矩阵(对接文档 10 第 5 章)
表 5-1 40 张表的目标归属与改造动作
| 表 | 行数 | 目标端 | 改造动作 |
|---|---|---|---|
gy_jbxx |
9 | 平台 | 升为公寓主数据,补全 37 个 gyid,版本化下发 |
gy_gyzd |
37 | 平台 | 主数据(网关/机器号/IP),下发只读 |
gy_gzzd |
17 | 平台 | 主数据(工种/客货类型) |
gy_sjgz |
17 | 平台 | 时间规则收敛为平台唯一口径,下发 |
gy_jcsj |
7 | 平台 | 计划数据 |
gy_jrb |
2 | 平台 | 主数据 |
gy_rooms |
197 | 平台 + 公寓 + 值班 | 拆三份:房间基线(平台)/ 房态(公寓)/ 分机与报警起点(公寓) |
gy_jcxx |
3,541 | 公寓 | 拆表:叫乘任务 / 入住离寓 / 通知去重键,见 6.4 |
gy_jcxx_his |
32,505 | 公寓 + 归档 | 按月分区;修正与主表的类型漂移 |
gy_bjxx |
2,490 | 公寓 | 保留,补主键与 gyid+时间 索引 |
gy_bjcstj |
0 | — | 退役(无数据无代码写入) |
gy_jdqkhz |
1 | 平台 | 转报表视图或退役 |
gy_rtjb |
0 | — | 退役 |
gy_call_duty_match_lock |
38 | 公寓 | 换为行级锁表(含 locked_at/owner)或 DB advisory lock |
gy_syn_jcxx |
1,139 | 平台 | 运安抓取上收平台,公寓只收下行计划 |
gy_syn_jcxx_his |
2,291 | 平台 | 归档分区 |
gy_syn_jcxx_status |
1 | 平台 | 改为 watermark 表(含 last_success_at/cursor) |
gy_ykt_ry |
1,349 | 平台 | 人员/卡主数据,公寓本地缓存 |
gy_ykt_ry_zp |
100 | 平台 | 迁对象存储,表只留 URL 与校验和 |
gy_ykt_remainder |
1,253 | 平台主账 + 公寓缓存 | 双记:本地流水 + 平台主账,日对账 |
gy_ykt_allowance |
34,182 | 平台 | 补助流水归平台,公寓不上报原始流水 |
gy_ykt_consume |
35 | 平台(餐饮)/ 公寓(其他) | 按消费来源拆分归属 |
gy_ykt_btbj |
5 | 平台 | 补助标准主数据 |
gy_ykt_btjs |
5 | 平台 | 补助规则主数据 |
gy_sb |
148 | 公寓 | 合并到 gy_device,命名归一 |
gy_device |
92 | 公寓 | 保留为唯一设备台账,补 (gyid, device_code) 唯一 |
gy_sb_xt |
246 | 公寓 | 保留;心跳改 upsert,加 last_heartbeat 索引 |
gy_sb_zl |
129,665 | 公寓 | 拆队列 / 历史,见 6.4 |
gy_sb_zl_his |
0 | 公寓 | 改分区表承接指令历史 |
gy_logs |
285,914 | 公寓 | 按月分区 + 保留期;"同步汇总"降为采样 |
gy_operation_log |
0 | 公寓 + 值班 | 启用,按端打标 |
gy_file |
1 | 平台 | 对象存储索引 |
gl_*(6 张) |
17/4/48/45/16/58 | 平台 | 统一账号与权限;公寓端沿用 RuoYi sys_* 或适配 gl_*,二者只能留一套 |
gen_table / gen_table_column |
18 / 279 | — | 退役,目标库不携带 |
6. 目标结构建议(设计输入,未执行)
6.1 命名与边界
- 保留 schema
gygl,用表名前缀表达归属:PLT_(平台主数据/计划)、APT_(公寓执行事实)、DUTY_(值班操作)、ARC_(归档)。 - 现有
gy_*表在迁移期保留原名,新增列/新表用新前缀,避免一次性重命名带来的全量改代码。 - 权限表二选一:若公寓端采用 RuoYi,则
gl_*只作为平台端兼容视图存在,不再有第二份可写副本。
6.2 主键与索引基线
表 6-1 必补约束与索引(按优先级)
| 表 | 建议主键 / 唯一 | 建议索引 |
|---|---|---|
gy_jcxx / gy_jcxx_his |
jcid |
(gyid, kcsj)、(gyid, ryzt) |
gy_rooms |
(gyid, room_no) |
(gyid, room_status) |
gy_ykt_ry |
ryid |
(card_id)、(gzzh) |
gy_ykt_remainder |
ryid |
(cardid) |
gy_ykt_allowance |
tid |
(gyid, czsj)、(ryid, czsj) |
gy_ykt_consume |
tid |
(gyid, xfsj)、(orderid) |
gy_device |
(gyid, device_code) |
(ip_addr)、(last_heartbeat) |
gy_sb_xt |
devicecode |
(gyid, heartbeattime) |
gy_sb_zl |
id |
(devicecode, deleted, generatedtime) —— 取指令的主路径 |
gy_logs |
id |
(eventtime) + 按月分区 |
gy_bjxx |
id |
(gyid, kssj) |
gy_syn_jcxx |
id |
(kcsj)、(jch, cc) |
gy_jbxx / gy_gyzd / gy_sjgz |
gyid |
— |
gl_yh / gl_js / gl_qx / gl_cd |
各自首列 | 关联表 (user_id)/(role_id) |
gy_call_duty_match_lock |
gyid |
— |
6.3 类型归一规则
表 6-2 类型目标
| 现类型 | 目标 | 规则 |
|---|---|---|
varcharbyte(无长度) |
varchar(n) |
按实测最大长度 + 30% 余量定 n;标识类统一 varchar(32/64) |
业务时间(kssj/jssj/ddsj/yrysj…) |
timestamp(3) |
禁止再新增字符串时间;迁移时按 YYYY-MM-DD HH24:MI:SS 解析,解析失败行单独落错误表 |
sys.date |
timestamp(0) |
保持秒精度,避免与 timestamp(6) 混比 |
金额(bzje/xfje/knye/xjye/xfze/bzze) |
numeric(12,2) |
统一两位小数,禁止 numeric 裸用 |
布尔标记(deleted/sfjc/finish_*/is_*/stoped) |
char(1) + CHECK IN ('0','1') |
或 boolean;同一字段名全库只允许一种表示 |
id(numeric vs varchar) |
varchar(32) 或 bigint |
每张表内自洽即可,但跨表引用必须同型 |
blob 照片 |
移出库 | 表内只留 photo_url + sha256 + size |
6.4 拆表与归档
gy_jcxx三分:APT_JC_TASK:任务与状态机(jcid/ryid/jch/cc/kcsj/jcsj/fjsj/jcbjsj/ryzt)APT_JC_STAY:入住离寓(yrysj/sjrysj/ylysj/sjlysj/ly_*/flag_send)APT_JC_NOTICE:通知去重键(route_notice_key/call_timeout_notice_key/missed_call_notice_key)→ 或并入通用NOTICE_KEY表- 物理上不再是 70 列宽表,写入口从"多处 update 同一行"变为"三个子域各自写自己的表",
JcglService的 4193 行才有可能拆开。
gy_sb_zl拆队列/历史:APT_DEV_CMD(只保留未消费,带status/attempt/expire_at)+ARC_DEV_CMD(消费即搬走),队列表行数应稳定在"在线设备数 × 个位数"量级,当前 7.1 万条积压即为异常基线。gy_logs分区:按月RANGE分区,保留 3 个月热数据;"运安叫乘同步汇总"改为只记异常与抽样,预计日志量下降 90%。gy_jcxx_his/gy_syn_jcxx_his/gy_sb_zl_his统一进ARC_分区族,保留期与合规要求对齐。
6.5 迁移时的兼容陷阱
varcharbyte无长度 → 转varchar(n)前必须先跑select max(length(col))全量核对,否则截断报错会中断迁移。ora_input_emptystr_isnull=on→ 迁移前后同一份数据''与NULL的判断结果不同;迁移脚本与校验 SQL 都要用coalesce(col,'')口径。gen_table_column里记录的is_pk全为 RuoYi 生成器推断值,不能当作主键依据——实测全库主键为 0,该字段不代表真实约束。- 60 个孤儿序列不要随库迁移,目标库从 0 重建,否则序列起始值会带来 ID 跳号与冲突排查成本。
- Oracle 兼容模式与金仓安全版 MAC 策略需在目标环境确认(
database_mode、sysmac是否启用),二者会改变 SQL 行为与权限模型。
7. 与三端拆分方案的衔接
表 7-1 结构缺陷 → 方案措施
| 本次实测缺陷 | 对应章节 | 方案措施 |
|---|---|---|
| 零主键零索引零外键(3.1) | 阶段零 基线修复 | 先补 (gyid, 业务键) 唯一约束与查询索引,再谈拆分 |
| 同名不同型 12 组(3.2) | 5 数据架构 / 6 接口契约 | 契约层强制类型规范;gyid 统一 varchar(64) |
| 空串即 NULL(3.4) | 6 接口契约 | 上报信封中 null 与 "" 语义显式化,服务端统一 coalesce |
gy_jcxx 70 列(3.5) |
4 领域与模块划分 | 拆三子域,状态机唯一写入口 |
| 双设备台账(3.6) | 7 设备接入 | 设备主数据唯一化,gy_device 为准,gy_sb 合并 |
| 指令积压 7.1 万(3.7) | 7.4 指令下发-ACK-重投 | 队列化 + ACK + 过期作废 + 优先级 |
| 日志月增 4.4 万(3.8) | 8 部署与运行 | 分区 + 保留期 + 异常化 |
gyid 取值域不一致(3.9) |
3 端职责边界 | 公寓主数据上收,租户键非空 + 外键校验 |
| RuoYi 残留与孤儿序列(3.10) | 阶段一 新公寓端脚手架 | 明确权限表归属,目标库不携带 gen_* 与孤儿序列 |
| 照片双真相(3.11) | 5.3 数据归属 | 统一对象存储 + 校验和 |
| 审计表空置(3.12) | 10 风险 | 值班操作留痕纳入验收项 |
| 账务不可对账(4 节) | 5.2 一致性策略 | 主账在平台 + 本地流水 + 日对账 |
8. 待现场确认
- 生产库是否真的没有索引:本机导入库与 dump 均为 0,但需要在生产 DBA 侧执行
\d+ gygl.gy_jcxx复核(导出工具理论上会带索引,需排除导出裁剪)。 - 37 个 gyid 与 9 个
gy_jbxx的差集:是"未登记"还是"已停用"?决定主数据补录范围。 - 样本库只有 2 个公寓的叫乘数据:确认是导出裁剪、数据迁移,还是这两个公寓曾单独部署。
gy_sb_zl积压 7.1 万条的成因:设备端未取、网关未转发,还是消费后回写失败?需查 2026-10 之后的设备侧日志。- Oracle 存量公寓:
ojdbc8依赖存在,确认是否存在仍直连 Oracle 的部署环境。 - 金仓安全版 MAC 是否启用:
sysmacschema 为空表,若启用策略会影响后续新增表的授权模型。
附录 A 采集 SQL(可复现)
-- 库级
select version(), current_setting('server_encoding');
select name, setting from pg_settings where name in ('database_mode','ora_input_emptystr_isnull');
select nspname, count(*) from pg_class c join pg_namespace n on n.oid=c.relnamespace
where c.relkind='r' group by 1;
-- 约束与索引
select count(*) from pg_indexes where schemaname='gygl';
select constraint_type, count(*) from information_schema.table_constraints
where table_schema='gygl' group by 1;
select count(*) from pg_class c join pg_namespace n on n.oid=c.relnamespace
where c.relkind='S' and n.nspname='gygl';
-- 类型与注释
select udt_name, count(*), sum(case when character_maximum_length is null then 1 else 0 end)
from information_schema.columns where table_schema='gygl' group by 1 order by 2 desc;
select column_name, count(distinct udt_name) from information_schema.columns
where table_schema='gygl' group by 1 having count(distinct udt_name)>1;
-- 规模与分布
select relname, n_live_tup, pg_size_pretty(pg_total_relation_size(relid))
from pg_stat_user_tables order by pg_total_relation_size(relid) desc;
select count(*), count(distinct jcid), count(distinct gyid), min(kcsj), max(kcsj) from gygl.gy_jcxx;
select deleted, count(*), min(generatedtime), max(generatedtime) from gygl.gy_sb_zl group by 1;
select count(*) from gygl.gy_sb_zl where deleted='0' and generatedtime < now() - interval '1 day';
select to_char(eventtime,'YYYY-MM'), count(*) from gygl.gy_logs group by 1 order by 1;
本轮对实例的全部操作均为
SELECT,未执行任何 DDL/DML;分析用临时脚本仅存在于本机临时目录。