# 数据库规范与存量库实测结论 > 本文档是 `parking-cloud` 的数据库事实记录。**所有结论都来自对老仓库 `zteits-clouds` 的实测, > 不是设计意图的复述**——老项目 `CLAUDE.md` 里关于「通用字段」的描述与存量库的实际结构不符, > 若按其执行会导致新代码查询不到数据。凡有冲突,以本文档为准。 ## 1. 库与归属 | 库 | 内容 | 数据源 | 落地阶段 | |---|---|---|---| | `parking_cloud` | 停车六库合并后的唯一库(表前缀 `sys_` / `park_` / `pay_` / `order_` / `bill_` / `charge_`) | primary | P0 建库,P2 灌表 | | 政务库(`zteits_ptms_chifeng` + `zteits-gov`) | 政务平台自有数据 | `@DS("gov")` | P6(是否并为一库待 P6a 确认) | 老库(**实际六个**:`zt_sys` / `zt_park` / `zt_order` / `zt_order_n` / `zt_bill` / `zt_charge`) **保留只读**作为回滚路径,直到 P8 全面下线。 > ⚠️ 注意**没有 `zt_pay` 这个库**——这是历史命名造成的错觉(env 里 `LEGACY_DB_PAY=zt_order`, > pay 域的活库其实叫 `zt_order`,order 域的活库叫 `zt_order_n`,见 [`legacy-db-actual.md`](./legacy-db-actual.md) §1)。 > 别照着"库名=业务域"去连库,会连错。 ## 2. ⚠️ 通用字段:存量库的实测结论 统计口径:对老仓库 `zteits-clouds` 下 **1311 个存量 mapper XML** 做全量词频统计 (XML 里出现的列名即该列真正被业务使用过,比 DDL 全集更可靠——`doc/sql/DDL.sql` 只是变更日志, 只有 1 张表,不能作为全库结构依据)。 | 字段 | 出现次数 | 结论 | |---|---:|---| | `del_flag` | **0** | **存量库没有逻辑删除列**。新代码不要假设它存在 | | `data_state` | 3619 | **通用状态列**,几乎每张业务表都有;语义按表定义(作废/有效/审核态等) | | `create_date` | 2550 | 审计字段风格 A | | `modfiy_date` | 1695 | 审计字段风格 A(注意拼写是 `modfiy`,不是 `modify`) | | `modify_date` | 511 | 少部分表用了正确拼写,与上一行**并存** | | `create_time` | 1187 | 审计字段风格 B(与风格 A 并存) | | `update_time` | 28 | 风格 B(极少) | | `create_by` / `update_by` | 8 / 8 | 基本不存在 | | `create_emp_id` | 1161 | 风格 A 的「操作人」列 | | `modfiy_empid` | 956 | 风格 A 的「修改人」列(同样拼写为 `modfiy`) | ### 由此确定的三条硬约束 1. **不配置全局逻辑删除**。`mybatis-plus.global-config.db-config` 里**不能**出现 `logic-delete-field: delFlag`;`BaseDO` 也不带 `@TableLogic`。 配置了它会让 MyBatis-Plus 给每条 SQL 拼上 `WHERE del_flag = 0`,存量表直接报 `Unknown column 'del_flag'`。软删除的语义在存量体系里由 `data_state` 表达。 2. **审计字段不统一**,`BaseDO` 只服务于**新表**(新表统一用 `create_time` / `update_time` / `create_by` / `update_by`)。 存量实体是纯 POJO,列名靠各自 XML 的 `resultMap` 映射,**不要**给它们套 `BaseDO`、 也不要用 `@TableField` 补全——否则会引入存量 SQL 里不存在的列。 3. **`data_state` 是业务状态列,不是通用布尔位**。不要做「按位与」之类的通用处理, 每张表的取值含义必须回读该表的 XML / 前端页面确认。 ## 3. 命名与放置 - 表名蛇形 + 模块前缀:`sys_org`、`park_lot`、`pay_order` - 新表(P1 起新建的表,如 `system_users`、`infra_file`)沿用 yudao 风格表名, 与存量表并存;存量表**不改名**(改名会牵动 1311 个 XML 的 namespace/列名) - 存量 mapper XML 落位:`parking-module-*/src/main/resources/mybatis/{mapper,smapper}/**` - 新代码不写 XML,用 MyBatis-Plus ## 4. P2 前置:表清单与冲突排查 ### 4.1 已产出(静态抽取,不依赖连库) [`table-inventory.md`](./table-inventory.md) + [`table-inventory.csv`](./table-inventory.csv), 由 [`tools/extract_table_inventory.py`](./tools/extract_table_inventory.py) 生成 (扫全仓 mapper XML 的 `FROM/JOIN/INTO/UPDATE` 与实体的 `@Table`/`@TableName`)。 口径提醒:它只覆盖**代码真正引用过的表**。`doc/sql/*.sql` 是 ALTER 变更日志、不含建表语句, **不能**当作全库结构来源(同 §2 的说明)。 三条主要结论: | 结论 | 数据 | 对 P2 的意义 | |---|---|---| | 表总量与归属 | 400 张,387 张单模块归属 | 归属清晰,可据此切模块边界 | | **跨模块直连的表只有 13 张** | 见报告 §2 | 这 13 张是合库后**唯一**会从"侥幸隔离"变成"真能写别人表"的地方,必须逐张定唯一归属模块,其余模块改走 `parking-module-api` | | `t_*` 前缀并非"无主" | 41 张里 39 张属 `zteits-bill` | 前缀与模块**不是**一一对应(`td_` 跨 pay/order/business 三家),归属要按表定,不能按前缀猜 | | ⚠️ **400 不是全量** | 还有 5 族**运行期拼后缀**的分表 | 见下面「分表族」 | ### ⚠️ 分表族:静态数不出真实表名 > **📍 本节的处数/族数已被后续实测修正,别照抄**:静态抽取得到"5 处 / 5 族", > 但连库实测后的权威结论是 **2 个族、共 228 张物理表**——`tp_p_eqp_inout_record{yyyyMM}` > (115 张)与 `tp_p_eqp_log{yyyyMM}`(113 张);`pay_order{yyyy}` **不是**分表族。 > 更关键的是判据换了:不是"代码里出现过 `{yyyyMM}` 这种占位",而是 > **代码里有没有 `"前缀"` 这个字面量**(运行期分表必然要拼字符串)—— > 见 [`legacy-db-actual.md`](./legacy-db-actual.md) §6。 > 本节保留的是**当时静态抽取看到的东西**,可作为"静态判据为什么会数错"的样例。 有 5 处 SQL 的表名后缀是**调用方在运行时拼的**(报告 §5):`tp_p_eqp_inout_record{yyyyMM}` (4 个变量名,都是按月)与 `pay_order{yyyy}`(按年)。 这意味着: 1. 真实物理表**远多于 400 张**(每月一张)。将来对生产库 `SHOW TABLES` 时会看到一批 "代码里搜不到"的表——它们正是这里拼出来的,**不要误判为垃圾表** 2. 建表/清理脚本不在本仓库(应该在定时任务或运维脚本里),合库时必须**连着这些分表族**一起搬 3. 迁移改造时**不能**把 `${...}` 写死成某个月,动态拼名逻辑要原样保留 4. 顺带查出一个**存量缺陷**:`ParkingLotUseStatisticDaoImpl:169` 用当前时间拼表名, 查历史区间时实际只命中本月那张表,跨月查询会静默出错。迁移时要么修、要么显式记录 ### 4.2 实测结论(已用只读账号直连) 见 **[`legacy-db-actual.md`](./legacy-db-actual.md)**。核心结论: | 待验证项 | 实测 | |---|---| | 六库同一实例,`INSERT INTO ... SELECT` 直接搬 | ✅ **成立**:六库全在张北 `renniting-prod-net` 一台实例上 | | 但库名与业务域是否对应 | ⚠️ **错位**:`zt_order` 装的是 **pay 域**(79/86 命中),order 域在 `zt_order_n`;且 `zt_order` 里混着一份**冻结于 2018 年的订单归档** | | 重名表"预期集中在字典/配置/日志类" | ⚠️ 19 组,且包含 **`parking_lot`(4 库)**、`sys_org`(4 库)、`park_in_out_record`、`td_c_cust_person` 等**核心业务表**;12 组结构不一致 | | 库小,可直接搬 | ⚠️ **709 张表 / ~19 亿行 / ~747 GB**,其中 16 亿行在 `zt_park` | 另实测确认:字符集 **666/709 非 utf8mb4**(含 30 张 `utf8_bin` + 1 张 `latin1`)、 分表族 **228 张物理表 / 2 个族**(到 2027-03)、**40 张代码引用但表不存在**。 迁移源已确定为张北实例(按"代码引用的表在哪个库存在"的命中矩阵判定, 87~100% 命中)。剩余需定夺的事项见该文档 §9.2。 **合库时的逐表处置**见 [`merge-conflict-plan.md`](./merge-conflict-plan.md): 19 组重名表每张保留哪一份、哪些副本可丢弃、哪些必须先查调用方。 **废弃/备份表的逐张确认**见 [`../docs/废弃表逐张确认.md`](../docs/废弃表逐张确认.md) (由 [`tools/audit_junk_tables.py`](./tools/audit_junk_tables.py) 生成,可随时重跑)。 61 张嫌疑表逐张给了精确行数 + 代码引用 + 建表时间;处置结论见 [`legacy-db-actual.md`](./legacy-db-actual.md) §8.3 / §10.1。 **合库怎么动手**见 [`migration/README.md`](./migration/README.md): 该目录的产物(排除清单 / 并集补行 / 快速校验 `99` / 逐表对账 `98` / 抽样校验 `97`) **全部从判定表 import 现算**,不存在第二份人工清单——重名表副本来自 `tools/check_subset.py` 的 `CHECKS`,拍板不搬的表来自 `tools/audit_junk_tables.py` 的 `DO_NOT_MIGRATE` (**7 张 = 空表 4 + 有数据的备份 3**,后者约 133 GB、由业务方拍板,两层证据强度不同); 改了任一处必须重跑 `tools/gen_migration_sql.py`。它已在本地用 `zt_sys` 彩排通过 (见该文档 §4.1)。 ⚠️ **该目录有两条互斥的路,别拿错尺子**(详见其 §0.1): | | 主张 | 走哪条 | |---|---|---| | **全量迁移**(生产切换) | 源库多少行、目标库就多少行 | ②③ → ④⑤⑥(`98` / `99`) | | **抽样开发库**(`SAMPLE=N`,本机开发) | 每张表都在、列结构一致、每张表都有样例行 | ②③ → **⑦**(`97_verify_sample.sql`) | `98` / `99` / `20` 的判据是"行数相等",跑到抽样镜像上**必然全红**——三份文件头 都加了告示。反过来说,`97` 也**不能**用在生产切换上(它允许每表只有 500 行)。 **当前 `parking_cloud` 里是本机开发用的抽样镜像**(`SAMPLE=500`,679 张表), 所以现在能跑、也该跑的是 `97`。 ## 5. P1 新建表的脚本与执行顺序 本地从零起库: ```bash mysql -h127.0.0.1 -uroot < 00_create_database.sql # 建 parking_cloud(utf8mb4_general_ci) mysql -h127.0.0.1 -uroot parking_cloud < 01_system_schema.sql # 13 张 system_* 表 mysql -h127.0.0.1 -uroot parking_cloud < 02_system_seed.sql # 种子数据 mysql -h127.0.0.1 -uroot parking_cloud < 03_legacy_schema.sql # 679 张存量表结构(生成物) # 可选:让列表页有数据可看。内含真实生产数据抽样,见 sql/DEPLOY.md §4 mysql -h127.0.0.1 -uroot parking_cloud < 04_legacy_sample.sql ``` `03` / `04` 是**生成物**(由 `tools/gen_deploy_sql.sh` 从本地镜像库导出), 用途是「拿一份能装到别的服务器上的部署包」,完整说明与验收证据见 **[`DEPLOY.md`](DEPLOY.md)**。 生成物不要手改——改了下次重跑就没了。 **本地 `parking_cloud` 已于 2026-10-10 用这套脚本重建**(692 张表、0 处 `0900_ai_ci`), 所以现在**本地库与部署包是同一个状态**:本地开发怎么跑,别的服务器上就怎么跑。 重建前本地镜像有 663 / 692 张表的排序规则是 `0900_ai_ci`(跨表 JOIN 会报 `Illegal mix of collations`),已一并修掉。 两个脚本都是幂等的(`CREATE TABLE IF NOT EXISTS` / `INSERT IGNORE` + 显式主键),可重复执行。 `01_system_schema.sql` 建的是 **新表**(`system_*`,沿用 yudao 风格表名),与 P2 才灌入的存量表并存, 存量表不改名。新表统一带 `del_flag`(与存量库相反,见 §2——存量表没有这一列)。 ### 三个容易误判的点 | 点 | 事实 | |---|---| | 组织表叫什么 | **`system_org`,不是 yudao 的 `system_dept`**。语义就是「部门」,用户的 `orgId` 与登录信息里的 `deptId` 是同一个值 | | 新建角色的数据范围 | `system_role.data_scope` 默认 **5(仅本人)**,刻意取最小值,避免新建角色静默拿到全量数据。`1 全部 / 2 自定义 / 3 本组织 / 4 本组织及以下 / 5 仅本人` | | 登录日志的取值 | `log_type`:1 账号登录 / 2 社交登录 / 3 自己登出 / 4 被删除登出;`result`:0 成功 / 10 密码错 / 11 验证码错 / 12 账号禁用。`user_id` 在登录失败时为空 | ## 6. 连接配置 - 本地:`src/main/resources/application-local.yaml`(Druid,`root` 无密码,见该文件的注释) - 环境配置一律走环境变量(`DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USERNAME` / `DB_PASSWORD`), 严禁把生产密码写进配置文件后提交(项目红线第 11 条)