簡體   English   中英

如何為用戶定義的字段設計數據庫?

[英]How to design a database for User Defined Fields?

我的要求是:

  • 需要能夠動態添加任何數據類型的用戶定義字段
  • 需要能夠快速查詢UDF
  • 需要能夠基於數據類型對UDF進行計算
  • 需要能夠根據數據類型對UDF進行排序

其他信息:

  • 我主要是在尋找表現
  • 有幾百萬條Master記錄可以附加UDF數據
  • 當我上次檢查時,我們當前的數據庫中有超過50mil的UDF記錄
  • 大多數情況下,UDF僅附加到幾千個Master記錄中,而不是全部記錄
  • UDF未加入或用作鍵。 它們只是用於查詢或報告的數據

選項:

  1. 使用StringValue1,StringValue2創建一個大表... IntValue1,IntValue2,...等我討厭這個想法,但如果有人能告訴我它比其他想法更好,為什么會考慮它。

  2. 創建一個動態表,根據需要按需添加新列。 我也不喜歡這個想法,因為除非你索引每一列,否則我覺得性能會很慢。

  3. 創建一個包含UDFName,UDFDataType和Value的表。 添加新的UDF時,生成一個View,它只提取該數據並將其解析為指定的任何類型。 不符合解析標准的項返回NULL。

  4. 創建多個UDF表,每種數據類型一個。 所以我們有UDFStrings,UDFDates等的表。可能和#2一樣,並且只要添加新字段就自動生成View

  5. XML數據類型? 我之前沒有使用過這些,但已經看過它們了。 不確定他們是否會給我我想要的結果,尤其是性能。

  6. 別的什么?

如果性能是主要考慮因素,我會選擇#6 ...每個UDF一個表(實際上,這是#2的變體)。 此答案專門針對此情況以及所描述的數據分布和訪問模式的描述而定制。

優點:

  1. 因為您指示某些UDF具有整個數據集的一小部分的值,所以單獨的表將為您提供最佳性能,因為該表將僅支持UDF所需的大小。 相關指數也是如此。

  2. 您還可以通過限制必須為聚合或其他轉換處理的數據量來提高速度。 將數據拆分為多個表允許您對UDF數據執行一些聚合和其他統計分析,然后通過外鍵將該結果連接到主表以獲取非聚合屬性。

  3. 您可以使用反映實際數據的表/列名稱。

  4. 您可以完全控制使用數據類型,檢查約束,默認值等來定義數據域。 不要低估即時數據類型轉換帶來的性能損失。 此類約束還有助於RDBMS查詢優化器開發更有效的計划。

  5. 如果您需要使用外鍵,內置的聲明性參照完整性很少會通過基於觸發器或應用程序級別約束實施來執行。

缺點:

  1. 這可能會創建很多表。 實施模式分離和/或命名約定可以緩解這種情況。

  2. 操作UDF定義和管理需要更多的應用程序代碼。 我希望這仍然比原始選項1,3和4更少的代碼。

其他考慮因素:

  1. 如果有關於UDF分組有意義的數據性質,應該鼓勵這樣做。 這樣,這些數據元素可以組合成一個表。 例如,假設您有顏色,大小和成本的UDF。 數據的趨勢是這些數據的大多數實例看起來像

      'red', 'large', 45.03 

    而不是

      NULL, 'medium', NULL 

    在這種情況下,通過組合1個表中的3列,您不會產生明顯的速度損失,因為很少的值將為NULL並且您避免再生成2個表,當您需要訪問所有3列時,需要的連接數減少2個。

  2. 如果您從人口密集且經常使用的UDF訪問性能牆,則應考慮將其包含在主表中。

  3. 邏輯表設計可以帶您到某個點,但是當記錄計數變得非常大時,您還應該開始查看您選擇的RDBMS提供的表分區選項。

我已經 了很多關於這個問題的文章 最常見的解決方案是Entity-Attribute-Value反模式,它類似於您在選項#3中描述的內容。 避免這種設計像瘟疫一樣

當我需要真正動態的自定義字段時,我使用此解決方案的方法是將它們存儲在一個XML blob中,這樣我就可以隨時添加新字段。 但為了使其快速,還需要為每個字段創建需要搜索或排序的附加表(每個字段不需要一個表 - 每個可搜索字段只有一個表)。 這有時被稱為倒排索引設計。

您可以在這里閱讀2009年關於此解決方案的有趣文章: http//backchannel.org/blog/friendfeed-schemaless-mysql

或者,您可以使用面向文檔的數據庫,期望每個文檔都有自定義字段。 我選擇索爾

我很可能會創建一個具有以下結構的表:

  • varchar名稱
  • varchar類型
  • 十進制NumberValue
  • varchar StringValue
  • date DateValue

確切的課程類型取決於您的需求(當然還有您正在使用的dbms)。 您還可以將NumberValue(十進制)字段用於int和booleans。 您可能還需要其他類型。

您需要一些指向擁有該值的主記錄的鏈接。 為每個主表創建一個用戶字段表並添加一個簡單的外鍵可能是最簡單,最快速的。 這樣,您可以輕松快速地按用戶字段過濾主記錄。

