用非關係數據庫替換軟體產品中的“糟糕”設計的關係數據庫?
編輯: 這個問題是關於如何處理整個系統設計中出現的許多問題,這使得系統的某些部分偏離了通用標準。例如,使用自己的程式碼管理業務模型中的所有內容,甚至包括關係完整性。這給數據庫和持久層帶來了糟糕設計的味道,將其用作“轉儲某些東西並以某種方式再次取出”的地方,而不是結構化儲存。我問了這個問題,因為在我看來,NoSQL 文件儲存就像一個選項,可以將已經沒有模式(或非常鬆散的模式)的數據庫移動到預設情況下沒有模式的數據庫。另外,我必須指出,儘管這裡描述了一些缺陷,但整個系統一點也不差。此外,一些問題(例如版本控制)正在解決或已經實施。
想一想你看到的一個軟體系統,它基於經典的關係數據庫(SQL Server、Oracle)、NHibernate 作為對象關係映射器(ORM)、頂部的業務邏輯模型層和大量模組(幾百個) ,主要是基於 .NET 的服務和一些 Web 服務(有客戶端,每個系統/客戶最多約 100 個,公司網路,非公共)。操作方式主要是 OLTP,寫入/CUD 訪問是工作量的重要組成部分。生產性數據庫通常約為 10GB,但總是遠低於 100GB(因此沒有“大數據”)。它確實工作得很好,但對我來說,數據庫和 ORM 實現有幾種反模式(對於關係數據庫)的味道。也許這些實現可以更好地使用另一種數據庫——面向文件的(“NoSQL”)或記憶體數據庫。
- 省略了許多關係數據庫和支持 ORM 功能:表被高度非規範化,外鍵關係失去或不可能,例如,由於元數據表引用不同的主表,列如
IdInTable INT, OwnerTable INT
. NHibernate 幾乎沒有映射對象關係(並且通常存在與它不適合的表結構的問題)。相反,這些是在業務邏輯中實現的(有時會導致孤立的子對像或低效的數據庫訪問,見下文)。- 低於基礎的非規範化:越來越多地使用非第一個 NF 數據:帶有 XML 的 nclob/nvarchar(max) 列、逗號分隔列表或複合數值列(例如,任務類型 123 的 123、10123、40123,但不同的模組配置由 0,1,4 * 10000 標識)。前兩個包含數據庫相關、邏輯“外鍵”和數據模型相關值,例如
<UserType>AdminUser</UserType>
(用 進行檢查LIKE '%...%'
)。這主要是由於許多快速發布、短命和定制的值不應該進入主模式或者更容易通過 XML 值實現。- 非 2nd NF 數據,包括被觸發器複製的表內容、後續儲存過程或應用程序到其他表中。例如,將表列值複製到“垂直”元數據表,這再次復製到元數據的“水平”或“透視”表示(每個元數據類型一個列),因為某些應用程序只能使用元數據或水平元數據. 使用“垃圾箱結構”的頻繁請求(將從各種來源收集的數據轉儲到一個 nclob/nvarchar(max)“垃圾箱”列,並讓應用程序搜尋它,而不是許多不同的來源)。
- 業務邏輯模型和應用程序中的“單對象病”: 單個對象的迭代和立即載入/保存:業務層主要對單個對象使用 Load/Save() 方法,很少使用基於批量/集合的操作。一個常見的工作是通過 SQL 或其 NHibernate 表示獲取對象 ID,然後遍歷所有檢索到的 Id,並以
foreach (oneId in Ids) { myObjects.Add( BizModel.GetMyObjectById(oneId) ); }
. 這與所有元數據、依賴對象集合等一起,是典型的 SELECT N+1 情況。此外,NHibernate 的大多數記憶體、持久性無知和組合操作已被強制禁用:載入一個對象顯式呼叫SELECT FROM MyObject WHERE Id=:id
以防止使用記憶體或延遲執行,但從目前 DB 行獲取一個新對象。MyObject.Save()
實施以強制立即插入/更新:session.Save(...)
其次是立即.Flush()
。整個事情都使用 NHibernate 微會話:載入的對象立即從會話上下文中取出並保存在新會話中(防止那些“奇怪”的、對 DB 中未保存對象的不希望更改)。通過 NHibernate 的持久性無知和對象關係似乎是不可取的,以保持對每個對象的狀態的控制。NHibernate 實際上被認為是一個映射器(一行到一個對象),而不是一個用於關係數據庫訪問的複雜工具。還有關於使用“快速”微 ORM 代替 NHibernate 的爭論,這將使 SELECT N+1 查詢閃電般快速地實現到對像中,但當然對 N+1 本身沒有任何作用。- 一個重要的要求是讓everything 與所有東西一起工作,因為每次更改都發布所有模組太多了:新模組必須與舊數據庫版本一起使用,其中某些列和表不會退出,以及舊模組必須仍然使用新的數據庫版本,添加列等。這會導致新列(如果不可為空)具有預設值,並且舊表/列被遺棄了很長時間,仍然在數據模型中,因為刪除可能會使舊模組崩潰。另一個後果是不願意添加新的表/列,一旦發布就很難擺脫。相反,首選 XML(在文本列中)和類似的非規範化內容或全域元數據表中的屬性值。
- 許多模組僅接收單個對象的任務,這在可能的基於集合的方法中沒有問題,因為如果需要,集合/批量數據訪問方法也可以處理單個對象/行。另一方面,有 web 伺服器、維護和後台服務,它們同時處理許多對象,需要業務邏輯並且在目前的單對象方式下執行效率非常低(使用原生 SQL 的 web 服務,或者,新的基於 Lucene 的搜尋引擎搜尋所需對象的 ID,但會一一檢索完整的模型對象)。
想像一下,你試圖改變這一點。一開始,您不了解 NHibernate 及其工作原理,但後來您想出瞭如何使數據訪問適應它的實際能力,並避免不必要的數據庫操作:在 NHibernate 中映射關係,保持會話和事務打開幾個對像操作,執行設置/批量操作,按照您多年前學習的方式規範化數據庫,添加外鍵、視圖,可能還有物化視圖。但是你不斷被拒絕,理由是:“沒有人願意為此買單”,“數據庫可以處理它,不管應用程序有多“糟糕””,以及簡單地說*“它可以工作”*. 磁碟空間、記憶體、CPU 能力和網路資源都很便宜;重構數據訪問會更加昂貴。很可能,首選程式碼程序員的物件導向方法,而不是 DB 程序員的基於集合的方法(包括它對 ORM 實現的強制執行)。如果系統能以目前的方式充分執行,如果系統可以快 10 倍甚至 100 倍,那又有什麼關係呢?無論如何不要關心SELECT N+1,今天的數據庫可以處理它!那隻會是鍍金!當數據庫增長到 TB 時,情況可能會有所不同,但現在還不行。
所以,也許,在“NoSQL”或“NewSQL”領域有一個解決方案。可以以快速有效的方式從數據庫中獲取對象並將它們儲存在數據庫中。即使在單個對像中有許多查詢,而不是設置方法,只要它是本地數據庫,沒有長距離延遲。看起來目前系統將關係數據庫用作擴展的持久主記憶體,而所有 IT 的“石器時代遺留物”,例如手動創建和維護表和索引,或將對象映射到關係表,只是添加了一個巨大的高架。
我的想法是:
“NoSQL”文件數據庫是一件好事,因為:
- 該文件主要包含整個對像圖以及依賴項、元數據和屬於它的所有內容,因此不需要額外的數據庫查詢,從而避免或大大減少了 SELECT N+1 問題。
- 在文件中,通過其父對象包含的依賴對象(嵌套在 XML 或 JSON 表示中),存在隱含的“關係完整性”。
- 在多個不同的文件中,數據庫中沒有關係,這些關係完全由業務邏輯維護(現在經常這樣做,但對於經典的關係數據庫設計來說是錯誤的)。
- 它通常沒有固定的模式,因此更容易處理不斷變化的資料結構。對象可以忽略以後添加的屬性,或者使用預設值填充舊版本數據中的缺失值。
- 具有外部/變數/無模式的後續數據可以不間斷地集成(與將 XML 儲存在關係文本列中相反)。
- 許多文件數據庫集成了自動索引或搜尋引擎。
仍然需要最低限度的自動化數據完整性,尤其是多對象事務。
記憶體中的關係數據庫,或任何專注於快速訪問而無需每次(寫入)操作都訪問慢速硬碟驅動器的數據庫,將有助於提高速度,但仍然基本上依賴於硬關係模式,這在很大程度上已被省略,並且似乎對利益相關者不利。
有經驗的人能告訴我我的假設是否正確嗎?
歡迎來到數據庫地獄!
NoSQL 通常被認為是這些類型應用程序的解決方案。但是,您的問題顯然是程序員不知道他們在做什麼。此外,您的管理層似乎害怕改變,或者不願意做出您正確列出並且可能解決根本原因的艱難決定。在這裡用另一個數據庫替換數據庫是否有幫助是高度不確定的。不怪工具,怪工匠。
雖然 NoSQL 中的每個對像都可以包含整個對像圖可能是真的,但您仍然不知道如何以適當的完整性使所有數據保持最新。順便說一句,這正是 3NF 所要解決的問題,它在所有設計合理的系統中都能很好地解決這個問題。一旦你維護了冗餘數據(無論是在 SQL 還是 NoSQL 中),你就會遇到各種關於鎖定和閂鎖以保持同步的問題。
只有當模式變化非常頻繁時,NoSQL 的動態模式特性才有優勢。從你對系統的描述來看,好像不是這樣,因為沒人敢碰程式碼。此外,關係數據庫中的模式更改並不難。NoSQL 倡導的動態模式優勢在很大程度上是一種解決方案,它尋找一個對於知道自己在做什麼的 DBA 來說並不存在的問題。
即使您可以從 NoSQL 中獲得一些理論上的好處,您仍然必須考慮如何從您所在的位置遷移而不會遇到您目前面臨的所有相同的爭論。在不接受激進變革的環境中,比較這兩種方法的成本可能會令人望而卻步。
我完全理解您為什麼要在這裡尋找解決方案。然而,在 IT 領域有很多很棒的工作。生命太短暫,無法應對這種類型的系統和組織。除非你需要耐心和忍耐痛苦的教訓,否則就繼續前進吧。
永遠不要碰正在執行的系統,尤其是不要為了讓它“更好”而不增加價值。如果您添加了一些功能並且在同一個類中可以進行一些重構,那就去做,徹底測試它。但絕不只是為了改變它。
在大多數情況下,NoSQL 也會讀取和儲存整個文件,就像您描述的休眠映射一樣,因此甚至可能不會提高性能。它將所有數據保存在記憶體中,因此當您在它們上有索引時,10 GB 的記憶體將更多。
在 SQL 中,可以將多個表的 Table+ID 拆分為每個可以為空的表的一個外鍵。
NoSQL 很棒,但您應該使用它(和真實數據)一段時間來了解其局限性。對於這個案例,我看不到一個比另一個有真正的好處。而且由於 SQL 已經存在,因此最好通過使用您提到的批量操作來提高性能。在那裡你應該真正衡量改進有多大。對於大多數使用者來說,等待 15 毫秒而不是 30 毫秒可能並不有趣。