README.md
数据库规范与存量库实测结论
本文档是
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,见 <code>legacy-db-actual.md</code> §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) |
由此确定的三条硬约束
- 不配置全局逻辑删除。
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表达。 - 审计字段不统一,
BaseDO只服务于新表(新表统一用create_time/update_time/create_by/update_by)。 存量实体是纯 POJO,列名靠各自 XML 的resultMap映射,不要给它们套BaseDO、 也不要用@TableField补全——否则会引入存量 SQL 里不存在的列。 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 已产出(静态抽取,不依赖连库)
<code>table-inventory.md</code> + <code>table-inventory.csv</code>,
由 <code>tools/extract_table_inventory.py</code> 生成
(扫全仓 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}这种占位",而是 代码里有没有"前缀"这个字面量(运行期分表必然要拼字符串)—— 见 <code>legacy-db-actual.md</code> §6。 本节保留的是当时静态抽取看到的东西,可作为"静态判据为什么会数错"的样例。
有 5 处 SQL 的表名后缀是调用方在运行时拼的(报告 §5):tp_p_eqp_inout_record{yyyyMM}
(4 个变量名,都是按月)与 pay_order{yyyy}(按年)。
这意味着:
- 真实物理表远多于 400 张(每月一张)。将来对生产库
SHOW TABLES时会看到一批 "代码里搜不到"的表——它们正是这里拼出来的,不要误判为垃圾表 - 建表/清理脚本不在本仓库(应该在定时任务或运维脚本里),合库时必须连着这些分表族一起搬
- 迁移改造时不能把
${...}写死成某个月,动态拼名逻辑要原样保留 - 顺带查出一个存量缺陷:
ParkingLotUseStatisticDaoImpl:169用当前时间拼表名, 查历史区间时实际只命中本月那张表,跨月查询会静默出错。迁移时要么修、要么显式记录
4.2 实测结论(已用只读账号直连)
见 <code>legacy-db-actual.md</code>。核心结论:
| 待验证项 | 实测 |
|---|---|
六库同一实例,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。
合库时的逐表处置见 <code>merge-conflict-plan.md</code>: 19 组重名表每张保留哪一份、哪些副本可丢弃、哪些必须先查调用方。
废弃/备份表的逐张确认见 <code>../docs/废弃表逐张确认.md</code> (由 <code>tools/audit_junk_tables.py</code> 生成,可随时重跑)。 61 张嫌疑表逐张给了精确行数 + 代码引用 + 建表时间;处置结论见 <code>legacy-db-actual.md</code> §8.3 / §10.1。
合库怎么动手见 <code>migration/README.md</code>:
该目录的产物(排除清单 / 并集补行 / 快速校验 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 新建表的脚本与执行顺序
本地从零起库:
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 从本地镜像库导出),
用途是「拿一份能装到别的服务器上的部署包」,完整说明与验收证据见 <code>DEPLOY.md</code>。
生成物不要手改——改了下次重跑就没了。
本地 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 条)