您可能希望獲得某種元數據信息。 所以你最終會得到以下結果:

表UdfMetaData

  • int id
  • varchar名稱
  • varchar類型

表MasterUdfValues

  • int Master_FK
  • int MetaData_FK
  • 十進制NumberValue
  • varchar StringValue
  • date DateValue

無論你做什么,我都不會動態地更改表結構。 這是一場維護噩夢。 我也不會使用XML結構,它們太慢了。

這聽起來像一個非關系解決方案可能更好地解決的問題,如MongoDB或CouchDB。

它們都允許動態架構擴展,同時允許您維護所尋求的元組完整性。

我同意Bill Karwin的觀點,EAV模型對你來說並不是一種高效的方法。 在關系系統中使用名稱 - 值對本質上並不壞,但只有在名稱 - 值對完整的元組信息時才能正常工作。 使用它時強制您在運行時動態重建表,各種事情開始變得困難。 查詢成為樞軸維護的練習或強制您將元組重建推送到對象層。

如果沒有在對象層中嵌入架構規則,則無法確定null值或缺失值是有效條目還是缺少條目。

您失去了有效管理架構的能力。 100個字符的varchar是“value”字段的正確類型嗎? 200字? 它應該是nvarchar嗎? 這可能是一個艱難的權衡,最后你必須對你的集合的動態性質進行人為限制。 像“你只能有x個用戶定義的字段,每個字符只能是y字符長”。

使用面向文檔的解決方案(如MongoDB或CouchDB),可以在單個元組中維護與用戶關聯的所有屬性。 因為連接不是問題,所以生活很幸福,因為盡管有炒作,但這兩者都不能很好地連接。 您的用戶可以根據需要(或允許)定義盡可能多的屬性,這些屬性的長度在您達到大約4MB之前難以管理。

如果您有需要ACID級完整性的數據,您可以考慮拆分解決方案,其中高完整性數據存儲在關系數據庫中,動態數據存儲在非關系存儲中。

即使您提供了添加自定義列的用戶,也不一定是查詢這些列的效果會很好。 查詢設計中有許多方面可以使它們表現良好,其中最重要的是對應該存儲的內容的正確規范。 因此,從根本上說,您是否希望允許用戶在不考慮規范的情況下創建模式,並能夠快速從該模式中獲取信息? 如果是這樣,那么任何此類解決方案都可以很好地擴展,尤其是如果您想允許用戶對數據進行數值分析時,這是不可能的。

選項1

IMO這種方法為您提供了模式,而不了解模式意味着什么是災難的處方和報表設計者的噩夢。 即,您必須擁有元數據才能知道哪些列存儲了哪些數據。 如果元數據搞砸了,它就有可能阻塞你的數據。 此外,它可以很容易地將錯誤的數據放入錯誤的列中。 (“什么?String1包含修道院的名稱?我認為這是Chalie Sheen最喜歡的葯物。”)

選項3,4,5

IMO,要求2,3和4消除了EAV的任何變化。 如果你需要對這些數據進行查詢,排序或計算,那么EAV就是Cthulhu的夢想,也是你的開發團隊和DBA的噩夢。 EAV將在性能方面造成瓶頸,無法為您提供快速獲取所需信息所需的數據完整性。 查詢將很快轉向交叉表Gordian結。

備選方案2,6

這確實留下了一個選擇:收集規范,然后構建模式。

如果客戶希望在他們希望存儲的數據上獲得最佳性能,那么他們需要經歷與開發人員合作的過程,以了解他們的需求,以便盡可能高效地存儲。 它仍然可以存儲在與其余表分開的表中,其代碼可以根據表的模式動態構建表單。 如果您有一個允許列上的擴展屬性的數據庫,您甚至可以使用它們來幫助表單構建器使用漂亮的標簽,工具提示等。所有必要的是添加模式。 無論哪種方式,要有效地構建和運行報告,都需要正確存儲數據。 如果有問題的數據有很多空值,則某些數據庫可以存儲該類型的信息。 例如,SQL Server 2008有一個名為Sparse Columns的功能,專門用於包含大量空值的數據。

如果這只是一袋沒有進行分析,過濾或排序的數據,我會說EAV的一些變化可能會成功。 但是,根據您的要求,最有效的解決方案是獲得正確的規范,即使您將這些新列存儲在單獨的表中並在這些表中動態構建表單也是如此。

稀疏列

  1. 創建多個UDF表,每種數據類型一個。 所以我們有UDFStrings,UDFDates等的表。可能和#2一樣,並且只要添加新字段就自動生成View

根據我的研究,基於數據類型的多個表格無法幫助您提高性能。 特別是如果您有批量數據,例如20K或25K記錄,包含50+ UDF。 表現最糟糕。

您應該使用包含多個列的單個表,例如:

varchar Name
varchar Type
decimal NumberValue
varchar StringValue
date DateValue

這是一個有問題的情況,並且沒有一個解決方案顯得“正確”。 然而,在簡單性和性能方面,選項1可能是最好的。

這也是一些商業企業應用程序中使用的解決方案。

編輯

