# 老库实测结论(只读账号直连) > 本文档记录 **2026-10-09** 用只读账号对老库做的实测。所有数字均为查询所得, > 不是推断。与 [`README.md`](./README.md) §4.2 的"待办清单"一一对应。 > > 复现方式:[`tools/legacy_db.py`](./tools/legacy_db.py)(连通性/表清单/DDL)、 > [`tools/compare_dup_tables.py`](./tools/compare_dup_tables.py)(重名表结构比对)。 > 凭据在 `私密/legacy-ro.env`(已 gitignore)。 > > ⚠️ 本文件里会出现**源实例地址与账号名**——它们是"实测对象"的标识,不写就说不清结论是 > 对哪台库得出的(该账号是**只读**的,没有口令也无法利用)。**口令不在本文件、也不在仓库 > 任何文件里**,只存在于 `私密/legacy-ro.env`。别为了"方便复现"把口令补进来。 ## 0. 连库方式:CLI 不可用,必须走 PyMySQL 本机 Homebrew 的 `mysql` 客户端是 **26.7**,已**移除 `mysql_native_password` 插件**, 而老库账号正是该认证方式,直接连报: ``` ERROR 2059 (HY000): Authentication plugin 'mysql_native_password' cannot be loaded ``` PyMySQL 用纯 Python 实现了该插件,故统一走 PyMySQL。 (备选:`/opt/homebrew/opt/mysql-client@9.6` 的插件目录里仍有该 .so,但需显式指定 plugin-dir。) **只读承诺的执行情况**:全程仅发 `SELECT` / `SHOW` / 读 `information_schema`, 未做任何写操作。账号本身也确为只读(见 §2 授权)。 ## 1. 六库同实例 ✅,但**库名有严重误导性** 先纠正一处我自己的误判:各模块配置文件里写的 `druid.url`(指向 `rm-2zeq6z9f00w52tcq7jo`)**与代码实际面向的库不符**,不能作为迁移源依据。 判定迁移源的可靠方法是:**看代码引用的表在哪个库里真实存在**。 按此做的模块 × 库命中矩阵(去分表后缀): | 模块 | 引用表数 | `zt_sys` | `zt_park` | `zt_order` | `zt_order_n` | `zt_bill` | `zt_charge` | |---|---:|---:|---:|---:|---:|---:|---:| | `zteits-sys` | 32 | **30 (94%)** | 1 | 1 | 1 | – | – | | `zteits-park` | 127 | – | **110 (87%)** | 2 | 3 | 1 | – | | `zteits-order` | 91 | – | – | 9 | **86 (95%)** | – | – | | `zteits-pay` | 86 | – | – | **79 (92%)** | 8 | – | – | | `zteits-bill` | 55 | – | – | 1 | – | **54 (98%)** | – | | `zteits-charge` | 4 | – | – | – | – | – | **4 (100%)** | 每个模块都有**唯一且高命中率**的归属库 → 六库全在 `renniting-prod-net.mysql.zhangbei.rds.aliyuncs.com` 这一台实例上, `INSERT INTO ... SELECT` 同实例搬迁的前提**成立**。 ### ⚠️ 但有两个必须记住的命名陷阱 1. **`zt_order` 里装的是 pay 域,不是 order 域**。 `zteits-pay` 引用的 86 张表里 **79 张在 `zt_order`**(92%); 而 order 域的表在 **`zt_order_n`**。库名与业务域**刚好错位**。 2. **`zt_order` 里还混着一份 2018 年的订单归档**。 该库的 `td_b_order` / `td_b_pay_order` / `td_b_order_detail_park` 末行 `create_date` 分别是 **2018-04-08 / 2018-04-26 / 2018-03-27**—— 冻结了 8 年;而同库的 `td_c_cust_person`(客户)今天仍在写入。 所以 `zt_order` = **pay 活库 + 2018 订单死档案**, 搬迁时**不能整库照搬**,要按表区分。 > 建议:新库按**业务域**命名,不要沿用 `zt_order`/`zt_order_n` 这种 > 与内容错位的旧名(方案 §4.4 定的 `parking_cloud` 单库 + 前缀正是为此)。 ### 对照:另一台实例是**另一个环境** `rm-2zeq6z9f00w52tcq7jo` 上有 `zt_sys`/`zt_park`/`zt_order`/`zt_pay`/`zt_bill`/`zt_business` ——库名与张北部分重叠但**内容不同**(例如它有 `zt_pay`,张北没有)。 本次迁移**不涉及**该实例(除 `zteits-business` 的 11 张表,见 §7)。 ## 2. 只读账号的授权范围(已核实,覆盖完整) ``` GRANT SELECT, LOCK TABLES, SHOW VIEW ON `zt_sys`.* TO 'readonly'@'%' GRANT SELECT, LOCK TABLES, SHOW VIEW ON `zt_park`.* TO 'readonly'@'%' GRANT SELECT, LOCK TABLES, SHOW VIEW ON `zt_order`.* TO 'readonly'@'%' GRANT SELECT, LOCK TABLES, SHOW VIEW ON `zt_order_n`.* TO 'readonly'@'%' GRANT SELECT, LOCK TABLES, SHOW VIEW ON `zt_bill`.* TO 'readonly'@'%' GRANT SELECT, LOCK TABLES, SHOW VIEW ON `zt_charge`.* TO 'readonly'@'%' GRANT SELECT, LOCK TABLES, SHOW VIEW ON `xxl_job`.* TO 'readonly'@'%' GRANT PROCESS, REPLICATION SLAVE, REPLICATION CLIENT ON *.* ``` 确为只读,且**正好覆盖迁移所需的六个库**——授权范围与 §1 的归属矩阵完全吻合, 说明这个账号就是为本次六库合一准备的。 > ⚠️ 澄清一个我此前的误报:`zt_pay` 不在这份授权里,但**它不是缺失**—— > pay 域的表物理上就在 `zt_order`(§1)。所以"pay 库结构拿不到"这个卡点 > **不存在**,无需额外授权。 ## 3. 六库概览 | 库 | 表数 | 近似行数 | 数据大小 | 非 utf8mb4 表 | |---|---:|---:|---:|---:| | `zt_park` | 387 | 1,193,632,380 | 414.4 GB | 368 | | `zt_order_n` | 112 | 409,273,992 | 260.3 GB | 99 | | `zt_order` | 108 | 293,091,812 | 88.8 GB | 106 | | `zt_bill` | 60 | 5,656,547 | 1.35 GB | 55 | | `zt_sys` | 38 | 1,775,497 | 0.58 GB | 38 | | `zt_charge` | 4 | 16 | 0.1 MB | 0 | | **合计** | **709** | ~19 亿 | **~747 GB** | **666** | 要点: - **规模远超预期**:747 GB / 19 亿行,且 19 亿里有 16 亿在 `zt_park`。 迁移窗口、分批策略都要按这个量级重估,不能用"小库直接搬"的假设。 - **`zt_charge` 基本是空库**(4 表 / 16 行)→ 充电业务实际未在这套库上跑, 或跑在 `dev_charge`。 - `TABLE_ROWS` 是 InnoDB 估算值(分表族会被逐张累加),量级可信,精确值需逐表 `COUNT(*)`。 - **非 InnoDB 表 0 张**(无视图、无 MyISAM),搬运无引擎障碍。 ## 4. 重名表:19 组,**7 组可直接合并、12 组必须处理** ### 4.1 ✅ 结构完全一致(列+索引+排序规则全同)—— 7 组 | 表 | 库 | |---|---| | `income_temp` | `zt_order_n`、`zt_park` | | `month_dchz` | `zt_order_n`、`zt_park` | | `table_15_free` | `zt_order_n`、`zt_park` | | `td_b_settlement` | `zt_order`、`zt_order_n` | | `td_b_settlement_org` | `zt_order`、`zt_order_n` | | `td_b_settlement_share_record` | `zt_order`、`zt_order_n` | | `td_p_parking_share_order_temp` | `zt_order`、`zt_order_n` | ### 4.2 ⚠️ 结构不一致 —— 12 组 差异分三类,处理方式完全不同: | 表 | 差异性质 | 活跃份 | |---|---|---| | **`parking_lot`**(4 库) | `zt_bill` == `zt_order`(列全同,仅索引差);`zt_order_n` / `zt_park` 是**超集**(多 `area_type`/`is_special`/`parent_pl_no`/`pl_category`/`push_state`/`support_etc` 等 8~10 列) | `zt_park`(3040 行,08-23 更新) | | **`sys_org`**(4 库) | `zt_sys` == `zt_park` == `zt_order_n`;`zt_order` 是旧版(`short_name` 非空、缺 `index_org_id`) | `zt_sys`(204 行);`zt_order` 为 **0 行** | | `park_in_out_record` | `zt_park` 是超集(多 4 列 + 6 个索引) | `zt_park`(9674 万行 / 32 GB,今天更新);`zt_order` 仅 115 万行 / 231 MB,**长期未更新** | | `t_vip_card_cust_info` | `zt_bill` 多 3 列(`cust_name`/`cust_phone`/`parent_cust_card_no`) | `zt_bill`(78816 行,今天更新);`zt_order` 1892 行,陈旧 | | `tb_b_park_income_day` | `zt_order_n` 多 2 个索引 | `zt_order_n`(22.7 万行,今天 02:00);`zt_order` 1796 行 | | `td_b_settlement_record` | `zt_order_n` 多 3 列(`case_refund_*`),`check_status` 默认值不同(左空/右 100) | `zt_order_n`(8604 行,今天 02:01);`zt_order` 90 行 | | `td_b_settlement_serial` | `zt_order_n` 多 `settlement_month` | 两侧都近乎空 | | `td_b_wx_config` | `zt_order_n` 多 `cert_name`;`org_id` 类型 int(30) vs int(10) | 两侧各 19/23 行 | | `td_c_coupon_person` | `cust_id` 索引不同 | — | | **`td_c_cust_person`** | ⚠️ **方向相反**:`zt_order` 反而多 4 列(`etc_open_state`/`etc_protocol_state`/`freeze_date`/`freeze_state`),`deivce_type` 默认值不同 | `zt_order`(82691 行,今天 10:59);`zt_order_n` 26367 行,陈旧 | | `tp_e_employee_charge` | `zt_park` 多 7 列(`eqp_*` 设备字段) | `zt_park`(2217 行,今天 10:55) | | `tp_p_berths` | `zt_park` 多 `car_number_color` | `zt_park`(30162 行,今天 11:01) | #### 由这 12 组得出的关键判断 **重名表其实是两种性质,必须分开处理:** 1. **跨库冗余副本**(`sys_org`、`parking_lot`、`park_in_out_record`、 `t_vip_card_cust_info`、`tp_p_berths`、`tp_e_employee_charge`) —— 为了在本库做 JOIN 而复制过来的**只读副本**,长期不同步、结构已漂移。 典型如 `sys_org`:`zt_sys` 有 204 行,`zt_park`/`zt_order_n` 各只有 20 行, `zt_order` 更是 **0 行** —— 副本不仅滞后,还是残缺的。 2. **新老两代同表**(`zt_order` vs `zt_order_n` 的结算/收入系列) —— 且**活跃方向不一致**:`tb_b_park_income_day`、`td_b_settlement_record` 活的是 `zt_order_n`,而 `td_c_cust_person` 活的是 `zt_order`。 **没有任何一条统一规则可判**,必须逐表定性。 > ⚠️ `parking_lot` / `sys_org` 这类"字段集是超集/子集"的差异, > 合库时**不能取并集**(会破坏另一侧代码),也**不能随意取一侧**(会丢列)。 > > 归属可由 §1 的矩阵推断:`parking_lot` 的活份在 `zt_park`(3040 行、有更新), > `sys_org` 的活份在 `zt_sys`(204 行,其余库只有 20 行甚至 0 行)。 > 也就是说 **`zt_bill`/`zt_order`/`zt_order_n` 里的那几份都是跨库只读副本**—— > 合库时**保留主份、丢弃副本**,并把原来的跨库 JOIN 改成走 `parking-module-api` > (正好落回红线第 7 条"禁止跨模块直接访问数据库")。 > 但**副本上的独有列不能直接丢**——那说明有代码在写副本,需先查清调用方。 ## 5. 字符集:**666/709 张不是 utf8mb4**,另有 30 张 `utf8_bin` 排序规则冲突 | 排序规则 | 表数 | 风险 | |---|---:|---| | `utf8_general_ci` | 635 | 3 字节 utf8,存不下 emoji/生僻字(4 字节) | | `utf8mb4_general_ci` | 39 | 目标形态 | | **`utf8_bin`** | **30** | ⚠️ 见下 | | `utf8mb4_unicode_ci` | 4 | 与 general_ci 混用需统一 | | **`latin1_swedish_ci`** | **1** | ⚠️ `zt_order_n.td_b_pay_blncac_wx`,含中文必乱码 | ### ⚠️ `utf8_bin` 造成的真实 JOIN 障碍 `parking_lot` 在 **`zt_bill` / `zt_order` / `zt_order_n` / `zt_park` 四个库里都是 `utf8_bin`**, 而周边表的同名列是 `utf8_general_ci`: | 表 | 列 | 排序规则 | |---|---|---| | `parking_lot` | `pl_no` | **utf8_bin** | | `park_in_out_record` | `pl_no` | utf8_general_ci | | `tp_p_berths` | `pl_no` | utf8_general_ci | MySQL 5.7 下两者相 `=` 比较会报 `Illegal mix of collations`。 即:**合库统一排序规则后,一批老系统里"本来就跑不通"的 JOIN 会开始能跑** —— 这与 Dubbo 那 8 处"合并顺手修好"属于同一类**行为变化**,回归时要专门过。 另外 `zt_park` 里有一批带 `del20181116` 前缀的历史备份表(如 `del20181116td_g_guide_park_berths`),而代码里仍在按**老名字** `td_g_guide_park_berths` 查(见 §7)——**表被改名删了,代码没跟**。 ## 6. 分表族:实测确认,共 228 张物理表(2 个族) > 🔁 **2026-10-10 修正**:本节原写「230 张、3 个族」,把 > `parking_running_statistic2023/2025` 也算成了族。**那是错的**—— > 判据见下。真正的分表族只有 **2 个、228 张**;那两张是人工快照, > 已移入 §8 的嫌疑清单(**合计约 133 GB**,见 §6.1)——2026-10-10 已拍板**不搬**。 **什么叫分表族**:表名由**代码在运行期拼出来**(`"tp_p_eqp_log" + yyyyMM`)。 因此判据是**代码里存不存在 `"前缀"` 这个字面量**——静态表在 SQL 里永远只以裸名字 出现(`FROM parking_running_statistic`),Java 里不会有它的引号字面量。 这条判据已固化进 `tools/audit_junk_tables.py` 的 `sharded_in_code()`, 并做**双向自检**(登记的都真在拼 / 在拼的都登记了,任一不满足即 exit 2)。 | 库 | 表名前缀 | 后缀 | 张数 | 范围 | 代码拼装处 | |---|---|---|---:|---|---| | `zt_park` | `tp_p_eqp_inout_record` | `yyyyMM` | **115** | 201709 ~ 202703 | `EqpInOutParkStatisticServiceImpl` | | `zt_park` | `tp_p_eqp_log` | `yyyyMM` | **113** | 201711 ~ 202703 | `TpPEqpLogDaoImpl` / `EqpLogDaoImpl` / `EqpHeartBeatJob` | **未来到 2027 年 3 月**(说明有预建表任务)。 合库时必须**连着分表族一起搬**,且不能把 `${...}` 写死; `ParkingLotUseStatisticDaoImpl:169` 那个"用当前时间拼表名"的存量缺陷也在此列。 ### 6.1 ⚠️ 被误判为族的 `parking_running_statistic*`:整个家族约 183 GB 判据(§6)改正后,这个前缀下的物理表**没有一张是分表族成员**,全是静态表; 带后缀的那几张贴是**人工滚动快照**: | 表 | 估算行数 | 大小 | 建表时间 | 代码引用 | 处置 | |---|---:|---:|---|---|---| | `zt_park.parking_running_statistic` | 1.05 亿 | 20.5 GB | 2026-04-17 12:05 | ✅ **有**(2 个 XML)——**全家族唯一被代码读的那张** | 随迁 | | `zt_park.parking_running_statistic2023` | 3.86 亿 | **83.0 GB** | 2024-05-22 | 无 | **不搬**(§10.1) | | `zt_park.parking_running_statistic2025` | 2.41 亿 | **50.5 GB** | 2026-04-17 12:03 | 无 | **不搬**(§10.1) | | `zt_park.parking_running_statistic_his` | 1.25 亿 | 25.3 GB | 2022-07-27 | 无 | 暂定**搬**(未确认) | | `zt_park.parking_running_statistic_chifeng_2019021` | 1947 万 | 3.6 GB | 2022-07-27 | 无 | 暂定**搬**(未确认) | | `zt_park.parking_running_statistic_chifeng` | 1.36 万 | 2.5 MB | 2022-07-27 | 无 | 暂定**搬**(未确认) | **怎么认出它是滚动快照的**:无后缀那张的建表时间与 `…2025` **只差 2 分钟** (12:03 / 12:05),是典型的「把老表 `CREATE TABLE … SELECT` 出来存档、再重建一张空表接着写」。 于是带日期的贴是**滚动前的历史数据**,而代码只读无后缀那张——这也解释了为什么年度 **不连号**(只有 2023、2025,没有 2024):它不是分区方案,是人工存档。 **全家族合计约 183 GB**(占六库 747 GB 的近四分之一),其中 **133.5 GB 已拍板不搬**。 > ⚠️ `_his`(25.3 GB)、`_chifeng_2019021`(3.6 GB)、`_chifeng`(2.5 MB)与已拍板不搬的 > 那两张**是同一形态**,但它们**连嫌疑判据都没命中**(名字里没有日期后缀), > 也**没拿到处置指示**——所以**仍按"先全搬"**。要一并省掉的话说一句即可, > 再省约 29 GB(改写一处 `DO_NOT_MIGRATE` 就行,见 §10.1)。 ## 7. 代码引用、但**六库中都不存在**的表(40 张) 这份清单比"表清单"更有用——它直接指出**哪些代码在查不存在的表**: | 模块 | 张数 | 表 | 定性 | |---|---:|---|---| | `zteits-business` | 11 | `td_c_business_*`、`order_business_coupon*` | 在**另一实例**的 `zt_business`(§1 对照)→ 属方案 §4.6 的随迁例外范畴 | | `zteits-pay` | 5 | `car_search_history`、`cust_person_credit`、`td_b_bills_days`、`td_b_park_card_cust`、`td_b_park_card_rel` | ⚠️ pay 的**归属库是 `zt_order`**(§1),这 5 张在六库里都**真的没有** → 要么是死代码,要么在另一实例。**需逐张确认** | | `zteits-park` | 17 | `hub_*`(6)、`nbtemp`、`generate_test_table`、`inpark_log`、`outpark_log`、`eqp_in_time`、`eqp_out_time`、`park_pay_log`、`parking_lot_berths`、`parking_lot_credentials`、`charger_assess_result_log`、`td_g_guide_park_berths` | 混合:部分是**从未建表的死代码**(`generate_test_table`/`nbtemp`);`td_g_guide_park_berths` 是**表已改名**——同库存在 `del20181116td_g_guide_park_berths`,代码没跟着改;`hub_*` 疑似未上线对接 | | `zteits-order` | 4 | `inspection_log`、`order_arrearage_edit_log`、`order_park_bjxc_success`、`park_in_time` | 待定 | | `zteits-sys` | 2 | `tb_work_order`、`tb_work_order_process` | 工单表,疑似属其他系统 | **对 P2 的意义**:`zt_business` 那 11 张与方案 §4.6 已识别的"business 随迁"对上了, 属**已规划项**;其余 29 张必须在搬迁前逐张定性, 否则会把"本来就坏的代码"原样搬进新系统、继续 silently 失败。 > 已确认的一例:`td_g_guide_park_berths` —— 表在 2018-11-16 被改名成 > `del20181116td_g_guide_park_berths`,代码仍在查老名字。 > 这类"改名未同步"很可能还有别的,值得用同一手法(比对物理表名单)继续挖。 ## 8. 废弃/备份表:逐张确认完毕(2026-10-10) **逐张确认已做完**,产物是 [`../docs/废弃表逐张确认.md`](../docs/废弃表逐张确认.md) (由 [`tools/audit_junk_tables.py`](./tools/audit_junk_tables.py) 生成,**可随时重跑**)。 本节只记**口径与结论**,逐张明细看那份报告。 ### 8.1 确认结果:61 张,按判据分布 | 判据 | 张数 | |---|---:| | `_bak 备份` | 16 | | `名含 temp`(独立词) | 12 | | `_<8位日期> 后缀` | 11 | | `_copy 副本` | 8 | | `_<4位数字> 后缀` | 6 | | `裸<数字>后缀`(无下划线,见 §6.1) | 3 | | 名含 test(独立词) | 2 | | `del 前缀带日期` | 2 | | `_<6位年月> 后缀` | 1 | 按库:`zt_park` 27、`zt_order_n` 13、`zt_sys` 8、`zt_order` 8、`zt_bill` 5 (`zt_pay` / `zt_charge` 无命中)。 > ⚠️ 本节原列的「`del<日期>` 2 张、`_<日期>` 12 张、`_copyN` 8 张、`_bak` 14 张、 > `temp` 词 13 张、`test` 词 1 张」是**按旧口径数的粗数**(未排除分表族、未做词边界), > **以 8.1 表为准**。 ### 8.2 确认方法(为什么不能只看名字) 三条证据缺一不可,全部由脚本产出: 1. **精确 `COUNT(*)`**(不是 `TABLE_ROWS` 估算)——估算对大表可差数十个百分点, 而"这张表到底是不是空的"恰恰是关键判断; 2. **代码引用**——在 `parking-module-*` 里搜表名。分**表位置**(`FROM/JOIN/…` 之后 或 Java 字面量,硬证据)与**疑似**(其余命中)两级,两级都贴命中行原文; 3. **建表时间**——`_bak` 不等于陈旧:`sys_op_advice_bak` 是 **2026-02-25** 建的 (109 万行 / 200 MB,与无后缀的 `sys_op_advice` 只差 **4 秒**——同一类滚动操作)。 近期建的备份表很可能是运维在用的。(这条问对了:2026-10-10 业务方确认那是备份, 不用管 → §10.1。) ### 8.3 结论分布与处置 | 结论 | 张数 | 处置 | |---|---:|---| | 🔴 有表位置代码引用 | 6 | **必须随迁**——是活表,只是名字像残留 | | 🟠 疑似引用(非表位置) | 1 | `zt_park.temp`——已人眼核对,6 处命中**全是 SQL 别名/`foreach item`**,**确认无引用**;它 7 行,故处置同下下行 | | 🟡 有数据但无引用 | 46 | **先全搬**(§10 决策;老库仍在,可逆) | | 🟡 无引用但行数未知 | 1 | `parking_running_statistic_chifeng_2019021`(3.6 GB,超限未数)。同族的 `_his`(25.3 GB)连判据都没命中——这两张**都没拿到处置指示**,仍按"先全搬"(§6.1 末注) | | ⬛ 已拍板不搬(空表 4 + 备份 3) | 7 | **不搬**(新库建表脚本里也不要有)。4 张空表不搬零代价;3 张**有数据的备份**见 §10.1 | 小计 6 + 1 + 46 + 1 + 7 = **61** √ (⚠️ 原表写的是 6 + 2 + 1 + 48 + 4:其中「🟠 近期创建 2 张」已由业务方答复, 转成 ⬛;🟢 4 张空表也归入 ⬛ 以便一处看全。分类口径变了,**总数不变**。) 🔴 的 6 张(**都不许按名字处置**,明细见报告): `zt_order.td_b_temp_parklot`、`zt_order.td_p_parking_share_order_temp`、 `zt_order_n.td_p_parking_share_order_temp`、`zt_park.duty_roster_base_temp`、 `zt_park.tp_p_eqp_inout_record_temp`、`zt_sys.sys_export_temp_file`。 > ⚠️ 这 6 张的存在恰恰说明「名字像临时表」与「是不是临时表」无关: > `td_b_temp_parklot` 是停车场临时信息表(`insert into …`),`duty_roster_base_temp` > 是排班导入的中间表(`TRUNCATE` + 搬进 `duty_roster_base`),都在日活路径上。 > ⚠️ **反方向的风险更大**:也**不能仅凭名字搬**。把 `sys_org_copy` 这类历史副本 > 原样搬进新库,它就永久留在新库里——`00` 建库脚本与后来的建表语句都不知道它存在, > 等于往新库注入一批**无来源、无归属、以后没人敢删**的表。这正是 §8.3 放行的 > 只有两类的原因:**4 张空表**(不搬零代价,判据是数据自证)与 > **3 张拿到明确拍板的备份**(§10.1,**有数据**,判据是业务判断而非数据)。 > 第二类的风险方向相反——**不搬会少数据**,所以它必须有外部拍板留痕, > 条目的理由串里写清"谁、何时、凭什么"(见 `tools/audit_junk_tables.py` 文件头)。 **另注**:`month_dchz`、`table_15_free` 这类"看着像临时表"的**不是残留**, 是被代码正常引用的业务表(且在两库结构一致)——名字判据一开始就没命中它们, 被误列进来过,这里明确排除。 ## 9. 闭环情况与剩余事项 ### 9.1 已闭环 | 原疑问 | 结论 | |---|---| | 迁移源是哪台实例? | ✅ **张北 `renniting-prod-net`**(模块×库命中矩阵 87~100%,见 §1) | | `zt_pay` 结构拿不到? | ✅ **不存在此问题**——pay 域的表就在 `zt_order`(§1) | | 六库是否同实例? | ✅ **是**,同实例可直接 `INSERT INTO ... SELECT` | | `zt_order` / `zt_order_n` 谁是谁? | ✅ `zt_order_n` = order 域活库;`zt_order` = **pay 域活库 + 2018 订单归档**(§1) | | 字符集是否都是 utf8mb4? | ✅ 否,**666/709 不是**,含 30 张 `utf8_bin` + 1 张 `latin1`(§5) | | 分表族到底有多少张? | ✅ 实测 **228 张物理表 / 2 个族**,到 2027-03(§6)。原写的 230 张 / 3 个族含误判,2026-10-10 修正 | | §8 那些废弃/备份表怎么处置? | ✅ **61 张逐张确认完毕**(2026-10-10,§8)→ 分层处置见 §8.3:**7 张不搬**(空表 4 + 备份 3),其余 54 张随迁。其中 3 张备份(约 133 GB)**不再进入迁移体量**(§10.1) | ### 9.2 仍需你定夺 | # | 事项 | 说明 | |---|---|---| | 1 | **`zteits-business` 的 11 张表要不要随迁** | 它们在**另一台实例**的 `zt_business`,本次只读账号覆盖不到。按方案 §4.6,business 仅在"被核心模块运行时调用"时才随迁——需要拿它是随迁还是砍掉 | | 2 | **`zt_order` 里那份 2018 订单归档怎么处理** | 是**一并搬进新库**(保留历史查询能力),还是**只搬 pay 域活表、归档另行放置**?这直接影响迁移体量(该库 88 GB,大部分是归档) | | 3 | 关键大表的精确行数 | 本次用 `TABLE_ROWS` 估算。定迁移窗口前,需对 `zt_park` 的几张大表取精确 `COUNT(*)` | | 4 | ~~§8 的废弃/备份表是否丢弃~~ | ✅ **2026-10-10 已闭环**:61 张逐张确认完毕,口径与处置见 §8;逐张明细见 `docs/废弃表逐张确认.md`(可重跑) | > 第 2 条影响最大:`zt_order`(88 GB)与 `zt_order_n`(260 GB)合计 349 GB, > 若归档可剥离,迁移体量能显著下降。 ## 10. 已确认的决策(2026-10-09) | # | 事项 | 决策 | 影响 | |---|---|---|---| | 1 | `zteits-business` | **随迁**。其 11 张表在另一实例的 `zt_business`,需在该实例开一个**只读**账号(只需 `SELECT ON zt_business.*`),填入 `私密/legacy-ro.env` 的 `LEGACY_DB_BUSINESS_*` | 未提供前,六库比对不受影响;提供后补做 business 的结构比对 | | 2 | `zt_order` 里的 2018 年订单归档 | **一并搬进新库**(零数据丢失优先;老库仍作只读回滚路径) | 迁移体量包含这部分,约为数十 GB | > 决策 2 意味着新库会携带一批"新代码永远不会查"的表。 > 若后续确认体积不可接受,可从**老库**单独导出这批表另存,而不影响新库结构—— > 因此这个决策是**可逆**的,现在按"先全搬"执行。 ### 10.1 废弃/备份表处置(2026-10-10) §9.2 第 4 项闭环后的决定,口径见 §8.3: | # | 事项 | 决策 | 影响 | |---|---|---|---| | 3 | §8 的 61 张废弃/备份表 | **按 §8.3 分层处置**:🔴 6 张随迁(是活表);🟠 1 张已核为无引用;🟡 47 张**先全搬**;⬛ 7 张**不搬**(空表 4 + 备份 3) | 与决策 2 的"零数据丢失优先"一致;⬛ 那 7 张不进 `00` 建库脚本 | | 4 | `parking_running_statistic2023` / `…2025`(**约 133 GB**) | ✅ **2026-10-10 业务方拍板:备份表,无需理会 → 不搬进新库** | 迁移体量最值得省的一块(§6.1);**133 GB 不进 `parking_cloud`**。数据留在老库(未销毁),需要时从老库单独导出 | | 5 | `sys_op_advice_bak`(2026-02-25 建,109 万行 / 200 MB) | ✅ **同上,不搬** | 只有 200 MB,搬不搬都无所谓;按拍板走,与新库"少一批没人认领的表"更一致 | | 6 | `parking_running_statistic_his`(25.3 GB)/ `_chifeng_2019021`(3.6 GB)/ `_chifeng`(2.5 MB) | **仍按"先全搬"**——与决策 4 是同一形态(§6.1),但**没拿到指示** | 要一并省掉约 29 GB 的话,把这三张加进 `DO_NOT_MIGRATE` 的 `备份` 层即可 | **落地在哪(这决定才是真的,报告只是生成物)**: | 落地处 | 内容 | |---|---| | `tools/audit_junk_tables.py` 的 `DO_NOT_MIGRATE` | **唯一事实来源**。值 = `(层级, 理由)`,`备份` 层的理由串必须写清"谁、何时、凭什么" | | 同上,`main()` 的自检 | 逐条复核**前提**仍成立(两层共有:仍存在、仍无表位置引用;`空表` 层另有"仍是 0 行")。不成立就 exit 2 | | `sql/migration/exclude_tables.txt` | 30 行(23 副本 + 7 不搬)→ `mysqldump --ignore-table` | | `sql/migration/99_verify.sql` §1 | `NOT IN (...)`——**不排掉它会永远报红**(那 7 张是有意不在目标库里的) | | `sql/migration/98_count_reconcile.sql` | 排除集同上;顺带把 zt_park 段最贵的两次 `COUNT(*)`(6.27 亿行)也省掉了 | > ⚠️ 决策 3 里 🔴 那 6 张**不能**按"名字像临时表"处理:它们是日活路径上的真表 > (如 `td_b_temp_parklot`、`duty_roster_base_temp`),误删会直接坏业务。 > 这正是 §8 一开始就警告的"不能仅凭名字删"。 > > ⚠️ 决策 4/5 是**唯一**一批"知道有数据、仍然不搬"的表。它们的守卫**比空表弱**: > 只能保证"代码里还是没人读它"这个前提,**证明不了"这是你需要的备份"**—— > 那是业务判断,任何查询都答不了。所以改动它们时先回到这条决策,别只看脚本过不过。