Просмотр исходного кода

docs(乐收赢): 补一份只读的商户号槽位诊断 SQL

线上那笔「删了商户重新新增 ⇒ 500」的现场读数一直缺着(用户裁定「先不碰库」,
DB 只有用户能执行)。这份脚本把「谁占着槽位」摊开,全部是 SELECT,不改任何数据。

五条:① 租户 2 的全部登记行(租户级排前)② 谁占着租户级槽位
③ 全局重号的 mch_no ④ 每租户槽位占用总览 ⑤ 线上三条唯一索引的真实定义。

★ 不用 psql 元命令(第一版写了 `\echo`,用户的客户端执行不了 —— 那不是 psql)。
  纯 SQL,图形客户端 / psycopg2 都能整段跑,也能一条条跑。
★ 中文列别名一律加双引号,去掉客户端编码这一层不确定性
  (本仓无未加引号的中文别名先例可照抄)。
alphaH 2 недель назад
Родитель
Сommit
f6b774af8a
1 измененных файлов с 88 добавлено и 0 удалено
  1. 88 0
      java/src/main/resources/db/lsy-merchant-slot-diag.sql

+ 88 - 0
java/src/main/resources/db/lsy-merchant-slot-diag.sql

@@ -0,0 +1,88 @@
+-- 乐收赢商户号槽位诊断(**只读**)2026-09-30
+-- 用途:线上报过
+--   duplicate key value violates unique constraint "uk_lsy_merchant_tenant_level"
+--   详细:Key (tenant_id)=(2) already exists.
+-- 本脚本把「谁占着槽位」摊开给人看 —— 全部是 SELECT,不改任何数据。
+--
+-- 【怎么跑】五条 SELECT 相互独立,可以整段跑,也可以一条一条跑。
+--   没有 psql 元命令(\echo 之类),图形客户端 / psycopg2 都能直接执行。
+--
+-- 背景:pay_lsy_merchant 上三条唯一约束都由数据库把关:
+--   uk_lsy_merchant_tenant_level  UNIQUE(tenant_id) WHERE enterprise_id IS NULL   ← 偏索引
+--   uk_lsy_merchant_tenant_ent    UNIQUE(tenant_id, enterprise_id)
+--   uk_lsy_merchant_mch_no        UNIQUE(mch_no)                                  ← 全局,不带 tenant_id
+-- (代码侧的预检只负责把冲突说清楚,不负责挡住 —— 挡住的是这三条。见
+--   LsyMerchantService#rejectUniqueConflicts 的 javadoc。)
+--
+-- 想查别的租户:把下面所有的 2 换成目标 tenant_id 即可。
+
+
+-- ============================================================================
+-- ① 租户 2 的全部商户号登记行(租户级排在前面)
+--    看这一条就够判断「谁占着槽位、我删掉的是哪一行」
+-- ============================================================================
+SELECT id,
+       tenant_id,
+       enterprise_id,
+       CASE WHEN enterprise_id IS NULL THEN '★ 租户级(占着 uk_lsy_merchant_tenant_level)'
+            ELSE '企业级'
+       END AS "槽位",
+       mch_no,
+       shop_no,
+       merchant_name,
+       review_status,
+       status,
+       created_time,
+       updated_time
+  FROM pay_lsy_merchant
+ WHERE tenant_id = 2
+ ORDER BY (enterprise_id IS NOT NULL), id;
+
+
+-- ============================================================================
+-- ② 谁占着租户 2 的租户级槽位(应当恰好 0 或 1 行)
+--    新增「企业 ID 留空」的行时,撞的就是这一行
+-- ============================================================================
+SELECT id, tenant_id, mch_no, merchant_name, review_status, status, created_time
+  FROM pay_lsy_merchant
+ WHERE tenant_id = 2
+   AND enterprise_id IS NULL;
+
+
+-- ============================================================================
+-- ③ 全局重号的商户号(同一 mch_no 出现在多行)
+--    只要这里出现一行,新增那个号必撞 uk_lsy_merchant_mch_no
+--    期望:0 行
+-- ============================================================================
+SELECT mch_no,
+       count(*)                                AS "行数",
+       array_agg(tenant_id ORDER BY tenant_id) AS "租户们",
+       array_agg(id ORDER BY id)               AS "ids"
+  FROM pay_lsy_merchant
+ GROUP BY mch_no
+HAVING count(*) > 1;
+
+
+-- ============================================================================
+-- ④ 每租户槽位占用总览
+--    「租户级行数」只要 > 1 就是线上数据已经违反约束(不可能);= 1 表示该租户
+--    已经不能再新增「企业 ID 留空」的行
+-- ============================================================================
+SELECT tenant_id,
+       count(*) FILTER (WHERE enterprise_id IS NULL)     AS "租户级行数",
+       count(*) FILTER (WHERE enterprise_id IS NOT NULL) AS "企业级行数",
+       count(*)                                          AS "合计"
+  FROM pay_lsy_merchant
+ GROUP BY tenant_id
+ ORDER BY tenant_id;
+
+
+-- ============================================================================
+-- ⑤ 线上三条唯一索引的真实定义(核对与代码的假设是否一致)
+--    期望恰好 3 行,且第 1 条带 WHERE ... IS NULL
+-- ============================================================================
+SELECT indexname, indexdef
+  FROM pg_indexes
+ WHERE tablename = 'pay_lsy_merchant'
+   AND indexdef ILIKE '%unique%'
+ ORDER BY indexname;