現在可用的另一個選項,但當問題是原始問題時不存在(或者至少不成熟)是在數據庫中使用json字段。

許多關系數據庫現在支持基於json的字段(可以包括子字段的動態列表)並允許查詢它們

postgress

MySQL的

我有過經驗或者1,3和4,它們都會變得混亂,因為不清楚數據是什么,或者通過某種軟分類將數據分解為動態記錄類型而變得非常復雜。

我很想嘗試使用XML,你應該能夠對xml的內容強制執行模式來檢查數據輸入等,這將有助於保存不同的UDF數據集。 在較新版本的SQL Server中,您可以對XML字段建立索引,這應該有助於提高性能。 (例如,參見http://blogs.technet.com/b/josebda/archive/2009/03/23/sql-server-2008-xml-indexing.aspx

如果您使用的是SQL Server,請不要忽略sqlvariant類型。 這很快,應該做你的工作。 其他數據庫可能有類似的東西。

出於性能原因,XML數據類型不太好。 如果你在服務器上進行計算,那么你經常需要反序列化這些。

選項1聽起來很糟糕,看起來很邋and,但性能方面可能是你最好的選擇。 我之前創建了名為Field00-Field99的列的表,因為你無法擊敗性能。 您可能也需要考慮您的INSERT性能,在這種情況下,這也是您需要的。 如果您希望它看起來整潔,您可以隨時在此表上創建視圖!

SharePoint使用選項1並具有合理的性能。

我過去使用這些選項都沒有成功(選項6?:))。

我為用戶創建了一個模型(存儲為xml並通過自定義建模工具公開),並從模型生成的表和視圖中創建模型,以使用用戶定義的數據表連接基表。 因此,每種類型都有一個包含核心數據的基表和一個包含用戶定義字段的用戶表。

以文檔為例:典型字段將是名稱,類型,日期,作者等。這將在核心表中。 然后用戶可以使用自己的字段定義自己的特殊文檔類型,例如contract_end_date,renewal_clause,blah blah blah。 對於該用戶定義的文檔,將在公共主鍵上連接核心文檔表xcontract表(因此xcontracts主鍵在核心表的主鍵上也是外來的)。 然后我會生成一個視圖來包裝這兩個表。 查詢時的性能很快。 其他業務規則也可以嵌入到視圖中。 這對我來說非常好。

我們的數據庫為SaaS應用程序(幫助台軟件)提供支持,用戶擁有超過7k的“自定義字段”。 我們使用組合方法:

  1. (EntityID, FieldID, Value)表用於搜索數據
  2. entities表中的JSON字段,包含用於顯示數據的所有實體值。 (這樣你不需要一百萬個JOIN來獲取值值)。

您可以進一步拆分#1以獲得“每個數據類型的表”, 如此答案所示,這樣您甚至可以索引UDF。

PS幾個詞來捍衛“實體 - 屬性 - 價值”的方法,每個人都在抨擊。 幾十年來,我們已經使用了沒有#2的#1,它工作得很好。 有時這是一個商業決策。 你有時間重寫你的應用程序並重新設計數據庫,或者你可以在雲服務器上花幾塊錢,這些天真的很便宜嗎? 順便說一句,當我們使用#1方法時,我們的數據庫擁有數百萬個實體,由數以千計的用戶訪問,而16GB雙核數據庫服務器做得很好(在AWS上真的是“r3”vm) 。

在評論中,我看到你說UDF字段是轉儲未被用戶正確映射的導入數據。

也許另一個選擇是跟蹤每個用戶所做的UDF的數量,並強制他們重用字段,說他們可以使用6個(或其他一些同等隨機限制)自定義字段tops。

當您遇到像這樣的數據庫結構問題時,通常最好回到應用程序的基本設計(在您的情況下導入系統)並對其進行一些限制。

現在我要做的是選項4(編輯),添加了一個用戶鏈接:

general_data_table
id
...


udfs_linked_table
id
general_data_id
udf_id


udfs_table
id
name
type
owner_id --> Use this to filter for the current user and limit their UDFs
string_link_id --> link table for string fields
int_link_id
type_link_id

現在確保創建視圖以優化性能並使索引正確。 這種標准化水平使DB占用空間更小,但您的應用程序更復雜。

我建議#4,因為這種類型的系統用於Magento ,這是一個高度認可的電子商務CMS平台。 使用單個表格使用fieldIdlabel列定義自定義字段。 然后,為每種數據類型分別使用表,並在每個表中都有一個索引,該索引按fieldId和數據類型列進行索引。 然后,在您的查詢中,使用以下內容:

SELECT *
FROM FieldValues_Text
WHERE fieldId IN (
    SELECT fieldId FROM Fields WHERE userId=@userId
)
AND value LIKE '%' + @search + '%'

在我看來,這將確保用戶定義類型的最佳性能。

根據我的經驗,我參與了幾個Magento網站,每月為數百萬用戶提供服務,托管數千種具有自定義產品屬性的產品,數據庫可以輕松處理工作量,甚至用於報告。

對於報告,您可以使用PIVOTFields標簽值轉換為列名,然后將每個數據類型表的查詢結果轉換為這些透視列。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM