資料庫同步的隱藏陷阱:Link Table 的重要性

問題現象:登入成功卻被拒於門外 最近在 Staging 環境遇到一個詭異的問題:使用者登入成功,拿到了有效的 JWT Token,但存取任何需要認證的 API 都回傳 401 Unauthorized。 # 登入成功,拿到 token POST /api/auth/local → 200 OK { "jwt": "eyJhbGc...xxxxx...your-jwt-token", "user": { "id": 1001, "email": "user@example.com" } } # 但存取個人資料失敗 GET /api/users/me → 401 Unauthorized Token 驗證通過、使用者存在、帳號未被封鎖。問題到底在哪? 根本原因:遺失的 Link Table 經過一番追查,發現問題出在資料庫同步時漏掉了關聯表(Link Table)。 什麼是 Link Table? 在關聯式資料庫中,多對多關係需要透過中間表來建立。這個中間表就是 Link Table(也稱為 Junction Table、Join Table、或 Pivot Table)。 使用者與角色的關係: 一個使用者可以有多個角色(User → Roles) 一個角色可以分配給多個使用者(Role → Users) 這是典型的多對多關係 各種 ORM 的 Link Table 命名 不同框架的 Link Table 命名慣例不同,但概念完全相同: ORM/Framework Link Table 範例 備註 Django user_groups, user_permissions 使用 _ 連接 Laravel role_user, permission_role 字母順序排列 TypeORM user_roles_role 較長的命名 Prisma _UserToRole 以 _ 開頭 Sequelize UserRoles 駝峰命名 問題的本質:資料不完整 當我們同步資料庫時,通常會注意主要的資料表: ...

January 20, 2026 · 4 分鐘 · Peter

刪了 52 萬筆資料,為什麼硬碟空間沒變小?

「奇怪,我明明刪了 52 萬筆資料,為什麼資料表還是 207MB?」 這是我今天在清理資料庫時遇到的真實情況。如果你也曾經困惑過這個問題,這篇文章會告訴你背後的原因。 事情是這樣的 專案的 user_notifications 資料表累積了幾十萬筆推播通知記錄,佔用了 207MB 空間。為了控制資料庫大小,我寫了一個 cron job 來清理超過 7 天的舊資料: // 刪除 7 天前的通知 const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); await strapi.db.query('api::user-notification.user-notification').deleteMany({ where: { createdAt: { $lt: sevenDaysAgo.toISOString() } }, }); 執行結果很漂亮: [Cleanup] Successfully deleted 521604 old user notifications 刪除了 521,604 筆!只剩下約 2 萬筆近期資料。 但當我打開 DBeaver 檢查時… 207MB?資料都刪了,空間怎麼沒變? 為什麼會這樣?理解 PostgreSQL 的 MVCC 這不是 bug,而是 PostgreSQL 的設計特性。 DELETE 不是真的刪除 PostgreSQL 使用 MVCC(Multi-Version Concurrency Control) 來處理並發交易。當你執行 DELETE 時,PostgreSQL 不會真的把資料從磁碟上移除,而是: 將該行標記為「已刪除」(稱為 dead tuple) 保留原始資料,直到沒有任何交易需要參照它 新的查詢看不到這些行,但它們仍佔用磁碟空間 為什麼要這樣設計? 效能考量:標記刪除比實際移除資料快非常多 並發安全:其他正在執行的 transaction 可能還需要看到舊版本 ACID 保證:確保 transaction isolation 不處理會怎樣? ...

December 31, 2025 · 3 分鐘 · Peter