| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- -- =====================================================================================
- -- 1.0.521 · S1 · 让 mdp_std_so 成为销售订单行的真实镜像(淘汰源侧已删除的孤儿)
- --
- -- ── 缺陷 ────────────────────────────────────────────────────────────────────────────
- -- 贴源层 mdp_stg_so 是纯 upsert、从不淘汰行;标准层 mdp_std_so 的 INSERT 又不带任何
- -- 批次条件、每轮重读整张贴源表。于是源侧被硬删除的订单行会:
- -- ① 永远留在 mdp_stg_so;
- -- ② 每轮被重新物化进 mdp_std_so,并且 sync_batch_id / sync_time 被刷成最新
- -- —— 所以「看时间戳」根本认不出它们是陈旧数据。
- --
- -- 实测(aidopdev,2026-09-08)租户 797403760988229:
- -- mdp_std_so 414 行 = 28 行真实存活 + 25 行 biz_key 旧分隔符(':')的重复行
- -- + 361 行源侧已删除的孤儿
- -- 其余三个租户 339/14/9 行,与源侧逐行一致,孤儿为 0。
- --
- -- ── 影响面不止 S8 ───────────────────────────────────────────────────────────────────
- -- mdp_std_so 有 16 个读取点,且没有任何一个带「当前行」判据:
- -- · dwd_ship_trans 以它为驱动表 —— 每条孤儿凭空造出一条发运行,日期早已过期,
- -- 必然被判 DELAYED / 高风险;
- -- · S1_L1_001/002/003、S1_L2_010/011/012、S7_L1_001/002/003、S9_L1_003 等 KPI
- -- 的分子分母都被孤儿污染;
- -- · S8 Rule02(订单交付延期预警)若改读中台,孤儿会直接变成凭空的异常单。
- --
- -- ── 修法 ────────────────────────────────────────────────────────────────────────────
- -- 不新增「当前标记」列,而是让标准层恢复「镜像」语义,这样 16 个消费者一个都不用改:
- -- ① 本脚本把 S1_SEORDER / S1_SEORDER_ENTRY 的 sync_mode 由 INCR 改为 FULL;
- -- ② 代码侧(S1MdpSyncTransformService 的 mdp_std_so 构建)只取每个租户的
- -- 最新一次 sync_batch_id;
- -- ③ 本脚本一次性清掉标准层里已经沉淀的孤儿。
- --
- -- 为什么 ① 是 ② 成立的前提:MdpSyncWindowResolver.Apply 对 sync_mode='FULL' 的实体
- -- 会强制 ctx.FullRefresh=true(:34-35),因而在**任何**调用路径上都跳过增量水位——
- -- 包括 RunInboundAsync 与 MdpHotWatchService 这两条 fullRefresh=false 的路径。
- -- 若维持 INCR,那两条路径会产出「只含变更行」的部分批次,② 会把未变更的存量行
- -- 当成孤儿删掉,后果比现在的缺陷更严重。
- --
- -- 代价可忽略:这两个实体实测分别只有 ~390 / ~81 行,且 S1 全量作业本来就每轮
- -- 以 fullRefresh:true 全量重读它们 —— 本次只是让「声明」与「实际行为」一致。
- -- =====================================================================================
- -- ── ① 让声明与实际行为一致:这两个实体每轮都是全量重读 ─────────────────────────────
- UPDATE `mdp_entity`
- SET `sync_mode` = 'FULL',
- `incr_column` = NULL,
- `update_time` = NOW()
- WHERE `entity_code` IN ('S1_SEORDER', 'S1_SEORDER_ENTRY')
- AND `sync_mode` <> 'FULL';
- -- ── ② 一次性清除标准层已沉淀的孤儿 ──────────────────────────────────────────────────
- -- 判据:该 (tenant_id, source_biz_key) 不在贴源层「该租户最新一次 sync_batch_id」里。
- --
- -- ⚠️ 安全闸门 —— `EXISTS (该租户在贴源层确实有数据)`:
- -- 若某租户在 mdp_stg_so 里一行都没有(从未同步 / 贴源被清过),
- -- 「最新批次」子查询会返回 NULL,NOT EXISTS 对该租户的每一行都成立,
- -- 没有这道闸门就会把该租户的标准层整个删空。这里宁可不删,也不冒清空的风险。
- DELETE s FROM `mdp_std_so` s
- WHERE s.`source_table` = 'crm_seorderentry'
- AND EXISTS (SELECT 1 FROM `mdp_stg_so` g
- WHERE g.`source_table` = 'crm_seorderentry'
- AND g.`tenant_id` = s.`tenant_id`)
- AND NOT EXISTS (
- SELECT 1 FROM `mdp_stg_so` lb
- WHERE lb.`source_table` = 'crm_seorderentry'
- AND lb.`tenant_id` = s.`tenant_id`
- AND lb.`source_biz_key` = s.`source_biz_key`
- AND lb.`sync_batch_id` = (SELECT x.`sync_batch_id` FROM `mdp_stg_so` x
- WHERE x.`source_table` = 'crm_seorderentry'
- AND x.`tenant_id` = s.`tenant_id`
- ORDER BY x.`sync_time` DESC, x.`id` DESC
- LIMIT 1));
|