Sfoglia il codice sorgente

docs(ac15): §19 同一列上的两套 id 空间(pay / courses_student_no / order_no)+ F18 注明「测试归一≠生产一致」

Q 4 ore fa
parent
commit
03daa90a74
1 ha cambiato i file con 46 aggiunte e 0 eliminazioni
  1. 46 0
      internal/academic/dualrun/stage2/README.md

+ 46 - 0
internal/academic/dualrun/stage2/README.md

@@ -1258,3 +1258,49 @@ $sql = "SELECT SUM(t1.discount_amount-t1.discount_amount_used) as amount FROM ..
 实现挂在 gorm 的 `logger.Trace` 上而不是 callback:`Trace` 是每条 SQL 的必经之路
 (Query / Row / Exec / Raw 全走它),挂在上面**不会漏**;按 callback 注册要漏一个就少算一类,
 而"少算"在报告上表现为"次数偏低",读的人只会以为"没有额外开销"。
+
+## 19. 同一列上的两套 id 空间(同一族的三个实例)
+
+「两套 id / 命名空间在同一列上相遇」已经出现三次。它每次的症状都是
+**假红看起来像实现错**,或**假绿看起来像一致** —— 所以单列一节。
+
+| # | 列 | 空间 A | 空间 B | 后果 |
+|---|---|---|---|---|
+| 1 | `pay` | 支付方式 | 支付状态 | 同名三义,读错就断言错 |
+| 2 | `t_courses_activity_user_discount_log.courses_student_no` | 迁移搬入的**旧 UUID** | 新写入落 `courses_student_id`(数字),此列**禁止新写入** | 直接比 `courses_student_no` 在**迁移数据**上对、在**新写入**上恒 NULL |
+| 3 | `t_courses_activity_used_log.order_no` | 历史 8185 行全是 **32 位十六进制 UUID**(实测:纯数字 0 行) | target 新写入是 `orderNoOf(StudentCourseID)` = **数字字符串** | 迁移来的历史额度**通过新 API 永远冲回不了**(台账 F18) |
+
+### 19.1 双跑里的处置:归一 + **注明这是测试归一**
+
+```go
+// legacy   used_log.orderid  = 旧 UUID(c.StuCourseID)
+// target   used_log.order_no = 数字 id 字符串(fmt.Sprint(c.StuCourseNumericID))
+// 两侧永远不可能相等 ⇒ 比对时都归一成 "<order>"(orderToken)
+```
+
+⚠ **必须在报告与 doc 里写清:这是"测试口径归一",不代表生产上两列形态一致。**
+否则将来有人看到"两侧一致",会以为数据形态也一致 —— 而实际上第 3 例是一个**功能缺口**(F18),
+不是测试细节:`RefundRechargeGift` 的游标是 `WHERE orderid = ?`,而 target 的入参是 `int64`,
+前端只能传数字 id ⇒ **历史 8185 条额度没有 API 能冲回**。
+
+### 19.2 检查方法(三步,照做就不会漏)
+
+1. **看列注释里的"写入:谁"**。`courses_student_no` 写着「写入:**迁移搬入**|红线:**禁止新写入**」——
+   这就等于告诉你"新老数据在这一列上不同形态"。**读注释比读代码快,也比读直觉准。**
+2. **数一下真实数据的形态分布**:
+   ```sql
+   SELECT SUM(orderid REGEXP '^[0-9]+$') AS 纯数字,      -- 0
+          SUM(orderid REGEXP '^[0-9a-f]{32}$') AS 十六进制, -- 8185
+          SUM(orderid IS NULL OR orderid='') AS 空
+     FROM finance_bk.t_courses_activity_used_log;
+   ```
+   全落在一个桶里 ⇒ 单形态;**落在两个桶里 ⇒ 这一列上有两套空间**。
+3. **问一句"新写入走哪条路"**:如果写入方拿的是 `int64` 而列里存的是 UUID,
+   那两套必然相遇,且**新老数据互相看不见**。
+
+### 19.3 另一条同族的坑:种子必须"先造出前一个状态"
+
+`refund_activity_recharge` 的游标是 `WHERE orderid=? AND activity_money_used<0 ORDER BY id DESC`。
+**不先种一条负值 used_log,冲回根本进不去循环** ⇒ case 会退化成"两侧都 no-op"的空过 ——
+而它照样全绿。这和 C2 的 `Baseline` 是同一个道理:**要测"后一个动作",必须先把"前一个状态"造出来**,
+并且**显式断言那个状态确实被造出来了**(否则空过的形态和正常形态无法区分)。