從多個表中連線資料是一個複雜的話題。主要有兩種策略:資料庫級別連線和應用程式級別連線。Prisma ORM 提供這兩種選項。在本文中,您將瞭解這兩種策略之間的權衡,以便為您的用例選擇最佳策略。
簡介
為什麼 Prisma ORM 最初只支援應用程式級連線?
Prisma ORM 最初只提供應用程式級連線策略。這有幾個原因
- 能夠在不同的資料庫引擎中使用相同的連線策略,確保可移植性。
- 透過將昂貴的操作轉移到應用程式層(應用程式層比資料庫更容易且成本更低),提高了整個系統的可擴充套件性。
- 在雲原生和無伺服器用例中,應用程式和資料庫在同一雲區域共置是常態,額外的資料庫往返開銷可以忽略不計。
- 具有數百萬行和深度巢狀查詢的高效能用例,使用額外的功能如過濾和分頁。
- 查詢除錯簡單,因為每個查詢只針對一個表(無需理解和除錯複雜的查詢計劃)。
- 透過將資料庫的職責限制為直接操作,並防止查詢效能因資料庫的查詢規劃器和執行時最佳化而出現顯著變化,從而實現可預測的效能。
2024年2月,Prisma ORM 增加了智慧資料庫級連線作為替代策略,使用 LATERAL 連線和 JSON 聚合等現代資料庫功能。當應用程式和資料庫伺服器彼此相距較遠,且額外網路往返的成本對查詢的總體延遲有顯著影響時,這種方法是有利的。
最終,這些方法各有優缺點,我們將在本文的其餘部分闡明這些,以幫助您為關係查詢選擇最佳策略。
巢狀物件與外部索引鍵關係
在深入探討連線的複雜性之前,讓我們快速回顧一下,瞭解“連線資料”這一主題的全部內容。
作為一名開發人員,您可能習慣於使用巢狀物件,它們看起來與此類似
在此示例中,“物件層級”如下:post → author → profile。
這種巢狀結構是大多數具有物件概念的程式語言中資料表示的方式。
然而,如果您之前使用過 SQL 資料庫,您可能會意識到相關資料在那裡的表示方式不同,即以 扁平 (或 規範化)的方式表示。透過這種方法,實體之間的關係透過 外部索引鍵 表示,這些外部索引鍵指定了表之間的引用。
這是這兩種方法的視覺表示

這是一個巨大的差異,不僅在於資料在 物理 磁碟和記憶體中的佈局方式,還在於 心智模型 和資料推理方式。
“連線”資料意味著什麼?
連線資料的過程是指將 SQL 資料庫中的扁平佈局資料轉換為應用程式開發人員可在其應用程式中使用的巢狀結構。
這可以在以下兩個地方之一發生
- 在 資料庫中:單個 SQL 查詢傳送到資料庫。該查詢使用
JOIN關鍵字(或可能是關聯子查詢),讓資料庫執行跨多個表的連線並返回巢狀結構。有多種執行此連線的方法,我們將在下一節中探討。 - 在 應用程式中:多個查詢傳送到資料庫。每個查詢只訪問一個表,然後查詢結果在應用程式層進行連線。
資料庫級連線有其優點,但如果變得過於複雜,也會有一些缺點。因此,根據模式、資料集和查詢複雜性等因素,任一方法都可能比另一種方法更適合特定用例。請繼續閱讀以瞭解詳細資訊!
三種 JOIN 策略:樸素、智慧和應用程式級 JOIN
從宏觀上看,可以應用三種不同的連線策略:資料庫級別的“樸素”和“智慧” JOIN,以及“應用程式級” JOIN。讓我們透過以下模式逐一考察它們
樸素的資料庫級 JOIN 導致資料冗餘
樸素的資料庫級 JOIN 指的是未採取任何額外最佳化措施的 JOIN 操作。這類 JOIN 通常會因多種原因而導致效能不佳,讓我們來探討一下!
例如,這是一個簡單的 LEFT JOIN 操作,開發人員可能會樸素地編寫它來連線 users 表和 post 表中的資料
資料庫返回的結果可能類似於此

您注意到什麼了嗎?user_name 列的資料中存在大量重複。
現在,讓我們將 comments 新增到查詢中
現在更糟糕了!不僅 user_name 重複,post_title 也重複了

