-- 乐收赢去企业化(租户账号级)2026-09-22 -- 手工执行: psycopg2 连接 payment_platform_java 库 -- -- 背景: 乐收赢与原有企业逻辑完全不相干,商户映射应按**租户账号**寻址。 -- 本脚本让 pay_lsy_merchant 支持「租户级商户」—— enterprise_id 为空的那一行。 -- 不新增列、不新增表;订单表 pay_lsy_order.enterprise_id 建表时已可空,本脚本不碰它。 -- -- ⚠ 第一条**不可逆**: NOT NULL 加不回来,除非先把所有 enterprise_id IS NULL 的行清掉 -- 或补上企业 ID。执行前请确认可以接受。 -- -- ⚠ **先执行本脚本,再部署代码**(顺序不能反)。配套的代码提交删掉了 save 里 -- 「企业 ID 不能为空」的 400 校验,而列还是 NOT NULL 的话,留空保存会从 400 -- 变成 500(NOT NULL 违约落进 GlobalExceptionHandler 兜底)。 -- 本脚本执行前,现有行为一切照旧(老调用方都带 enterprise_id)。 -- 1. 允许租户级商户 ALTER TABLE pay_lsy_merchant ALTER COLUMN enterprise_id DROP NOT NULL; -- 2. 租户级商户的唯一性 -- 现有唯一索引 uk_lsy_merchant_tenant_ent 建在 (tenant_id, enterprise_id) 上, -- 而 PostgreSQL 唯一索引里 **NULL 互不相等** ⇒ 缺了下面这条局部索引, -- 同一个租户可以插入任意多行租户级商户,数据库一声不吭, -- 之后 requireByTenant 的 LIMIT 1 会随机返回其中一行。 -- -- 存量数据核对(2026-09-22 只读实测,非推断):全表 1 行 -- id=1, tenant_id=2, enterprise_id='2088780645490698', mch_no='Y52211090004' -- 该行 enterprise_id **非空**,落在局部索引的谓词之外 ⇒ 不冲突,无需清理。 -- 将来若真出现同租户多行租户级商户(只可能来自索引建成之前的窗口),先清理再建索引: -- DELETE FROM pay_lsy_merchant a USING pay_lsy_merchant b -- WHERE a.id < b.id AND a.tenant_id = b.tenant_id -- AND a.enterprise_id IS NULL AND b.enterprise_id IS NULL; CREATE UNIQUE INDEX IF NOT EXISTS uk_lsy_merchant_tenant_level ON pay_lsy_merchant (tenant_id) WHERE enterprise_id IS NULL; -- 3. 只读核对(执行后可手工跑一遍确认) -- SELECT is_nullable FROM information_schema.columns -- WHERE table_name='pay_lsy_merchant' AND column_name='enterprise_id'; -- 期望 YES -- SELECT indexname FROM pg_indexes WHERE tablename='pay_lsy_merchant'; -- 期望含上述两条