Files
jcgl/docs/11-既有数据库结构分析.md

30 KiB
Raw Permalink Blame History

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 无主键、无索引的连锁后果

  1. 每次按 devicecode / ryid / jcid 查询都是全表扫描。gy_sb_zl 12.9 万行、gy_logs 28.6 万行,心跳与指令链路是高频路径,随运行时间线性劣化。
  2. 重复数据无法阻断。实测 gy_jcxx 3,541 行中 jcid 只有 3,540 个不同值——已存在 1 组重复 jcid,且没有任何机制能阻止再次发生。
  3. 更新语义不可靠。update ... where jcid=? 命中多行时,MyBatis 返回的影响行数与业务预期不符,容易被误判为"成功"。
  4. 分页与排序无稳定基准。无主键导致同一秒内多行顺序不确定,翻页可能重复或漏行。
  5. 归档与对账无法定位单行。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 = '' → 0
  • select 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 行

三个结论:

  1. 主数据严重缺失:系统实际服务的 gyid 至少 37 个,但只有 9 个在 gy_jbxx 有基础信息,31 个公寓缺面积/床位/负责人/时间规则来源。
  2. 租户键可空:gy_rooms 有 1 行 gyid 为 NULL(很可能是代码写入 "" 后被 Oracle 空串语义转成 NULL),这类行在按租户过滤时会被全局漏掉或全局命中。
  3. 行锁表用 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 命名与边界

  1. 保留 schema gygl,用表名前缀表达归属:PLT_(平台主数据/计划)、APT_(公寓执行事实)、DUTY_(值班操作)、ARC_(归档)。
  2. 现有 gy_* 表在迁移期保留原名,新增列/新表用新前缀,避免一次性重命名带来的全量改代码。
  3. 权限表二选一:若公寓端采用 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 拆表与归档

  1. 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 行才有可能拆开。
  2. gy_sb_zl 拆队列/历史:APT_DEV_CMD(只保留未消费,带 status/attempt/expire_at)+ ARC_DEV_CMD(消费即搬走),队列表行数应稳定在"在线设备数 × 个位数"量级,当前 7.1 万条积压即为异常基线。
  3. gy_logs 分区:按月 RANGE 分区,保留 3 个月热数据;"运安叫乘同步汇总"改为只记异常与抽样,预计日志量下降 90%。
  4. gy_jcxx_his / gy_syn_jcxx_his / gy_sb_zl_his 统一进 ARC_ 分区族,保留期与合规要求对齐。

6.5 迁移时的兼容陷阱

  1. varcharbyte 无长度 → 转 varchar(n) 前必须先跑 select max(length(col)) 全量核对,否则截断报错会中断迁移。
  2. ora_input_emptystr_isnull=on → 迁移前后同一份数据 '' 与 NULL 的判断结果不同;迁移脚本与校验 SQL 都要用 coalesce(col,'') 口径。
  3. gen_table_column 里记录的 is_pk 全为 RuoYi 生成器推断值,不能当作主键依据——实测全库主键为 0,该字段不代表真实约束。
  4. 60 个孤儿序列不要随库迁移,目标库从 0 重建,否则序列起始值会带来 ID 跳号与冲突排查成本。
  5. 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. 待现场确认

  1. 生产库是否真的没有索引:本机导入库与 dump 均为 0,但需要在生产 DBA 侧执行 \d+ gygl.gy_jcxx 复核(导出工具理论上会带索引,需排除导出裁剪)。
  2. 37 个 gyid 与 9 个 gy_jbxx 的差集:是"未登记"还是"已停用"?决定主数据补录范围。
  3. 样本库只有 2 个公寓的叫乘数据:确认是导出裁剪、数据迁移,还是这两个公寓曾单独部署。
  4. gy_sb_zl 积压 7.1 万条的成因:设备端未取、网关未转发,还是消费后回写失败?需查 2026-10 之后的设备侧日志。
  5. Oracle 存量公寓:ojdbc8 依赖存在,确认是否存在仍直连 Oracle 的部署环境。
  6. 金仓安全版 MAC 是否启用:sysmac schema 为空表,若启用策略会影响后续新增表的授权模型。

附录 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;分析用临时脚本仅存在于本机临时目录。