資料的冗餘帶來了幾個負面影響
- 透過網路傳送的資料量(不必要的)增加,消耗網路頻寬並增加整體查詢延遲。
- 應用程式層需要做額外的工作才能獲得所需的巢狀物件
- 對冗餘資料進行去重
- 重建資料記錄之間的關係
此外,這種操作會給資料庫帶來高 CPU 開銷,因為它將查詢所有三個表並執行自己的記憶體對映,將資料連線成一個結果集。
以上仍然是一個相對簡單的例子。想象一下,如果您使用更多的 JOIN 和更深層次的巢狀執行此操作。達到一定級別後,資料庫將放棄最佳化查詢計劃,轉而為每個表執行全表掃描,然後使用自己的 CPU 在記憶體中拼接資料。這會很快變得昂貴!
資料庫 CPU 和記憶體的擴充套件比應用程式級 CPU 和記憶體複雜得多(且成本更高)。因此,改善這種情況的一種方法是利用應用伺服器的 CPU 來完成資料連線的工作,這引出了下一種方法:“應用程式級連線”。
應用程式級連線簡單高效,但有網路成本
另一種執行這些樸素的資料庫級連線的替代方法是在應用程式層連線資料。在這種情況下,開發人員會編寫三個不同的查詢,分別傳送到資料庫。一旦資料庫返回查詢結果,開發人員就可以應用自己的業務邏輯來連線資料。
在 TypeScript 中,一個示例如下(使用普通的 Postgres 驅動程式,如 node-postgres)
這種方法有幾個優點
- 資料庫將為每個查詢生成高度最佳化的執行計劃,並且幾乎不執行 CPU 工作,因為它只是從單個表中返回資料。
- 透過網路傳送的資料針對應用程式的資料需求進行了最佳化(並且不會像樸素的資料庫級連線策略那樣出現相同的冗餘問題)。
- 由於大部分對映和連線工作現在都在應用程式本身完成,資料庫伺服器有更多資源來處理更復雜的查詢。
透過將 CPU 成本從資料庫轉移到應用程式層,這種方法增強了整個系統的橫向可擴充套件性。
在 O'Reilly 出版社的《高效能 MySQL》一書中,這種應用程式級連線技術被稱為連線分解:“許多高效能網站使用 連線分解。您可以透過執行多個單表查詢而不是多表連線來分解連線,然後在應用程式中執行連線。”
然而,一個主要缺點是它需要多次往返資料庫。如果應用程式伺服器和資料庫彼此相距較遠,這是一個重要的因素,會對效能產生嚴重影響,並可能使此策略不可行。但如果資料庫和應用程式託管在同一區域,網路開銷通常可以忽略不計,並且這種方法總體上可能更具效能優勢。
智慧資料庫級連線解決冗餘問題
樸素的資料庫級連線幾乎從來都不是從資料庫中檢索相關資料的最佳方式,但這是否意味著您的資料庫永遠不應該負責連線資料?當然不是!
過去幾年,資料庫引擎變得非常強大,並不斷改進它們最佳化查詢的方式。為了使資料庫能夠生成最優的查詢計劃,最重要的是它能理解查詢的意圖。
這有兩個不同的因素
- 使用 JSON 聚合等技術減少冗餘
- 使用現代資料庫功能,如 PostgreSQL 中的
LATERAL連線(或 MySQL 中的關聯子查詢),這些功能包含查詢複雜性
使用上述相同的模式示例,一個很好的表示方式是
這樣的查詢會產生以下結果

這些資料與關於樸素資料庫級連線部分的相似,不同之處在於
- 它不再包含冗餘
- 帖子已以 JSON 結構格式化
雖然這個查詢可能比樸素策略產生格式更好的結果,但它也變得冗長和複雜。請記住,我們仍在討論一個相對簡單的整體場景:連線三個表,而沒有大多數實際應用程式所處理的額外因素(例如過濾和分頁)。
Prisma ORM 中 JOIN 策略的演進
當 Prisma ORM 於2021 年首次釋出時,它對其所有關係查詢都實現了應用程式級連線策略。
當應用伺服器和資料庫彼此靠近時,這種策略非常有效,有助於跨資料庫引擎的可移植性,並提高整個系統的可擴充套件性(因為應用程式層的 CPU 比資料庫層的 CPU 更容易且成本更低)。
儘管應用程式級連線方法對大多數開發人員都很有用,但當應用程式伺服器和資料庫無法彼此靠近託管時,有時會導致問題,並且額外的往返會負面影響整體查詢效能。
這就是為什麼我們在一年前添加了智慧資料庫級連線作為替代方案,這樣開發人員就可以選擇最適合其特定用例的連線策略。
能夠使用資料庫級連線一直是 Prisma ORM 最受歡迎的功能請求之一,並且自發布預覽版以來一直受到我們社群的好評。一旦此功能普遍可用,資料庫級連線將成為 Prisma ORM 用於其關係查詢的預設連線策略。
社群反饋是幫助我們優先處理 Prisma ORM 改進工作的主要驅動力之一。
結論
找出從資料庫中連線多個表資料最高效的方法是一個複雜的話題。在本文中,我們探討了三種不同的方法:資料庫層面的樸素連線和智慧連線,以及應用程式級連線。
樸素的資料庫級連線會給資料庫伺服器帶來高 CPU 成本,並因不必要地傳輸冗餘資料而導致網路開銷。
應用程式級連線因其簡單性以及在資料庫層面執行成本低廉,可能更適合許多場景。使用此策略的系統通常也更容易且成本更低地進行擴充套件。
最後,智慧資料庫級連線解決了冗餘問題,可以返回為應用程式開發人員量身定製的巢狀結構資料,並且總體上更有可能被資料庫引擎更好地最佳化。
不要錯過下一篇文章!
訂閱 Prisma 新聞郵件