1.0.296.sql 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. -- 1.0.296.sql S0 Warehouse P1:ItemPackMaster / NbrControl 唯一索引租户化(幂等 + 冲突前置守卫)。
  2. -- 目的:唯一约束从「全局业务键」改为「租户内业务键」,使不同租户可复用相同业务编码,
  3. -- 同一租户内重复业务键仍被数据库拒绝。
  4. -- 不改字段类型、不改 tenant_id 可空性、不插入/更新/删除任何业务数据、不动其它表索引。
  5. -- 业务键依据(information_schema 实证 + 消费链路审计):
  6. -- ItemPackMaster:历史唯一键 (Domain, ItemNum);Domain 与 factory_ref_id 在数据中 1:1;
  7. -- tenant 内唯一键 = (tenant_id, Domain, ItemNum)。
  8. -- NbrControl :历史唯一键 (domain_code, NbrType);domain_code NOT NULL(现值全为空串,作安全唯一锚),
  9. -- NbrType 为真实单号类型码;tenant 内唯一键 = (tenant_id, domain_code, NbrType)。
  10. -- ============================================================================
  11. -- ========== 一、冲突前置检测(同租户内重复则跳过创建,绝不自动清理历史数据) ==========
  12. SET @ipk_dup := (SELECT COUNT(*) FROM (
  13. SELECT tenant_id, `Domain`, ItemNum
  14. FROM ItemPackMaster
  15. GROUP BY tenant_id, `Domain`, ItemNum
  16. HAVING COUNT(*) > 1) t);
  17. SET @nbr_dup := (SELECT COUNT(*) FROM (
  18. SELECT tenant_id, domain_code, NbrType
  19. FROM NbrControl
  20. GROUP BY tenant_id, domain_code, NbrType
  21. HAVING COUNT(*) > 1) t);
  22. SELECT @ipk_dup AS itempack_intratenant_dup, @nbr_dup AS nbrcontrol_intratenant_dup;
  23. -- 期望均为 0;若非 0,本脚本不会创建新唯一索引(下方 ADD 被守卫跳过),需人工治理后重跑。
  24. -- ========== 二、ItemPackMaster:旧全局唯一索引 -> 租户内唯一索引 ==========
  25. -- 2.1 删除旧唯一索引 uk_ItemPackMaster_domain_itemnum(存在才删)
  26. SET @has_old_ipk := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  27. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
  28. AND INDEX_NAME = 'uk_ItemPackMaster_domain_itemnum');
  29. SET @sql := IF(@has_old_ipk > 0,
  30. 'ALTER TABLE ItemPackMaster DROP INDEX uk_ItemPackMaster_domain_itemnum', 'DO 0');
  31. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  32. -- 2.2 创建新租户内唯一索引(不存在且无同租户冲突才建)
  33. SET @has_new_ipk := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  34. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
  35. AND INDEX_NAME = 'uk_ItemPackMaster_tenant_domain_itemnum');
  36. SET @sql := IF(@has_new_ipk = 0 AND @ipk_dup = 0,
  37. 'ALTER TABLE ItemPackMaster ADD UNIQUE INDEX uk_ItemPackMaster_tenant_domain_itemnum (tenant_id, `Domain`, ItemNum)',
  38. 'DO 0');
  39. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  40. -- 2.3 处理隐藏的第二个全局唯一索引 ix_ItemPackMaster (Domain, ItemNum)。
  41. -- 该索引 NON_UNIQUE=0(唯一),与被删的 uk 同样强制全局唯一,会继续阻断跨租户相同 (Domain, ItemNum),必须一并处理。
  42. -- 方案:删除其“唯一”版本,重建为“非唯一”同名索引(保留 (Domain, ItemNum) 查询性能,但不再强制全局唯一)。
  43. SET @ix_unique := (SELECT COUNT(*) FROM information_schema.STATISTICS
  44. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
  45. AND INDEX_NAME = 'ix_ItemPackMaster' AND NON_UNIQUE = 0);
  46. SET @sql := IF(@ix_unique > 0,
  47. 'ALTER TABLE ItemPackMaster DROP INDEX ix_ItemPackMaster', 'DO 0');
  48. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  49. SET @has_ix := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  50. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
  51. AND INDEX_NAME = 'ix_ItemPackMaster');
  52. SET @sql := IF(@has_ix = 0,
  53. 'ALTER TABLE ItemPackMaster ADD INDEX ix_ItemPackMaster (`Domain`, ItemNum)', 'DO 0');
  54. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  55. -- ========== 三、NbrControl:旧全局唯一索引 -> 租户内唯一索引 ==========
  56. -- 3.1 删除旧唯一索引 uk_NbrControl_domain_nbrtype(存在才删)
  57. SET @has_old_nbr := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  58. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'NbrControl'
  59. AND INDEX_NAME = 'uk_NbrControl_domain_nbrtype');
  60. SET @sql := IF(@has_old_nbr > 0,
  61. 'ALTER TABLE NbrControl DROP INDEX uk_NbrControl_domain_nbrtype', 'DO 0');
  62. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  63. -- 3.2 创建新租户内唯一索引(不存在且无同租户冲突才建)
  64. SET @has_new_nbr := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  65. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'NbrControl'
  66. AND INDEX_NAME = 'uk_NbrControl_tenant_domain_nbrtype');
  67. SET @sql := IF(@has_new_nbr = 0 AND @nbr_dup = 0,
  68. 'ALTER TABLE NbrControl ADD UNIQUE INDEX uk_NbrControl_tenant_domain_nbrtype (tenant_id, domain_code, NbrType)',
  69. 'DO 0');
  70. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  71. -- ========== 四、执行后验证 ==========
  72. SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE, SEQ_IN_INDEX, COLUMN_NAME
  73. FROM information_schema.STATISTICS
  74. WHERE TABLE_SCHEMA = DATABASE()
  75. AND TABLE_NAME IN ('ItemPackMaster', 'NbrControl')
  76. AND INDEX_NAME <> 'PRIMARY'
  77. ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
  78. -- 期望:ItemPackMaster 仅 uk_ItemPackMaster_tenant_domain_itemnum(唯一) + ix_ItemPackMaster(NON_UNIQUE=1);
  79. -- NbrControl 仅 uk_NbrControl_tenant_domain_nbrtype(唯一)。均无全局唯一 (Domain,ItemNum)/(domain_code,NbrType)。
  80. -- ============================================================================
  81. -- 回滚 SQL(如需恢复到全局唯一索引,历史数据无跨租户同键冲突时可执行):
  82. -- ALTER TABLE ItemPackMaster DROP INDEX uk_ItemPackMaster_tenant_domain_itemnum;
  83. -- ALTER TABLE ItemPackMaster DROP INDEX ix_ItemPackMaster;
  84. -- ALTER TABLE ItemPackMaster ADD UNIQUE INDEX uk_ItemPackMaster_domain_itemnum (`Domain`, ItemNum);
  85. -- ALTER TABLE ItemPackMaster ADD UNIQUE INDEX ix_ItemPackMaster (`Domain`, ItemNum);
  86. -- ALTER TABLE NbrControl DROP INDEX uk_NbrControl_tenant_domain_nbrtype;
  87. -- ALTER TABLE NbrControl ADD UNIQUE INDEX uk_NbrControl_domain_nbrtype (domain_code, NbrType);
  88. -- 注意:回滚前必须确认不存在跨租户相同业务键,否则回滚 ADD 会因重复而失败(这正是本次改造要放开的场景)。
  89. -- ============================================================================