1.0.525.sql 6.8 KB

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