| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- -- =====================================================================================
- -- 1.0.525 · S3 · 让 mdp_std_purchase_order 恢复「源的镜像」语义(Rule01 Authority 整改)
- --
- -- ── 缺陷 ────────────────────────────────────────────────────────────────────────────
- -- 标准层的 INSERT 不带任何批次条件,每轮重读整张贴源表;贴源层又是纯 upsert、从不淘汰。
- -- 于是源侧已删除的采购订单行会:
- -- ① 永远留在 mdp_stg_purchase_order;
- -- ② 每轮被重新物化进 mdp_std_purchase_order,并被盖上最新的 sync_batch_id
- -- —— 所以「看时间戳」根本认不出它们是陈旧数据。
- --
- -- 实测(aidopdev,2026-09-08)租户 797403760988229:
- -- 源侧 PurOrdDetail 182 行;贴源最新批次 182 行(与源逐行相等);
- -- 标准层 518 行,其中 336 行源侧已不存在(64.9%),且 518 行共享同一个批次号。
- -- DWD 是标准层的 1:1 复制(518/518,两侧差集均为 0)——
- -- 即孤儿首现于标准层,不是 DWD 也不是 Provider。
- --
- -- 另有身份断裂放大了它:source_biz_key 的格式与 source_system 都换过代
- -- (旧 `Domain:PurOrd:Line` + AIDOP → 新的裸 RecID + AIDOPDEV_MYSQL),
- -- 而贴源唯一键是 (source_system, source_table, source_biz_key),
- -- 两代永远不会互相 ODKU 覆盖,因此并存至今(实测 568 行旧格式 / 258 行新格式)。
- --
- -- ── 影响面 ──────────────────────────────────────────────────────────────────────────
- -- S8 Rule01(采购交付延期预警)读 dwd_supplier_delivery 的最新快照。
- -- 若按其判据评估租户 797 当前快照:483 行命中,其中 335 行(69%)对应的采购订单行
- -- 在源侧已不存在 —— 启用即凭空建出 335 条永不恢复的异常。
- --
- -- ── 修法 ────────────────────────────────────────────────────────────────────────────
- -- 代码侧(本迁移的前提,随同一版本发布):
- -- · 标准层 INSERT 按 @BatchId 收窄驱动行;
- -- · 每轮追加淘汰步骤,删掉本租户下批次号不等于 @BatchId 的行;
- -- · DWD 追加同日淘汰,使 MAX(stat_date) 取到的必然是单一批次。
- -- 本脚本负责两件代码做不到的事:
- -- ① 把两个实体的 sync_mode 由 INCR 改为 FULL(下方 ① 的理由);
- -- ② 一次性清掉已经沉淀的孤儿(代码只能阻止新增,删不掉存量)。
- -- =====================================================================================
- -- ── ① 让声明与实际行为一致:这两个实体每轮都必须全量重读 ─────────────────────────────
- --
- -- 「不在本批次里」只有在**每轮都是全量重读**时才等于「源侧已删除」。
- -- S3 的全量作业确实以 fullRefresh:true 调用,但 RunInboundAsync 与 MdpHotWatchService 不是,
- -- 而贴源层里已经存在这样一个部分批次(`hot-20260813003331`,3 行)——
- -- 若它恰好是最新批次,按批次淘汰会把该租户的标准层几乎删空。
- -- 只有 sync_mode='FULL' 能让 MdpSyncWindowResolver 在**所有**调用路径上强制 FullRefresh=true。
- --
- -- 只改这两个正式实体;*_API 与 *_SQLSERVER 变体当前未被调度,保持原样不动。
- UPDATE `mdp_entity`
- SET `sync_mode` = 'FULL',
- `incr_column` = NULL,
- `update_time` = NOW()
- WHERE `entity_code` IN ('S3_PURCHASE_ORDER_MASTER', 'S3_PURCHASE_ORDER_DETAIL')
- AND `sync_mode` <> 'FULL';
- -- ── ② 一次性清除标准层已沉淀的孤儿 ──────────────────────────────────────────────────
- --
- -- 判据:该 (tenant_id, po_no, po_line) 在源表 PurOrdDetail 里已不存在。
- -- 直接对源表求证而不是对贴源层 —— 贴源层本身也在累积,拿它当基准会漏掉一部分。
- --
- -- ⚠️ 安全闸门 —— 「该租户在源表里确实有数据」:
- -- 若某租户在 PurOrdDetail 里一行都没有(从未接入 / 源侧被清空),
- -- 没有这道闸门就会把该租户的标准层整个删空。这里宁可不删,也不冒清空的风险。
- -- (源侧合法清空的场景由代码侧每轮淘汰处理,它用 mdp_sync_log 的成功记录做闸门,
- -- 能区分「拉取成功且确实 0 行」与「拉取没跑成」。)
- --
- -- 规模:实测全表 787 行、孤儿 339 行,DELETE 走 uk_po_line 主键回表,毫秒级。
- DELETE s FROM `mdp_std_purchase_order` s
- WHERE EXISTS (SELECT 1 FROM `PurOrdDetail` g WHERE g.`tenant_id` = s.`tenant_id`)
- AND NOT EXISTS (
- SELECT 1 FROM `PurOrdDetail` d
- WHERE d.`tenant_id` = s.`tenant_id`
- AND d.`PurOrd` = s.`po_no`
- AND CAST(d.`Line` AS CHAR) = s.`po_line`);
- -- ── ③ 清除 DWD 当前快照里已失去标准层依据的行 ───────────────────────────────────────
- --
- -- Rule01 只读每租户 stat_date 最大的那一天。②之后标准层已经干净,
- -- 但当天的 DWD 快照仍留着孤儿 —— 代码侧的同日淘汰要等下一轮跑批才会生效,
- -- 而部署后规则可能先于跑批被启用。这里把当前快照一次性对齐。
- --
- -- 只处理每租户的最新 stat_date,不动历史快照(历史是留档,Provider 不读)。
- -- 用派生表而不是相关子查询:dwd_supplier_delivery 实测 41,422 行,
- -- 相关子查询在该表上已多次超时(历史上有 600s 迁移超时的先例)。
- DELETE w FROM `dwd_supplier_delivery` w
- JOIN (SELECT `tenant_id`, MAX(`stat_date`) AS `latest_stat_date`
- FROM `dwd_supplier_delivery` GROUP BY `tenant_id`) t
- ON t.`tenant_id` = w.`tenant_id` AND t.`latest_stat_date` = w.`stat_date`
- WHERE NOT EXISTS (
- SELECT 1 FROM `mdp_std_purchase_order` s
- WHERE s.`tenant_id` = w.`tenant_id`
- AND s.`po_no` = w.`po_no`
- AND s.`po_line` = w.`po_line`);
- -- ── 回滚(注释,非可执行)──
- -- UPDATE mdp_entity SET sync_mode='INCR', incr_column='UpdateTime'
- -- WHERE entity_code IN ('S3_PURCHASE_ORDER_MASTER','S3_PURCHASE_ORDER_DETAIL');
- -- 被删除的孤儿不做回滚:它们对应的采购订单行在源侧已不存在,
- -- 重建方式是重跑 S3 全量跑批(标准层与 DWD 均为纯派生)。
|