|
|
@@ -0,0 +1,100 @@
|
|
|
+-- 1.0.296.sql S0 Warehouse P1:ItemPackMaster / NbrControl 唯一索引租户化(幂等 + 冲突前置守卫)。
|
|
|
+-- 目的:唯一约束从「全局业务键」改为「租户内业务键」,使不同租户可复用相同业务编码,
|
|
|
+-- 同一租户内重复业务键仍被数据库拒绝。
|
|
|
+-- 不改字段类型、不改 tenant_id 可空性、不插入/更新/删除任何业务数据、不动其它表索引。
|
|
|
+-- 业务键依据(information_schema 实证 + 消费链路审计):
|
|
|
+-- ItemPackMaster:历史唯一键 (Domain, ItemNum);Domain 与 factory_ref_id 在数据中 1:1;
|
|
|
+-- tenant 内唯一键 = (tenant_id, Domain, ItemNum)。
|
|
|
+-- NbrControl :历史唯一键 (domain_code, NbrType);domain_code NOT NULL(现值全为空串,作安全唯一锚),
|
|
|
+-- NbrType 为真实单号类型码;tenant 内唯一键 = (tenant_id, domain_code, NbrType)。
|
|
|
+-- ============================================================================
|
|
|
+
|
|
|
+-- ========== 一、冲突前置检测(同租户内重复则跳过创建,绝不自动清理历史数据) ==========
|
|
|
+SET @ipk_dup := (SELECT COUNT(*) FROM (
|
|
|
+ SELECT tenant_id, `Domain`, ItemNum
|
|
|
+ FROM ItemPackMaster
|
|
|
+ GROUP BY tenant_id, `Domain`, ItemNum
|
|
|
+ HAVING COUNT(*) > 1) t);
|
|
|
+
|
|
|
+SET @nbr_dup := (SELECT COUNT(*) FROM (
|
|
|
+ SELECT tenant_id, domain_code, NbrType
|
|
|
+ FROM NbrControl
|
|
|
+ GROUP BY tenant_id, domain_code, NbrType
|
|
|
+ HAVING COUNT(*) > 1) t);
|
|
|
+
|
|
|
+SELECT @ipk_dup AS itempack_intratenant_dup, @nbr_dup AS nbrcontrol_intratenant_dup;
|
|
|
+-- 期望均为 0;若非 0,本脚本不会创建新唯一索引(下方 ADD 被守卫跳过),需人工治理后重跑。
|
|
|
+
|
|
|
+-- ========== 二、ItemPackMaster:旧全局唯一索引 -> 租户内唯一索引 ==========
|
|
|
+-- 2.1 删除旧唯一索引 uk_ItemPackMaster_domain_itemnum(存在才删)
|
|
|
+SET @has_old_ipk := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
|
|
|
+ AND INDEX_NAME = 'uk_ItemPackMaster_domain_itemnum');
|
|
|
+SET @sql := IF(@has_old_ipk > 0,
|
|
|
+ 'ALTER TABLE ItemPackMaster DROP INDEX uk_ItemPackMaster_domain_itemnum', 'DO 0');
|
|
|
+PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
|
|
+
|
|
|
+-- 2.2 创建新租户内唯一索引(不存在且无同租户冲突才建)
|
|
|
+SET @has_new_ipk := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
|
|
|
+ AND INDEX_NAME = 'uk_ItemPackMaster_tenant_domain_itemnum');
|
|
|
+SET @sql := IF(@has_new_ipk = 0 AND @ipk_dup = 0,
|
|
|
+ 'ALTER TABLE ItemPackMaster ADD UNIQUE INDEX uk_ItemPackMaster_tenant_domain_itemnum (tenant_id, `Domain`, ItemNum)',
|
|
|
+ 'DO 0');
|
|
|
+PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
|
|
+
|
|
|
+-- 2.3 处理隐藏的第二个全局唯一索引 ix_ItemPackMaster (Domain, ItemNum)。
|
|
|
+-- 该索引 NON_UNIQUE=0(唯一),与被删的 uk 同样强制全局唯一,会继续阻断跨租户相同 (Domain, ItemNum),必须一并处理。
|
|
|
+-- 方案:删除其“唯一”版本,重建为“非唯一”同名索引(保留 (Domain, ItemNum) 查询性能,但不再强制全局唯一)。
|
|
|
+SET @ix_unique := (SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
|
|
|
+ AND INDEX_NAME = 'ix_ItemPackMaster' AND NON_UNIQUE = 0);
|
|
|
+SET @sql := IF(@ix_unique > 0,
|
|
|
+ 'ALTER TABLE ItemPackMaster DROP INDEX ix_ItemPackMaster', 'DO 0');
|
|
|
+PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
|
|
+
|
|
|
+SET @has_ix := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ItemPackMaster'
|
|
|
+ AND INDEX_NAME = 'ix_ItemPackMaster');
|
|
|
+SET @sql := IF(@has_ix = 0,
|
|
|
+ 'ALTER TABLE ItemPackMaster ADD INDEX ix_ItemPackMaster (`Domain`, ItemNum)', 'DO 0');
|
|
|
+PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
|
|
+
|
|
|
+-- ========== 三、NbrControl:旧全局唯一索引 -> 租户内唯一索引 ==========
|
|
|
+-- 3.1 删除旧唯一索引 uk_NbrControl_domain_nbrtype(存在才删)
|
|
|
+SET @has_old_nbr := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'NbrControl'
|
|
|
+ AND INDEX_NAME = 'uk_NbrControl_domain_nbrtype');
|
|
|
+SET @sql := IF(@has_old_nbr > 0,
|
|
|
+ 'ALTER TABLE NbrControl DROP INDEX uk_NbrControl_domain_nbrtype', 'DO 0');
|
|
|
+PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
|
|
+
|
|
|
+-- 3.2 创建新租户内唯一索引(不存在且无同租户冲突才建)
|
|
|
+SET @has_new_nbr := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
|
|
|
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'NbrControl'
|
|
|
+ AND INDEX_NAME = 'uk_NbrControl_tenant_domain_nbrtype');
|
|
|
+SET @sql := IF(@has_new_nbr = 0 AND @nbr_dup = 0,
|
|
|
+ 'ALTER TABLE NbrControl ADD UNIQUE INDEX uk_NbrControl_tenant_domain_nbrtype (tenant_id, domain_code, NbrType)',
|
|
|
+ 'DO 0');
|
|
|
+PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
|
|
+
|
|
|
+-- ========== 四、执行后验证 ==========
|
|
|
+SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE, SEQ_IN_INDEX, COLUMN_NAME
|
|
|
+FROM information_schema.STATISTICS
|
|
|
+WHERE TABLE_SCHEMA = DATABASE()
|
|
|
+ AND TABLE_NAME IN ('ItemPackMaster', 'NbrControl')
|
|
|
+ AND INDEX_NAME <> 'PRIMARY'
|
|
|
+ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
|
|
|
+-- 期望:ItemPackMaster 仅 uk_ItemPackMaster_tenant_domain_itemnum(唯一) + ix_ItemPackMaster(NON_UNIQUE=1);
|
|
|
+-- NbrControl 仅 uk_NbrControl_tenant_domain_nbrtype(唯一)。均无全局唯一 (Domain,ItemNum)/(domain_code,NbrType)。
|
|
|
+
|
|
|
+-- ============================================================================
|
|
|
+-- 回滚 SQL(如需恢复到全局唯一索引,历史数据无跨租户同键冲突时可执行):
|
|
|
+-- ALTER TABLE ItemPackMaster DROP INDEX uk_ItemPackMaster_tenant_domain_itemnum;
|
|
|
+-- ALTER TABLE ItemPackMaster DROP INDEX ix_ItemPackMaster;
|
|
|
+-- ALTER TABLE ItemPackMaster ADD UNIQUE INDEX uk_ItemPackMaster_domain_itemnum (`Domain`, ItemNum);
|
|
|
+-- ALTER TABLE ItemPackMaster ADD UNIQUE INDEX ix_ItemPackMaster (`Domain`, ItemNum);
|
|
|
+-- ALTER TABLE NbrControl DROP INDEX uk_NbrControl_tenant_domain_nbrtype;
|
|
|
+-- ALTER TABLE NbrControl ADD UNIQUE INDEX uk_NbrControl_domain_nbrtype (domain_code, NbrType);
|
|
|
+-- 注意:回滚前必须确认不存在跨租户相同业务键,否则回滚 ADD 会因重复而失败(这正是本次改造要放开的场景)。
|
|
|
+-- ============================================================================
|