fix(mining-admin): use Prisma relationMode=prisma for CDC sync tables

Switch to Prisma's "prisma" relation mode to handle CDC event ordering issues.
This mode emulates foreign key relations at the Prisma Client layer instead of
creating database-level FK constraints, which is the recommended approach for
CDC scenarios where event arrival order cannot be guaranteed.

Reference: https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/relation-mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-12 08:33:21 -08:00
parent ea789f7fec
commit 01ff873264
2 changed files with 37 additions and 10 deletions

View File

@ -1,7 +1,9 @@
-- ============================================================================= -- =============================================================================
-- 移除 CDC 同步表的外键约束 -- 切换到 Prisma relationMode = "prisma"
-- 移除数据库层的外键约束,改由 Prisma Client 在应用层模拟外键关系
--
-- 原因CDC 事件异步到达,顺序不可控,子表记录可能在父表记录之前到达 -- 原因CDC 事件异步到达,顺序不可控,子表记录可能在父表记录之前到达
-- 参考https://estuary.dev/blog/cdc-done-correctly/ -- 参考https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/relation-mode
-- ============================================================================= -- =============================================================================
-- 移除 synced_contribution_accounts 表的外键约束 -- 移除 synced_contribution_accounts 表的外键约束
@ -19,3 +21,16 @@ DROP CONSTRAINT IF EXISTS "synced_mining_accounts_accountSequence_fkey";
-- 移除 synced_trading_accounts 表的外键约束 -- 移除 synced_trading_accounts 表的外键约束
ALTER TABLE "synced_trading_accounts" ALTER TABLE "synced_trading_accounts"
DROP CONSTRAINT IF EXISTS "synced_trading_accounts_accountSequence_fkey"; DROP CONSTRAINT IF EXISTS "synced_trading_accounts_accountSequence_fkey";
-- 为 accountSequence 字段添加索引以优化 JOIN 性能(如果不存在)
CREATE INDEX IF NOT EXISTS "synced_contribution_accounts_accountSequence_idx"
ON "synced_contribution_accounts"("accountSequence");
CREATE INDEX IF NOT EXISTS "synced_referrals_accountSequence_idx"
ON "synced_referrals"("accountSequence");
CREATE INDEX IF NOT EXISTS "synced_mining_accounts_accountSequence_idx"
ON "synced_mining_accounts"("accountSequence");
CREATE INDEX IF NOT EXISTS "synced_trading_accounts_accountSequence_idx"
ON "synced_trading_accounts"("accountSequence");

View File

@ -3,8 +3,9 @@ generator client {
} }
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
relationMode = "prisma" // CDC场景Prisma层模拟外键关系数据库不创建FK约束
} }
// ============================================================================= // =============================================================================
@ -163,8 +164,11 @@ model SyncedUser {
syncedAt DateTime @default(now()) syncedAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
// CDC 同步表不设置外键约束,因为事件到达顺序不可控 // 关联同步表
// 使用 accountSequence 作为逻辑关联键,查询时用 LEFT JOIN contributionAccount SyncedContributionAccount?
miningAccount SyncedMiningAccount?
tradingAccount SyncedTradingAccount?
referral SyncedReferral?
@@index([phone]) @@index([phone])
@@index([status]) @@index([status])
@ -179,7 +183,7 @@ model SyncedUser {
model SyncedContributionAccount { model SyncedContributionAccount {
id String @id @default(uuid()) id String @id @default(uuid())
accountSequence String @unique // 逻辑关联 SyncedUser.accountSequence无外键约束 accountSequence String @unique
personalContribution Decimal @db.Decimal(30, 8) @default(0) personalContribution Decimal @db.Decimal(30, 8) @default(0)
teamLevelContribution Decimal @db.Decimal(30, 8) @default(0) teamLevelContribution Decimal @db.Decimal(30, 8) @default(0)
teamBonusContribution Decimal @db.Decimal(30, 8) @default(0) teamBonusContribution Decimal @db.Decimal(30, 8) @default(0)
@ -192,6 +196,8 @@ model SyncedContributionAccount {
syncedAt DateTime @default(now()) syncedAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
user SyncedUser @relation(fields: [accountSequence], references: [accountSequence])
@@map("synced_contribution_accounts") @@map("synced_contribution_accounts")
} }
@ -201,7 +207,7 @@ model SyncedContributionAccount {
model SyncedReferral { model SyncedReferral {
id String @id @default(uuid()) id String @id @default(uuid())
accountSequence String @unique // 逻辑关联 SyncedUser.accountSequence无外键约束 accountSequence String @unique
referrerAccountSequence String? // 推荐人账户序列号 referrerAccountSequence String? // 推荐人账户序列号
referrerUserId BigInt? // 1.0 的 referrer_id referrerUserId BigInt? // 1.0 的 referrer_id
originalUserId BigInt? // 1.0 的 user_id originalUserId BigInt? // 1.0 的 user_id
@ -210,6 +216,8 @@ model SyncedReferral {
syncedAt DateTime @default(now()) syncedAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
user SyncedUser @relation(fields: [accountSequence], references: [accountSequence])
@@index([referrerAccountSequence]) @@index([referrerAccountSequence])
@@index([depth]) @@index([depth])
@@map("synced_referrals") @@map("synced_referrals")
@ -301,7 +309,7 @@ model SyncedNetworkProgress {
model SyncedMiningAccount { model SyncedMiningAccount {
id String @id @default(uuid()) id String @id @default(uuid())
accountSequence String @unique // 逻辑关联 SyncedUser.accountSequence无外键约束 accountSequence String @unique
totalMined Decimal @db.Decimal(30, 8) @default(0) totalMined Decimal @db.Decimal(30, 8) @default(0)
availableBalance Decimal @db.Decimal(30, 8) @default(0) availableBalance Decimal @db.Decimal(30, 8) @default(0)
frozenBalance Decimal @db.Decimal(30, 8) @default(0) frozenBalance Decimal @db.Decimal(30, 8) @default(0)
@ -309,6 +317,8 @@ model SyncedMiningAccount {
syncedAt DateTime @default(now()) syncedAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
user SyncedUser @relation(fields: [accountSequence], references: [accountSequence])
@@map("synced_mining_accounts") @@map("synced_mining_accounts")
} }
@ -318,7 +328,7 @@ model SyncedMiningAccount {
model SyncedTradingAccount { model SyncedTradingAccount {
id String @id @default(uuid()) id String @id @default(uuid())
accountSequence String @unique // 逻辑关联 SyncedUser.accountSequence无外键约束 accountSequence String @unique
shareBalance Decimal @db.Decimal(30, 8) @default(0) shareBalance Decimal @db.Decimal(30, 8) @default(0)
cashBalance Decimal @db.Decimal(30, 8) @default(0) cashBalance Decimal @db.Decimal(30, 8) @default(0)
frozenShares Decimal @db.Decimal(30, 8) @default(0) frozenShares Decimal @db.Decimal(30, 8) @default(0)
@ -328,6 +338,8 @@ model SyncedTradingAccount {
syncedAt DateTime @default(now()) syncedAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
user SyncedUser @relation(fields: [accountSequence], references: [accountSequence])
@@map("synced_trading_accounts") @@map("synced_trading_accounts")
} }