diff --git a/content/tw/ch1.md b/content/tw/ch1.md index 7b19ba02..e6775581 100644 --- a/content/tw/ch1.md +++ b/content/tw/ch1.md @@ -164,10 +164,10 @@ Apache Hive、Spark SQL、Presto 和 Trino 是這種方法的例子。 與事務型系統和分析型系統之間的區別相關,本書還區分了 **權威記錄系統** 和 **派生資料系統**。這些術語很有用,因為它們可以幫助你澄清資料在系統中的流動: 權威記錄系統 -: 權威記錄系統,也稱為 **權威資料來源**,儲存某些資料的權威或 **規範** 版本。當新資料進入時,例如作為使用者輸入,它首先寫入這裡。每個事實只表示一次(表示通常是 **規範化** 的;參見["規範化、反規範化和連線"](/tw/ch3#sec_datamodels_normalization))。如果另一個系統與權威記錄系統之間存在任何差異,那麼權威記錄系統中的值(根據定義)是正確的。 +: 權威記錄系統,也稱為 **權威資料來源**,儲存某些資料的權威或 **規範** 版本。當新資料進入時,例如作為使用者輸入,它首先寫入這裡。每個事實只表示一次(表示通常是 **正規化** 的;參見["正規化、反正規化和連線"](/tw/ch3#sec_datamodels_normalization))。如果另一個系統與權威記錄系統之間存在任何差異,那麼權威記錄系統中的值(根據定義)是正確的。 派生資料系統 -: 派生系統中的資料是從另一個系統獲取一些現有資料並以某種方式轉換或處理它的結果。如果你丟失了派生資料,你可以從原始源重新建立它。一個經典的例子是快取:如果存在,可以從快取提供資料,但如果快取不包含你需要的內容,你可以回退到底層資料庫。反規範化值、索引、物化檢視、轉換的資料表示和在資料集上訓練的模型也屬於這一類別。 +: 派生系統中的資料是從另一個系統獲取一些現有資料並以某種方式轉換或處理它的結果。如果你丟失了派生資料,你可以從原始源重新建立它。一個經典的例子是快取:如果存在,可以從快取提供資料,但如果快取不包含你需要的內容,你可以回退到底層資料庫。反正規化值、索引、物化檢視、轉換的資料表示和在資料集上訓練的模型也屬於這一類別。 從技術上講,派生資料是 **冗餘** 的,因為它複製了現有資訊。然而,它通常對於在讀取查詢上獲得良好效能至關重要。你可以從單個源派生幾個不同的資料集,使你能夠從不同的"視角"檢視資料。 diff --git a/content/tw/ch11.md b/content/tw/ch11.md index 8415f93b..8678a3d2 100644 --- a/content/tw/ch11.md +++ b/content/tw/ch11.md @@ -279,7 +279,7 @@ Hadoop 的各種高階工具(如 Pig 【30】、Hive 【31】、Cascading 【3 我們在 [第二章](/tw/ch2) 中討論了資料模型和查詢語言的連線,但是我們還沒有深入探討連線是如何實現的。現在是我們再次撿起這條線索的時候了。 -在許多資料集中,一條記錄與另一條記錄存在關聯是很常見的:關係模型中的 **外部索引鍵**,文件模型中的 **文件引用** 或圖模型中的 **邊**。當你需要同時訪問這一關聯的兩側(持有引用的記錄與被引用的記錄)時,連線就是必須的。正如 [第二章](/tw/ch2) 所討論的,非規範化可以減少對連線的需求,但通常無法將其完全移除 [^v]。 +在許多資料集中,一條記錄與另一條記錄存在關聯是很常見的:關係模型中的 **外部索引鍵**,文件模型中的 **文件引用** 或圖模型中的 **邊**。當你需要同時訪問這一關聯的兩側(持有引用的記錄與被引用的記錄)時,連線就是必須的。正如 [第二章](/tw/ch2) 所討論的,反正規化可以減少對連線的需求,但通常無法將其完全移除 [^v]。 [^v]: 我們在本書中討論的連線通常是等值連線,即最常見的連線型別,其中記錄透過與其他記錄在特定欄位(例如 ID)中具有 **相同值** 相關聯。有些資料庫支援更通用的連線型別,例如使用小於運算子而不是等號運算子,但是我們沒有地方來講這些東西。 diff --git a/content/tw/ch12.md b/content/tw/ch12.md index 132e0f23..12af2335 100644 --- a/content/tw/ch12.md +++ b/content/tw/ch12.md @@ -382,9 +382,9 @@ $$ 如果你不需要擔心如何查詢與訪問資料,那麼儲存資料通常是非常簡單的。模式設計、索引和儲存引擎的許多複雜性,都是希望支援某些特定查詢和訪問模式的結果(請參閱 [第三章](/tw/ch3))。出於這個原因,透過將資料寫入的形式與讀取形式相分離,並允許幾個不同的讀取檢視,你能獲得很大的靈活性。這個想法有時被稱為 **命令查詢責任分離(command query responsibility segregation, CQRS)**【42,58,59】。 -資料庫和模式設計的傳統方法是基於這樣一種謬論,資料必須以與查詢相同的形式寫入。如果可以將資料從針對寫入最佳化的事件日誌轉換為針對讀取最佳化的應用狀態,那麼有關規範化和非規範化的爭論就變得無關緊要了(請參閱 “[多對一和多對多的關係](/tw/ch2#多對一和多對多的關係)”):在針對讀取最佳化的檢視中對資料進行非規範化是完全合理的,因為翻譯過程提供了使其與事件日誌保持一致的機制。 +資料庫和模式設計的傳統方法是基於這樣一種謬論,資料必須以與查詢相同的形式寫入。如果可以將資料從針對寫入最佳化的事件日誌轉換為針對讀取最佳化的應用狀態,那麼有關正規化和反正規化的爭論就變得無關緊要了(請參閱 “[多對一和多對多的關係](/tw/ch2#多對一和多對多的關係)”):在針對讀取最佳化的檢視中對資料進行反正規化是完全合理的,因為翻譯過程提供了使其與事件日誌保持一致的機制。 -在 “[描述負載](/tw/ch1#描述負載)” 中,我們討論了推特主頁時間線,它是特定使用者關注的人群所發推特的快取(類似郵箱)。這是 **針對讀取最佳化的狀態** 的又一個例子:主頁時間線是高度非規範化的,因為你的推文與你所有粉絲的時間線都構成了重複。然而,扇出服務保持了這種重複狀態與新推特以及新關注關係的同步,從而保證了重複的可管理性。 +在 “[描述負載](/tw/ch1#描述負載)” 中,我們討論了推特主頁時間線,它是特定使用者關注的人群所發推特的快取(類似郵箱)。這是 **針對讀取最佳化的狀態** 的又一個例子:主頁時間線是高度反正規化的,因為你的推文與你所有粉絲的時間線都構成了重複。然而,扇出服務保持了這種重複狀態與新推特以及新關注關係的同步,從而保證了重複的可管理性。 #### 併發控制 diff --git a/content/tw/ch13.md b/content/tw/ch13.md index c32c36fe..c43a1287 100644 --- a/content/tw/ch13.md +++ b/content/tw/ch13.md @@ -38,7 +38,7 @@ breadcrumbs: false 例如,為了處理任意關鍵詞的搜尋查詢,將 OLTP 資料庫與全文檢索索引整合在一起是很常見的需求。儘管一些資料庫(例如 PostgreSQL)包含了全文索引功能,對於簡單的應用完全夠了【1】,但更複雜的搜尋能力就需要專業的資訊檢索工具了。相反的是,搜尋索引通常不適合作為持久的記錄系統,因此許多應用需要組合這兩種不同的工具以滿足所有需求。 -我們在 “[保持系統同步](/tw/ch12#保持系統同步)” 中接觸過整合資料系統的問題。隨著資料不同表示形式的增加,整合問題變得越來越困難。除了資料庫和搜尋索引之外,也許你需要在分析系統(資料倉庫,或批處理和流處理系統)中維護資料副本;維護從原始資料中派生的快取,或反規範化的資料版本;將資料灌入機器學習、分類、排名或推薦系統中;或者基於資料變更傳送通知。 +我們在 “[保持系統同步](/tw/ch12#保持系統同步)” 中接觸過整合資料系統的問題。隨著資料不同表示形式的增加,整合問題變得越來越困難。除了資料庫和搜尋索引之外,也許你需要在分析系統(資料倉庫,或批處理和流處理系統)中維護資料副本;維護從原始資料中派生的快取,或反正規化的資料版本;將資料灌入機器學習、分類、排名或推薦系統中;或者基於資料變更傳送通知。 令人驚訝的是,我經常看到軟體工程師做出這樣的陳述:“根據我的經驗,99% 的人只需要 X” 或者 “...... 不需要 X”(對於各種各樣的 X)。我認為這種陳述更像是發言人自己的經驗,而不是技術實際上的實用性。可能對資料執行的操作,其範圍極其寬廣。某人認為雞肋而毫無意義的功能可能是別人的核心需求。當你拉高視角,並考慮跨越整個組織範圍的資料流時,資料整合的需求往往就會變得明顯起來。 diff --git a/content/tw/ch3.md b/content/tw/ch3.md index de5ce98c..0f1a8035 100644 --- a/content/tw/ch3.md +++ b/content/tw/ch3.md @@ -142,7 +142,7 @@ NoSQL 運動的一個持久影響是 **文件模型** 的流行,它通常將 -------- -### 規範化、反規範化與連線 {#sec_datamodels_normalization} +### 正規化、反正規化與連線 {#sec_datamodels_normalization} 在前一節的 [示例 3-1](/tw/ch3#fig_obama_json) 中,`region_id` 被給出為 ID,而不是純文字字串 `"Washington, DC, United States"`。為什麼? @@ -154,11 +154,11 @@ NoSQL 運動的一個持久影響是 **文件模型** 的流行,它通常將 * 本地化支援 —— 當網站被翻譯成其他語言時,標準化列表可以被本地化,因此區域可以用檢視者的語言顯示 * 更好的搜尋 —— 例如,搜尋美國東海岸的人可以匹配此個人資料,因為區域列表可以編碼華盛頓位於東海岸的事實(這從字串 `"Washington, DC"` 中並不明顯) -無論你儲存 ID 還是文字字串,這都是 *規範化* 的問題。當你使用 ID 時,你的資料更加規範化:對人類有意義的資訊(如文字 *Washington, DC*)只儲存在一個地方,所有引用它的地方都使用 ID(它只在資料庫中有意義)。當你直接儲存文字時,你在使用它的每條記錄中都複製了對人類有意義的資訊;這種表示是 *反規範化* 的。 +無論你儲存 ID 還是文字字串,這都是 *正規化* 的問題。當你使用 ID 時,你的資料更加正規化:對人類有意義的資訊(如文字 *Washington, DC*)只儲存在一個地方,所有引用它的地方都使用 ID(它只在資料庫中有意義)。當你直接儲存文字時,你在使用它的每條記錄中都複製了對人類有意義的資訊;這種表示是 *反正規化* 的。 使用 ID 的優勢在於,因為它對人類沒有意義,所以永遠不需要更改:即使它標識的資訊發生變化,ID 也可以保持不變。任何對人類有意義的東西將來某個時候可能需要更改 —— 如果該資訊被複制,所有冗餘副本都需要更新。這需要更多的程式碼、更多的寫操作、更多的磁碟空間,並且存在不一致的風險(其中一些資訊副本被更新但其他的沒有)。 -規範化表示的缺點是,每次要顯示包含 ID 的記錄時,都必須進行額外的查詢以將 ID 解析為人類可讀的內容。在關係資料模型中,這是使用 *連線* 完成的,例如: +正規化表示的缺點是,每次要顯示包含 ID 的記錄時,都必須進行額外的查詢以將 ID 解析為人類可讀的內容。在關係資料模型中,這是使用 *連線* 完成的,例如: ```sql SELECT users.*, regions.region_name @@ -167,7 +167,7 @@ SELECT users.*, regions.region_name WHERE users.id = 251; ``` -文件資料庫可以儲存規範化和反規範化的資料,但它們通常與反規範化相關聯 —— 部分是因為 JSON 資料模型使得儲存額外的反規範化欄位變得容易,部分是因為許多文件資料庫中對連線的弱支援使得規範化不方便。一些文件資料庫根本不支援連線,因此你必須在應用程式程式碼中執行它們 —— 也就是說,你首先獲取包含 ID 的文件,然後執行第二個查詢將該 ID 解析為另一個文件。在 MongoDB 中,也可以使用聚合管道中的 `$lookup` 運算子執行連線: +文件資料庫可以儲存正規化和反正規化的資料,但它們通常與反正規化相關聯 —— 部分是因為 JSON 資料模型使得儲存額外的反正規化欄位變得容易,部分是因為許多文件資料庫中對連線的弱支援使得正規化不方便。一些文件資料庫根本不支援連線,因此你必須在應用程式程式碼中執行它們 —— 也就是說,你首先獲取包含 ID 的文件,然後執行第二個查詢將該 ID 解析為另一個文件。在 MongoDB 中,也可以使用聚合管道中的 `$lookup` 運算子執行連線: ```mongodb-json db.users.aggregate([ @@ -181,24 +181,24 @@ db.users.aggregate([ ]) ``` -#### 規範化的權衡 {#trade-offs-of-normalization} +#### 正規化的權衡 {#trade-offs-of-normalization} -在簡歷示例中,雖然 `region_id` 欄位是對標準化區域集的引用,但 `organization`(人工作的公司或政府)和 `school_name`(他們學習的地方)的名稱只是字串。這種表示是反規範化的:許多人可能在同一家公司工作過,但沒有 ID 將他們聯絡起來。 +在簡歷示例中,雖然 `region_id` 欄位是對標準化區域集的引用,但 `organization`(人工作的公司或政府)和 `school_name`(他們學習的地方)的名稱只是字串。這種表示是反正規化的:許多人可能在同一家公司工作過,但沒有 ID 將他們聯絡起來。 也許組織和學校應該是實體,個人資料應該引用它們的 ID 而不是它們的名稱?引用區域 ID 的相同論點也適用於此。例如,假設我們想在他們的名字之外包括學校或公司的標誌: -* 在反規範化表示中,我們會在每個人的個人資料中包含標誌的影像 URL;這使得 JSON 文件自包含,但如果我們需要更改標誌,就會產生麻煩,因為我們現在需要找到舊 URL 的所有出現並更新它們 [^9]。 -* 在規範化表示中,我們將建立一個代表組織或學校的實體,並在該實體上儲存其名稱、標誌 URL 以及可能的其他屬性(描述、新聞提要等)一次。然後,每個提到該組織的簡歷都會簡單地引用其 ID,更新標誌很容易。 +* 在反正規化表示中,我們會在每個人的個人資料中包含標誌的影像 URL;這使得 JSON 文件自包含,但如果我們需要更改標誌,就會產生麻煩,因為我們現在需要找到舊 URL 的所有出現並更新它們 [^9]。 +* 在正規化表示中,我們將建立一個代表組織或學校的實體,並在該實體上儲存其名稱、標誌 URL 以及可能的其他屬性(描述、新聞提要等)一次。然後,每個提到該組織的簡歷都會簡單地引用其 ID,更新標誌很容易。 -作為一般原則,規範化資料通常寫入更快(因為只有一個副本),但查詢更慢(因為它需要連線);反規範化資料通常讀取更快(連線更少),但寫入更昂貴(更多副本要更新,使用更多磁碟空間)。你可能會發現將反規範化視為派生資料的一種形式很有幫助(["記錄系統和派生資料"](/tw/ch1#sec_introduction_derived)),因為你需要設定一個過程來更新資料的冗餘副本。 +作為一般原則,正規化資料通常寫入更快(因為只有一個副本),但查詢更慢(因為它需要連線);反正規化資料通常讀取更快(連線更少),但寫入更昂貴(更多副本要更新,使用更多磁碟空間)。你可能會發現將反正規化視為派生資料的一種形式很有幫助(["記錄系統和派生資料"](/tw/ch1#sec_introduction_derived)),因為你需要設定一個過程來更新資料的冗餘副本。 除了執行所有這些更新的成本之外,如果程序在進行更新的過程中崩潰,你還需要考慮資料庫的一致性。提供原子事務的資料庫(參見 ["原子性"](/tw/ch8#sec_transactions_acid_atomicity))使保持一致性變得更容易,但並非所有資料庫都在多個文件之間提供原子性。透過流處理確保一致性也是可能的,我們將在 [待補充連結] 中討論。 -規範化往往更適合 OLTP 系統,其中讀取和更新都需要快速;分析系統通常使用反規範化資料表現更好,因為它們批次執行更新,只讀查詢的效能是主要關注點。此外,在中小規模的系統中,規範化資料模型通常是最好的,因為你不必擔心保持資料的多個副本相互一致,執行連線的成本是可以接受的。然而,在非常大規模的系統中,連線的成本可能會成為問題。 +正規化往往更適合 OLTP 系統,其中讀取和更新都需要快速;分析系統通常使用反正規化資料表現更好,因為它們批次執行更新,只讀查詢的效能是主要關注點。此外,在中小規模的系統中,正規化資料模型通常是最好的,因為你不必擔心保持資料的多個副本相互一致,執行連線的成本是可以接受的。然而,在非常大規模的系統中,連線的成本可能會成為問題。 -#### 社交網路案例研究中的反規範化 {#denormalization-in-the-social-networking-case-study} +#### 社交網路案例研究中的反正規化 {#denormalization-in-the-social-networking-case-study} -在 ["案例研究:社交網路主頁時間線"](/tw/ch2#sec_introduction_twitter) 中,我們比較了規範化表示([圖 2-1](/tw/ch2#fig_twitter_relational))和反規範化表示(預計算的物化時間線):這裡,`posts` 和 `follows` 之間的連線太昂貴了,物化時間線是該連線結果的快取。將新帖子插入關注者時間線的扇出過程是我們保持反規範化表示一致的方式。 +在 ["案例研究:社交網路主頁時間線"](/tw/ch2#sec_introduction_twitter) 中,我們比較了正規化表示([圖 2-1](/tw/ch2#fig_twitter_relational))和反正規化表示(預計算的物化時間線):這裡,`posts` 和 `follows` 之間的連線太昂貴了,物化時間線是該連線結果的快取。將新帖子插入關注者時間線的扇出過程是我們保持反正規化表示一致的方式。 然而,X(前 Twitter)的物化時間線實現實際上並不儲存每個帖子的實際文字:每個條目實際上只儲存帖子 ID、釋出者的使用者 ID,以及一些額外的資訊來識別轉發和回覆 [^11]。換句話說,它是(大約)以下查詢的預計算結果: @@ -213,11 +213,11 @@ SELECT posts.id, posts.sender_id 這意味著每當讀取時間線時,服務仍然需要執行兩個連線:透過 ID 查詢帖子以獲取實際的帖子內容(以及點贊數和回覆數等統計資訊),並透過 ID 查詢傳送者的個人資料(以獲取他們的使用者名稱、個人資料圖片和其他詳細資訊)。這個透過 ID 查詢人類可讀資訊的過程稱為 *hydrating* ID,它本質上是在應用程式程式碼中執行的連線 [^11]。 -在預計算時間線中僅儲存 ID 的原因是它們引用的資料變化很快:熱門帖子的點贊數和回覆數可能每秒變化多次,一些使用者定期更改他們的使用者名稱或個人資料照片。由於時間線在檢視時應該顯示最新的點贊數和個人資料圖片,因此將此資訊反規範化到物化時間線中是沒有意義的。此外,這種反規範化會顯著增加儲存成本。 +在預計算時間線中僅儲存 ID 的原因是它們引用的資料變化很快:熱門帖子的點贊數和回覆數可能每秒變化多次,一些使用者定期更改他們的使用者名稱或個人資料照片。由於時間線在檢視時應該顯示最新的點贊數和個人資料圖片,因此將此資訊反正規化到物化時間線中是沒有意義的。此外,這種反正規化會顯著增加儲存成本。 這個例子表明,在讀取資料時必須執行連線並不像有時聲稱的那樣,是建立高效能、可擴充套件服務的障礙。Hydrating 帖子 ID 和使用者 ID 實際上是一個相當容易擴充套件的操作,因為它可以很好地並行化,並且成本不取決於你關注的帳戶數量或你擁有的關注者數量。 -如果你需要決定是否在應用程式中反規範化某些內容,社交網路案例研究表明選擇並不是立即顯而易見的:最可擴充套件的方法可能涉及反規範化某些內容並保持其他內容規範化。你必須仔細考慮資訊更改的頻率以及讀寫成本(這可能由異常值主導,例如在典型社交網路的情況下擁有許多關注/關注者的使用者)。規範化和反規範化本質上並不好或壞 —— 它們只是在讀寫效能以及實施工作量方面的權衡。 +如果你需要決定是否在應用程式中反正規化某些內容,社交網路案例研究表明選擇並不是立即顯而易見的:最可擴充套件的方法可能涉及反正規化某些內容並保持其他內容正規化。你必須仔細考慮資訊更改的頻率以及讀寫成本(這可能由異常值主導,例如在典型社交網路的情況下擁有許多關注/關注者的使用者)。正規化和反正規化本質上並不好或壞 —— 它們只是在讀寫效能以及實施工作量方面的權衡。 ### 多對一與多對多關係 {#sec_datamodels_many_to_many} @@ -227,7 +227,7 @@ SELECT posts.id, posts.sender_id {{< figure src="/fig/ddia_0303.png" id="fig_datamodels_m2m_rel" caption="圖 3-3. 關係模型中的多對多關係。" class="w-full my-4" >}} -多對一和多對多關係不容易適應一個自包含的 JSON 文件;它們更適合規範化表示。在文件模型中,一種可能的表示如 [示例 3-2](/tw/ch3#fig_datamodels_m2m_json) 所示,並在 [圖 3-4](/tw/ch3#fig_datamodels_many_to_many) 中說明:每個虛線矩形內的資料可以分組到一個文件中,但到組織和學校的連結最好表示為對其他文件的引用。 +多對一和多對多關係不容易適應一個自包含的 JSON 文件;它們更適合正規化表示。在文件模型中,一種可能的表示如 [示例 3-2](/tw/ch3#fig_datamodels_m2m_json) 所示,並在 [圖 3-4](/tw/ch3#fig_datamodels_many_to_many) 中說明:每個虛線矩形內的資料可以分組到一個文件中,但到組織和學校的連結最好表示為對其他文件的引用。 {{< figure id="fig_datamodels_m2m_json" title="示例 3-2. 透過 ID 引用組織的簡歷。" class="w-full my-4" >}} @@ -246,9 +246,9 @@ SELECT posts.id, posts.sender_id {{< figure src="/fig/ddia_0304.png" id="fig_datamodels_many_to_many" caption="圖 3-4. 文件模型中的多對多關係:每個虛線框內的資料可以分組到一個文件中。" class="w-full my-4" >}} -多對多關係通常需要"雙向"查詢:例如,找到特定人員工作過的所有組織,以及找到在特定組織工作過的所有人員。啟用此類查詢的一種方法是在兩邊都儲存 ID 引用,即簡歷包含該人工作過的每個組織的 ID,組織文件包含提到該組織的簡歷的 ID。這種表示是反規範化的,因為關係儲存在兩個地方,可能會相互不一致。 +多對多關係通常需要"雙向"查詢:例如,找到特定人員工作過的所有組織,以及找到在特定組織工作過的所有人員。啟用此類查詢的一種方法是在兩邊都儲存 ID 引用,即簡歷包含該人工作過的每個組織的 ID,組織文件包含提到該組織的簡歷的 ID。這種表示是反正規化的,因為關係儲存在兩個地方,可能會相互不一致。 -規範化表示僅在一個地方儲存關係,並依賴 *二級索引*(我們將在 [第 4 章](/tw/ch4#ch_storage) 中討論)來允許有效地雙向查詢關係。在 [圖 3-3](/tw/ch3#fig_datamodels_m2m_rel) 的關係模式中,我們會告訴資料庫在 `positions` 表的 `user_id` 和 `org_id` 列上建立索引。 +正規化表示僅在一個地方儲存關係,並依賴 *二級索引*(我們將在 [第 4 章](/tw/ch4#ch_storage) 中討論)來允許有效地雙向查詢關係。在 [圖 3-3](/tw/ch3#fig_datamodels_m2m_rel) 的關係模式中,我們會告訴資料庫在 `positions` 表的 `user_id` 和 `org_id` 列上建立索引。 在 [示例 3-2](/tw/ch3#fig_datamodels_m2m_json) 的文件模型中,資料庫需要索引 `positions` 陣列內物件的 `org_id` 欄位。許多文件資料庫和具有 JSON 支援的關係資料庫能夠在文件內的值上建立此類索引。 @@ -270,15 +270,15 @@ SELECT posts.id, posts.sender_id [圖 3-5](/tw/ch3#fig_dwh_schema) 是星型模式的一個例子。該名稱來自這樣一個事實:當表關係被視覺化時,事實表位於中間,被其維度表包圍;到這些表的連線就像星星的光芒。 -這個模板的一個變體被稱為 *雪花模式*,其中維度被進一步分解為子維度。例如,品牌和產品類別可能有單獨的表,`dim_product` 表中的每一行都可以將品牌和類別作為外部索引鍵引用,而不是將它們作為字串儲存在 `dim_product` 表中。雪花模式比星型模式更規範化,但星型模式通常更受歡迎,因為它們對分析師來說更簡單 [^12]。 +這個模板的一個變體被稱為 *雪花模式*,其中維度被進一步分解為子維度。例如,品牌和產品類別可能有單獨的表,`dim_product` 表中的每一行都可以將品牌和類別作為外部索引鍵引用,而不是將它們作為字串儲存在 `dim_product` 表中。雪花模式比星型模式更正規化,但星型模式通常更受歡迎,因為它們對分析師來說更簡單 [^12]。 在典型的資料倉庫中,表通常非常寬:事實表通常有超過 100 列,有時有幾百列。維度表也可能很寬,因為它們包括所有可能與分析相關的元資料 —— 例如,`dim_store` 表可能包括每個商店提供哪些服務的詳細資訊、是否有店內麵包房、平方英尺、商店首次開業的日期、最後一次改造的時間、距離最近的高速公路有多遠等。 -星型或雪花模式主要由多對一關係組成(例如,許多銷售發生在一個特定產品,在一個特定商店),表示為事實表對維度表的外部索引鍵,或維度對子維度的外部索引鍵。原則上,其他型別的關係可能存在,但它們通常被反規範化以簡化查詢。例如,如果客戶一次購買多種不同的產品,則該多項交易不會被明確表示;相反,事實表中為每個購買的產品都有一個單獨的行,這些事實都恰好具有相同的客戶 ID、商店 ID 和時間戳。 +星型或雪花模式主要由多對一關係組成(例如,許多銷售發生在一個特定產品,在一個特定商店),表示為事實表對維度表的外部索引鍵,或維度對子維度的外部索引鍵。原則上,其他型別的關係可能存在,但它們通常被反正規化以簡化查詢。例如,如果客戶一次購買多種不同的產品,則該多項交易不會被明確表示;相反,事實表中為每個購買的產品都有一個單獨的行,這些事實都恰好具有相同的客戶 ID、商店 ID 和時間戳。 -一些資料倉庫模式進一步進行反規範化,完全省略維度表,將維度中的資訊摺疊到事實表上的反規範化列中(本質上是預計算事實表和維度表之間的連線)。這種方法被稱為 *一張大表*(OBT),雖然它需要更多的儲存空間,但有時可以實現更快的查詢 [^13]。 +一些資料倉庫模式進一步進行反正規化,完全省略維度表,將維度中的資訊摺疊到事實表上的反正規化列中(本質上是預計算事實表和維度表之間的連線)。這種方法被稱為 *一張大表*(OBT),雖然它需要更多的儲存空間,但有時可以實現更快的查詢 [^13]。 -在分析的背景下,這種反規範化是沒有問題的,因為資料通常代表不會改變的歷史資料日誌(除了偶爾糾正錯誤)。OLTP 系統中反規範化出現的資料一致性和寫入開銷問題在分析中並不那麼緊迫。 +在分析的背景下,這種反正規化是沒有問題的,因為資料通常代表不會改變的歷史資料日誌(除了偶爾糾正錯誤)。OLTP 系統中反正規化出現的資料一致性和寫入開銷問題在分析中並不那麼緊迫。 ### 何時使用哪種模型 {#sec_datamodels_document_summary} @@ -340,7 +340,7 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL XML 資料庫通常使用 XQuery 和 XPath 查詢,它們旨在允許複雜的查詢,包括跨多個文件的連線,並將其結果格式化為 XML [^28]。JSON Pointer [^29] 和 JSONPath [^30] 為 JSON 提供了等效於 XPath 的功能。 -MongoDB 的聚合管道,我們在 ["規範化、反規範化與連線"](/tw/ch3#sec_datamodels_normalization) 中看到了其用於連線的 `$lookup` 運算子,是 JSON 文件集合查詢語言的一個例子。 +MongoDB 的聚合管道,我們在 ["正規化、反正規化與連線"](/tw/ch3#sec_datamodels_normalization) 中看到了其用於連線的 `$lookup` 運算子,是 JSON 文件集合查詢語言的一個例子。 讓我們看另一個例子來感受這種語言 —— 這次是聚合,這對分析特別需要。想象你是一名海洋生物學家,每次你在海洋中看到動物時,你都會向資料庫新增一條觀察記錄。現在你想生成一份報告,說明你每個月看到了多少條鯊魚。在 PostgreSQL 中,你可能會這樣表達該查詢: @@ -864,7 +864,7 @@ query ChatApp { `replyTo` 欄位類似:在 [示例 3-14](/tw/ch3#fig_graphql_response) 中,第二條訊息是對第一條訊息的回覆,內容("Hey!…")和傳送者 Aaliyah 在 `replyTo` 下重複。可以改為返回被回覆訊息的 ID,但如果該 ID 不在返回的 50 條最新訊息中,客戶端就必須向伺服器發出額外的請求。重複內容使得處理資料變得更加簡單。 -伺服器的資料庫可以以更規範化的形式儲存資料,並執行必要的連線來處理查詢。例如,伺服器可能儲存訊息以及傳送者的使用者 ID 和它所回覆的訊息的 ID;當它收到如上所示的查詢時,伺服器將解析這些 ID 以查詢它們引用的記錄。但是,客戶端只能要求伺服器執行 GraphQL 模式中明確提供的連線。 +伺服器的資料庫可以以更正規化的形式儲存資料,並執行必要的連線來處理查詢。例如,伺服器可能儲存訊息以及傳送者的使用者 ID 和它所回覆的訊息的 ID;當它收到如上所示的查詢時,伺服器將解析這些 ID 以查詢它們引用的記錄。但是,客戶端只能要求伺服器執行 GraphQL 模式中明確提供的連線。 即使對 GraphQL 查詢的響應看起來類似於文件資料庫的響應,即使它的名稱中有"graph",GraphQL 也可以在任何型別的資料庫之上實現 —— 關係型、文件型或圖型。 @@ -895,7 +895,7 @@ query ChatApp { * 對於開發系統的人來說,事件更好地傳達了 *為什麼* 發生某事的意圖。例如,理解事件"預訂已取消"比理解"`bookings` 表第 4001 行的 `active` 列被設定為 `false`,與該預訂相關的三行從 `seat_assignments` 表中刪除,並且在 `payments` 表中插入了一行代表退款"更容易。當物化檢視處理取消事件時,這些行修改仍可能發生,但當它們由事件驅動時,更新的原因變得更加清晰。 * 事件溯源的關鍵原則是物化檢視以可重現的方式從事件日誌派生:你應該始終能夠刪除物化檢視並透過以相同順序處理相同事件,使用相同程式碼來重新計算它們。如果檢視維護程式碼中有錯誤,你可以刪除檢視並使用新程式碼重新計算它。查詢錯誤也更容易,因為你可以隨意重新執行檢視維護程式碼並檢查其行為。 -* 你可以有多個物化檢視,針對應用程式所需的特定查詢進行最佳化。它們可以儲存在與事件相同的資料庫中,也可以儲存在不同的資料庫中,具體取決於你的需求。它們可以使用任何資料模型,並且可以為快速讀取而反規範化。你甚至可以只在記憶體中保留檢視並避免持久化它,只要可以在服務重新啟動時從事件日誌重新計算檢視即可。 +* 你可以有多個物化檢視,針對應用程式所需的特定查詢進行最佳化。它們可以儲存在與事件相同的資料庫中,也可以儲存在不同的資料庫中,具體取決於你的需求。它們可以使用任何資料模型,並且可以為快速讀取而反正規化。你甚至可以只在記憶體中保留檢視並避免持久化它,只要可以在服務重新啟動時從事件日誌重新計算檢視即可。 * 如果你決定以新方式呈現現有資訊,很容易從現有事件日誌構建新的物化檢視。你還可以透過新增新型別的事件或向現有事件型別新增新屬性(任何舊事件保持未修改)來發展系統以支援新功能。你還可以將新行為連結到現有事件(例如,當會議參與者取消時,他們的座位可以提供給等候名單上的下一個人)。 * 如果事件被錯誤寫入,你可以再次刪除它,然後可以在沒有刪除事件的情況下重建檢視。另一方面,在直接更新和刪除資料的資料庫中,已提交的事務通常很難撤銷。因此,事件溯源可以減少系統中不可逆操作的數量,使其更容易更改(參見 ["可演化性:讓變更變得容易"](/tw/ch2#sec_introduction_evolvability))。 * 事件日誌還可以作為系統中發生的所有事情的審計日誌,這在需要此類可審計性的受監管行業中很有價值。 diff --git a/content/tw/ch8.md b/content/tw/ch8.md index c70b8b74..9069d349 100644 --- a/content/tw/ch8.md +++ b/content/tw/ch8.md @@ -145,7 +145,7 @@ SELECT COUNT(*) FROM emails WHERE recipient_id = 2 AND unread_flag = true {{< figure src="/fig/ddia_0802.png" id="fig_transactions_read_uncommitted" caption="圖 8-2. 違反隔離性:一個事務讀取另一個事務的未提交寫入(“髒讀”)。" class="w-full my-4" >}} -然而,如果有很多電子郵件,你可能會發現這個查詢太慢,並決定將未讀訊息的數量儲存在一個單獨的欄位中(一種反規範化,我們在["規範化、反規範化和連線"](/tw/ch3#sec_datamodels_normalization)中討論)。現在,每當有新訊息進來時,你必須增加未讀計數器,每當訊息被標記為已讀時,你也必須減少未讀計數器。 +然而,如果有很多電子郵件,你可能會發現這個查詢太慢,並決定將未讀訊息的數量儲存在一個單獨的欄位中(一種反正規化,我們在["正規化、反正規化和連線"](/tw/ch3#sec_datamodels_normalization)中討論)。現在,每當有新訊息進來時,你必須增加未讀計數器,每當訊息被標記為已讀時,你也必須減少未讀計數器。 在[圖 8-2](/tw/ch8#fig_transactions_read_uncommitted) 中,使用者 2 遇到了異常:郵箱列表顯示有未讀訊息,但計數器顯示零未讀訊息,因為計數器增量尚未發生。(如果電子郵件應用程式中的錯誤計數器看起來太微不足道,請考慮客戶賬戶餘額而不是未讀計數器,以及支付事務而不是電子郵件。)隔離本可以透過確保使用者 2 看到插入的電子郵件和更新的計數器,或者兩者都不看到,但不是不一致的中間點,來防止這個問題。 @@ -186,7 +186,7 @@ SELECT COUNT(*) FROM emails WHERE recipient_id = 2 AND unread_flag = true 在某些用例中,單物件插入、更新和刪除就足夠了。然而,在許多其他情況下,需要協調對多個不同物件的寫入: * 在關係資料模型中,一個表中的行通常具有對另一個表中行的外部索引鍵引用。類似地,在類似圖的資料模型中,頂點具有指向其他頂點的邊。多物件事務允許你確保這些引用保持有效:插入引用彼此的多個記錄時,外部索引鍵必須正確且最新,否則資料變得毫無意義。 -* 在文件資料模型中,需要一起更新的欄位通常在同一文件內,它被視為單個物件——更新單個文件時不需要多物件事務。然而,缺乏連線功能的文件資料庫也鼓勵反規範化(參見["何時使用哪種模型"](/tw/ch3#sec_datamodels_document_summary))。當需要更新反規範化資訊時,如[圖 8-2](/tw/ch8#fig_transactions_read_uncommitted) 的示例,你需要一次更新多個文件。事務在這種情況下非常有用,可以防止反規範化資料失去同步。 +* 在文件資料模型中,需要一起更新的欄位通常在同一文件內,它被視為單個物件——更新單個文件時不需要多物件事務。然而,缺乏連線功能的文件資料庫也鼓勵反正規化(參見["何時使用哪種模型"](/tw/ch3#sec_datamodels_document_summary))。當需要更新反正規化資訊時,如[圖 8-2](/tw/ch8#fig_transactions_read_uncommitted) 的示例,你需要一次更新多個文件。事務在這種情況下非常有用,可以防止反正規化資料失去同步。 * 在具有二級索引的資料庫中(幾乎除了純鍵值儲存之外的所有資料庫),每次更改值時都需要更新索引。從事務的角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離,記錄可能出現在一個索引中但不在另一個索引中,因為對第二個索引的更新尚未發生(參見["分片和二級索引"](/tw/ch7#sec_sharding_secondary_indexes))。 這些應用程式仍然可以在沒有事務的情況下實現。然而,沒有原子性的錯誤處理變得更加複雜,缺乏隔離性可能導致併發問題。我們將在["弱隔離級別"](/tw/ch8#sec_transactions_isolation_levels)中討論這些問題,並在[待補充連結]中探索替代方法。 @@ -1016,7 +1016,7 @@ XA 的最大問題可以透過以下方式解決: 在本章中,我們看到了許多事務有助於防止的問題示例。並非所有應用程式都容易受到所有這些問題的影響:具有非常簡單的訪問模式的應用程式(例如,僅讀取和寫入單個記錄)可能可以在沒有事務的情況下管理。但是,對於更複雜的訪問模式,事務可以大大減少你需要考慮的潛在錯誤情況的數量。 -沒有事務,各種錯誤場景(程序崩潰、網路中斷、停電、磁碟已滿、意外併發等)意味著資料可能以各種方式變得不一致。例如,反規範化資料很容易與源資料失去同步。沒有事務,很難推理複雜的互動訪問對資料庫可能產生的影響。 +沒有事務,各種錯誤場景(程序崩潰、網路中斷、停電、磁碟已滿、意外併發等)意味著資料可能以各種方式變得不一致。例如,反正規化資料很容易與源資料失去同步。沒有事務,很難推理複雜的互動訪問對資料庫可能產生的影響。 在本章中,我們特別深入地探討了併發控制的主題。我們討論了幾種廣泛使用的隔離級別,特別是*讀已提交*、*快照隔離*(有時稱為*可重複讀*)和*可序列化*。我們透過討論各種競態條件的示例來描述這些隔離級別,總結在[表 8-1](/tw/ch8#ch_transactions_isolation_levels) 中: diff --git a/content/tw/glossary.md b/content/tw/glossary.md index 0613fd61..4b6266c4 100644 --- a/content/tw/glossary.md +++ b/content/tw/glossary.md @@ -61,9 +61,9 @@ breadcrumbs: false 描述某些東西應有的屬性,但不知道如何實現它的確切步驟。在查詢的上下文中,查詢最佳化器採用宣告性查詢並決定如何最好地執行它。請參閱“[資料查詢語言](/tw/ch2#資料查詢語言)”。 -## **非規範化(denormalize)** +## **反正規化(denormalize)** - 為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。非規範化的值是一種預先計算的查詢結果,像物化檢視。請參閱“[單物件和多物件操作](/tw/ch7#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](/tw/ch11#從同一事件日誌中派生多個檢視)”。 + 為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。反正規化的值是一種預先計算的查詢結果,像物化檢視。請參閱“[單物件和多物件操作](/tw/ch7#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](/tw/ch11#從同一事件日誌中派生多個檢視)”。 ## **派生資料(derived data)** @@ -157,9 +157,9 @@ breadcrumbs: false 計算機上執行的一些軟體的例項,透過網路與其他節點通訊以完成某項任務。 -## **規範化(normalized)** +## **正規化(normalized)** - 以沒有冗餘或重複的方式進行結構化。在規範化資料庫中,當某些資料發生變化時,你只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。請參閱“[多對一和多對多的關係](/tw/ch2#多對一和多對多的關係)”。 + 以沒有冗餘或重複的方式進行結構化。在正規化資料庫中,當某些資料發生變化時,你只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。請參閱“[多對一和多對多的關係](/tw/ch2#多對一和多對多的關係)”。 ## **OLAP(Online Analytic Processing)** diff --git a/content/tw/part-iii.md b/content/tw/part-iii.md index 83b7c913..44068920 100644 --- a/content/tw/part-iii.md +++ b/content/tw/part-iii.md @@ -24,9 +24,9 @@ breadcrumbs: false 派生資料系統(Derived data systems) : **派生系統** 中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失派生資料,可以從原始來源重新建立。 - 典型的例子是 **快取(cache)**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。非規範化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常派生自使用者日誌。 + 典型的例子是 **快取(cache)**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。反正規化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常派生自使用者日誌。 -從技術上講,派生資料是 **冗餘的(redundant)**,因為它重複了已有的資訊。但是派生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是非規範化的。可以從單個源頭派生出多個不同的資料集,使你能從不同的 “視角” 洞察資料。 +從技術上講,派生資料是 **冗餘的(redundant)**,因為它重複了已有的資訊。但是派生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是反正規化的。可以從單個源頭派生出多個不同的資料集,使你能從不同的 “視角” 洞察資料。 並不是所有的系統都在其架構中明確區分 **記錄系統** 和 **派生資料系統**,但是這是一種有用的區分方式,因為它明確了系統中的資料流:系統的哪一部分具有哪些輸入和哪些輸出,以及它們如何相互依賴。 diff --git a/content/v1_tw/ch10.md b/content/v1_tw/ch10.md index f233c9e5..a4a45cb3 100644 --- a/content/v1_tw/ch10.md +++ b/content/v1_tw/ch10.md @@ -274,7 +274,7 @@ Hadoop 的各種高階工具(如 Pig 【30】、Hive 【31】、Cascading 【3 我們在 [第二章](/v1_tw/ch2) 中討論了資料模型和查詢語言的連線,但是我們還沒有深入探討連線是如何實現的。現在是我們再次撿起這條線索的時候了。 -在許多資料集中,一條記錄與另一條記錄存在關聯是很常見的:關係模型中的 **外部索引鍵**,文件模型中的 **文件引用** 或圖模型中的 **邊**。當你需要同時訪問這一關聯的兩側(持有引用的記錄與被引用的記錄)時,連線就是必須的。正如 [第二章](/v1_tw/ch2) 所討論的,非規範化可以減少對連線的需求,但通常無法將其完全移除 [^v]。 +在許多資料集中,一條記錄與另一條記錄存在關聯是很常見的:關係模型中的 **外部索引鍵**,文件模型中的 **文件引用** 或圖模型中的 **邊**。當你需要同時訪問這一關聯的兩側(持有引用的記錄與被引用的記錄)時,連線就是必須的。正如 [第二章](/v1_tw/ch2) 所討論的,反正規化可以減少對連線的需求,但通常無法將其完全移除 [^v]。 [^v]: 我們在本書中討論的連線通常是等值連線,即最常見的連線型別,其中記錄透過與其他記錄在特定欄位(例如 ID)中具有 **相同值** 相關聯。有些資料庫支援更通用的連線型別,例如使用小於運算子而不是等號運算子,但是我們沒有地方來講這些東西。 diff --git a/content/v1_tw/ch11.md b/content/v1_tw/ch11.md index d5408b04..07372945 100644 --- a/content/v1_tw/ch11.md +++ b/content/v1_tw/ch11.md @@ -380,9 +380,9 @@ $$ 如果你不需要擔心如何查詢與訪問資料,那麼儲存資料通常是非常簡單的。模式設計、索引和儲存引擎的許多複雜性,都是希望支援某些特定查詢和訪問模式的結果(請參閱 [第三章](/v1_tw/ch3))。出於這個原因,透過將資料寫入的形式與讀取形式相分離,並允許幾個不同的讀取檢視,你能獲得很大的靈活性。這個想法有時被稱為 **命令查詢責任分離(command query responsibility segregation, CQRS)**【42,58,59】。 -資料庫和模式設計的傳統方法是基於這樣一種謬論,資料必須以與查詢相同的形式寫入。如果可以將資料從針對寫入最佳化的事件日誌轉換為針對讀取最佳化的應用狀態,那麼有關規範化和非規範化的爭論就變得無關緊要了(請參閱 “[多對一和多對多的關係](/v1_tw/ch2#多對一和多對多的關係)”):在針對讀取最佳化的檢視中對資料進行非規範化是完全合理的,因為翻譯過程提供了使其與事件日誌保持一致的機制。 +資料庫和模式設計的傳統方法是基於這樣一種謬論,資料必須以與查詢相同的形式寫入。如果可以將資料從針對寫入最佳化的事件日誌轉換為針對讀取最佳化的應用狀態,那麼有關正規化和反正規化的爭論就變得無關緊要了(請參閱 “[多對一和多對多的關係](/v1_tw/ch2#多對一和多對多的關係)”):在針對讀取最佳化的檢視中對資料進行反正規化是完全合理的,因為翻譯過程提供了使其與事件日誌保持一致的機制。 -在 “[描述負載](/v1_tw/ch1#描述負載)” 中,我們討論了推特主頁時間線,它是特定使用者關注的人群所發推特的快取(類似郵箱)。這是 **針對讀取最佳化的狀態** 的又一個例子:主頁時間線是高度非規範化的,因為你的推文與你所有粉絲的時間線都構成了重複。然而,扇出服務保持了這種重複狀態與新推特以及新關注關係的同步,從而保證了重複的可管理性。 +在 “[描述負載](/v1_tw/ch1#描述負載)” 中,我們討論了推特主頁時間線,它是特定使用者關注的人群所發推特的快取(類似郵箱)。這是 **針對讀取最佳化的狀態** 的又一個例子:主頁時間線是高度反正規化的,因為你的推文與你所有粉絲的時間線都構成了重複。然而,扇出服務保持了這種重複狀態與新推特以及新關注關係的同步,從而保證了重複的可管理性。 #### 併發控制 diff --git a/content/v1_tw/ch12.md b/content/v1_tw/ch12.md index 36359fcb..ab25e5e5 100644 --- a/content/v1_tw/ch12.md +++ b/content/v1_tw/ch12.md @@ -34,7 +34,7 @@ breadcrumbs: false 例如,為了處理任意關鍵詞的搜尋查詢,將 OLTP 資料庫與全文搜尋索引整合在一起是很常見的需求。儘管一些資料庫(例如 PostgreSQL)包含了全文索引功能,對於簡單的應用完全夠了【1】,但更複雜的搜尋能力就需要專業的資訊檢索工具了。相反的是,搜尋索引通常不適合作為持久的記錄系統,因此許多應用需要組合這兩種不同的工具以滿足所有需求。 -我們在 “[保持系統同步](/v1_tw/ch11#保持系統同步)” 中接觸過整合資料系統的問題。隨著資料不同表示形式的增加,整合問題變得越來越困難。除了資料庫和搜尋索引之外,也許你需要在分析系統(資料倉庫,或批處理和流處理系統)中維護資料副本;維護從原始資料中衍生的快取,或反規範化的資料版本;將資料灌入機器學習、分類、排名或推薦系統中;或者基於資料變更傳送通知。 +我們在 “[保持系統同步](/v1_tw/ch11#保持系統同步)” 中接觸過整合資料系統的問題。隨著資料不同表示形式的增加,整合問題變得越來越困難。除了資料庫和搜尋索引之外,也許你需要在分析系統(資料倉庫,或批處理和流處理系統)中維護資料副本;維護從原始資料中衍生的快取,或反正規化的資料版本;將資料灌入機器學習、分類、排名或推薦系統中;或者基於資料變更傳送通知。 令人驚訝的是,我經常看到軟體工程師做出這樣的陳述:“根據我的經驗,99% 的人只需要 X” 或者 “...... 不需要 X”(對於各種各樣的 X)。我認為這種陳述更像是發言人自己的經驗,而不是技術實際上的實用性。可能對資料執行的操作,其範圍極其寬廣。某人認為雞肋而毫無意義的功能可能是別人的核心需求。當你拉高視角,並考慮跨越整個組織範圍的資料流時,資料整合的需求往往就會變得明顯起來。 diff --git a/content/v1_tw/ch2.md b/content/v1_tw/ch2.md index daecd8cd..1ccb3da7 100644 --- a/content/v1_tw/ch2.md +++ b/content/v1_tw/ch2.md @@ -73,7 +73,7 @@ breadcrumbs: false 例如,[圖 2-1](/v1/ddia_0201.png) 展示了如何在關係模式中表示簡歷(一個 LinkedIn 簡介)。整個簡介可以透過一個唯一的識別符號 `user_id` 來標識。像 `first_name` 和 `last_name` 這樣的欄位每個使用者只出現一次,所以可以在 User 表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示: -* 傳統 SQL 模型(SQL:1999 之前)中,最常見的規範化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對 User 表提供外部索引鍵引用,如 [圖 2-1](/v1/ddia_0201.png) 所示。 +* 傳統 SQL 模型(SQL:1999 之前)中,最常見的正規化表示形式是將職位,教育和聯絡資訊放在單獨的表中,對 User 表提供外部索引鍵引用,如 [圖 2-1](/v1/ddia_0201.png) 所示。 * 後續的 SQL 標準增加了對結構化資料型別和 XML 資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在 Oracle,IBM DB2,MS SQL Server 和 PostgreSQL 中都有不同程度的支援【6,7】。JSON 資料型別也得到多個數據庫的支援,包括 IBM DB2,MySQL 和 PostgreSQL 【8】。 * 第三種選擇是將職業,教育和聯絡資訊編碼為 JSON 或 XML 文件,將其儲存在資料庫的文字列中,並讓應用程式解析其結構和內容。這種配置下,通常不能使用資料庫來查詢該編碼列中的值。 @@ -143,13 +143,13 @@ JSON 表示比 [圖 2-1](/v1/ddia_0201.png) 中的多表模式具有更好的 ** 儲存 ID 還是文字字串,這是個 **副本(duplication)** 問題。當使用 ID 時,對人類有意義的資訊(比如單詞:Philanthropy)只儲存在一處,所有引用它的地方使用 ID(ID 只在資料庫中有意義)。當直接儲存文字時,對人類有意義的資訊會複製在每處使用記錄中。 -使用 ID 的好處是,ID 對人類沒有任何意義,因而永遠不需要改變:ID 可以保持不變,即使它標識的資訊發生變化。任何對人類有意義的東西都可能需要在將來某個時候改變 —— 如果這些資訊被複制,所有的冗餘副本都需要更新。這會導致寫入開銷,也存在不一致的風險(一些副本被更新了,還有些副本沒有被更新)。去除此類重複是資料庫 **規範化(normalization)** 的關鍵思想。[^ii] +使用 ID 的好處是,ID 對人類沒有任何意義,因而永遠不需要改變:ID 可以保持不變,即使它標識的資訊發生變化。任何對人類有意義的東西都可能需要在將來某個時候改變 —— 如果這些資訊被複制,所有的冗餘副本都需要更新。這會導致寫入開銷,也存在不一致的風險(一些副本被更新了,還有些副本沒有被更新)。去除此類重複是資料庫 **正規化(normalization)** 的關鍵思想。[^ii] -[^ii]: 關於關係模型的文獻區分了幾種不同的規範形式,但這些區別幾乎沒有實際意義。一個經驗法則是,如果重複儲存了可以儲存在一個地方的值,則模式就不是 **規範化(normalized)** 的。 +[^ii]: 關於關係模型的文獻區分了幾種不同的規範形式,但這些區別幾乎沒有實際意義。一個經驗法則是,如果重複儲存了可以儲存在一個地方的值,則模式就不是 **正規化(normalized)** 的。 -> 資料庫管理員和開發人員喜歡爭論規範化和非規範化,讓我們暫時保留判斷吧。在本書的 [第三部分](/v1_tw/part-iii),我們將回到這個話題,探討系統的方法用以處理快取,非規範化和衍生資料。 +> 資料庫管理員和開發人員喜歡爭論正規化和反正規化,讓我們暫時保留判斷吧。在本書的 [第三部分](/v1_tw/part-iii),我們將回到這個話題,探討系統的方法用以處理快取,反正規化和衍生資料。 -不幸的是,對這些資料進行規範化需要多對一的關係(許多人生活在一個特定的地區,許多人在一個特定的行業工作),這與文件模型不太吻合。在關係資料庫中,透過 ID 來引用其他表中的行是正常的,因為連線很容易。在文件資料庫中,一對多樹結構沒有必要用連線,對連線的支援通常很弱 [^iii]。 +不幸的是,對這些資料進行正規化需要多對一的關係(許多人生活在一個特定的地區,許多人在一個特定的行業工作),這與文件模型不太吻合。在關係資料庫中,透過 ID 來引用其他表中的行是正常的,因為連線很容易。在文件資料庫中,一對多樹結構沒有必要用連線,對連線的支援通常很弱 [^iii]。 [^iii]: 在撰寫本文時,RethinkDB 支援連線,MongoDB 不支援連線,而 CouchDB 只支援預先宣告的檢視。 @@ -181,7 +181,7 @@ JSON 表示比 [圖 2-1](/v1/ddia_0201.png) 中的多表模式具有更好的 ** IMS 的設計中使用了一個相當簡單的資料模型,稱為 **層次模型(hierarchical model)**,它與文件資料庫使用的 JSON 模型有一些驚人的相似之處【2】。它將所有資料表示為巢狀在記錄中的記錄樹,這很像 [圖 2-2](/v1/ddia_0202.png) 的 JSON 結構。 -同文檔資料庫一樣,IMS 能良好處理一對多的關係,但是很難應對多對多的關係,並且不支援連線。開發人員必須決定是否複製(非規範化)資料或手動解決從一個記錄到另一個記錄的引用。這些二十世紀六七十年代的問題與現在開發人員遇到的文件資料庫問題非常相似【15】。 +同文檔資料庫一樣,IMS 能良好處理一對多的關係,但是很難應對多對多的關係,並且不支援連線。開發人員必須決定是否複製(反正規化)資料或手動解決從一個記錄到另一個記錄的引用。這些二十世紀六七十年代的問題與現在開發人員遇到的文件資料庫問題非常相似【15】。 那時人們提出了各種不同的解決方案來解決層次模型的侷限性。其中最突出的兩個是 **關係模型**(relational model,它變成了 SQL,並統治了世界)和 **網狀模型**(network model,最初很受關注,但最終變得冷門)。這兩個陣營之間的 “大辯論” 在 70 年代持續了很久時間【2】。 @@ -233,7 +233,7 @@ CODASYL 中的查詢是透過利用遍歷記錄列和跟隨訪問路徑表在資 文件資料庫對連線的糟糕支援可能是個問題,也可能不是問題,這取決於應用程式。例如,如果某分析型應用程式使用一個文件資料庫來記錄何時何地發生了何事,那麼多對多關係可能永遠也用不上。【19】。 -但如果你的應用程式確實會用到多對多關係,那麼文件模型就沒有那麼誘人了。儘管可以透過反規範化來消除對連線的需求,但這需要應用程式程式碼來做額外的工作以確保資料一致性。儘管應用程式程式碼可以透過向資料庫發出多個請求的方式來模擬連線,但這也將複雜性轉移到應用程式中,而且通常也會比由資料庫內的專用程式碼更慢。在這種情況下,使用文件模型可能會導致更複雜的應用程式碼與更差的效能【15】。 +但如果你的應用程式確實會用到多對多關係,那麼文件模型就沒有那麼誘人了。儘管可以透過反正規化來消除對連線的需求,但這需要應用程式程式碼來做額外的工作以確保資料一致性。儘管應用程式程式碼可以透過向資料庫發出多個請求的方式來模擬連線,但這也將複雜性轉移到應用程式中,而且通常也會比由資料庫內的專用程式碼更慢。在這種情況下,使用文件模型可能會導致更複雜的應用程式碼與更差的效能【15】。 我們沒有辦法說哪種資料模型更有助於簡化應用程式碼,因為它取決於資料項之間的關係種類。對高度關聯的資料而言,文件模型是極其糟糕的,關係模型是可以接受的,而選用圖形模型(請參閱 “[圖資料模型](#圖資料模型)”)是最自然的。 diff --git a/content/v1_tw/ch3.md b/content/v1_tw/ch3.md index d085d089..ba83adc6 100644 --- a/content/v1_tw/ch3.md +++ b/content/v1_tw/ch3.md @@ -441,7 +441,7 @@ Teradata、Vertica、SAP HANA 和 ParAccel 等資料倉庫供應商通常使用 “星型模式” 這個名字來源於這樣一個事實,即當我們對錶之間的關係進行視覺化時,事實表在中間,被維度表包圍;與這些表的連線就像星星的光芒。 -這個模板的變體被稱為雪花模式,其中維度被進一步分解為子維度。例如,品牌和產品類別可能有單獨的表格,並且 `dim_product` 表格中的每一行都可以將品牌和類別作為外部索引鍵引用,而不是將它們作為字串儲存在 `dim_product` 表格中。雪花模式比星形模式更規範化,但是星形模式通常是首選,因為分析師使用它更簡單【55】。 +這個模板的變體被稱為雪花模式,其中維度被進一步分解為子維度。例如,品牌和產品類別可能有單獨的表格,並且 `dim_product` 表格中的每一行都可以將品牌和類別作為外部索引鍵引用,而不是將它們作為字串儲存在 `dim_product` 表格中。雪花模式比星形模式更正規化,但是星形模式通常是首選,因為分析師使用它更簡單【55】。 在典型的資料倉庫中,表格通常非常寬:事實表通常有 100 列以上,有時甚至有數百列【51】。維度表也可以是非常寬的,因為它們包括了所有可能與分析相關的元資料 —— 例如,`dim_store` 表可以包括在每個商店提供哪些服務的細節、它是否具有店內麵包房、店面面積、商店第一次開張的日期、最近一次改造的時間、離最近的高速公路的距離等等。 @@ -566,7 +566,7 @@ WHERE product_sk = 31 AND store_sk = 3 建立這種快取的一種方式是物化檢視(Materialized View)。在關係資料模型中,它通常被定義為一個標準(虛擬)檢視:一個類似於表的物件,其內容是一些查詢的結果。不同的是,物化檢視是查詢結果的實際副本,會被寫入硬碟,而虛擬檢視只是編寫查詢的一個捷徑。從虛擬檢視讀取時,SQL 引擎會將其展開到檢視的底層查詢中,然後再處理展開的查詢。 -當底層資料發生變化時,物化檢視需要更新,因為它是資料的非規範化副本。資料庫可以自動完成該操作,但是這樣的更新使得寫入成本更高,這就是在 OLTP 資料庫中不經常使用物化檢視的原因。在讀取繁重的資料倉庫中,它們可能更有意義(它們是否實際上改善了讀取效能取決於使用場景)。 +當底層資料發生變化時,物化檢視需要更新,因為它是資料的反正規化副本。資料庫可以自動完成該操作,但是這樣的更新使得寫入成本更高,這就是在 OLTP 資料庫中不經常使用物化檢視的原因。在讀取繁重的資料倉庫中,它們可能更有意義(它們是否實際上改善了讀取效能取決於使用場景)。 物化檢視的常見特例稱為資料立方體或 OLAP 立方【64】。它是按不同維度分組的聚合網格。[圖 3-12](/v1/ddia_0312.png) 顯示了一個例子。 diff --git a/content/v1_tw/ch7.md b/content/v1_tw/ch7.md index 4a1cfec1..3637eccb 100644 --- a/content/v1_tw/ch7.md +++ b/content/v1_tw/ch7.md @@ -141,7 +141,7 @@ ACID 意義上的隔離性意味著,**同時執行的事務是相互隔離的* SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true ``` -但如果郵件太多,你可能會覺得這個查詢太慢,並決定用單獨的欄位儲存未讀郵件的數量(一種反規範化)。現在每當一個新訊息寫入時,必須也增長未讀計數器,每當一個訊息被標記為已讀時,也必須減少未讀計數器。 +但如果郵件太多,你可能會覺得這個查詢太慢,並決定用單獨的欄位儲存未讀郵件的數量(一種反正規化)。現在每當一個新訊息寫入時,必須也增長未讀計數器,每當一個訊息被標記為已讀時,也必須減少未讀計數器。 在 [圖 7-2](/v1/ddia_0702.png) 中,使用者 2 遇到異常情況:郵件列表裡顯示有未讀訊息,但計數器顯示為零未讀訊息,因為計數器增長還沒有發生 [^ii]。隔離性可以避免這個問題:透過確保使用者 2 要麼同時看到新郵件和增長後的計數器,要麼都看不到,而不是一個前後矛盾的中間結果。 @@ -188,7 +188,7 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true 有一些場景中,單物件插入,更新和刪除是足夠的。但是許多其他場景需要協調寫入幾個不同的物件: * 在關係資料模型中,一個表中的行通常具有對另一個表中的行的外部索引鍵引用。(類似的是,在一個圖資料模型中,一個頂點有著到其他頂點的邊)。多物件事務使你確保這些引用始終有效:當插入幾個相互引用的記錄時,外部索引鍵必須是正確的和最新的,不然資料就沒有意義。 -* 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件 —— 更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵非規範化(請參閱 “[關係型資料庫與文件資料庫在今日的對比](/v1_tw/ch2#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新非規範化的資訊時,如 [圖 7-2](/v1/ddia_0702.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止非規範化的資料不同步。 +* 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件 —— 更新單個文件時不需要多物件事務。但是,缺乏連線功能的文件資料庫會鼓勵反正規化(請參閱 “[關係型資料庫與文件資料庫在今日的對比](/v1_tw/ch2#關係型資料庫與文件資料庫在今日的對比)”)。當需要更新反正規化的資訊時,如 [圖 7-2](/v1/ddia_0702.png) 所示,需要一次更新多個文件。事務在這種情況下非常有用,可以防止反正規化的資料不同步。 * 在具有次級索引的資料庫中(除了純粹的鍵值儲存以外幾乎都有),每次更改值時都需要更新索引。從事務角度來看,這些索引是不同的資料庫物件:例如,如果沒有事務隔離性,記錄可能出現在一個索引中,但沒有出現在另一個索引中,因為第二個索引的更新還沒有發生。 這些應用仍然可以在沒有事務的情況下實現。然而,**沒有原子性,錯誤處理就要複雜得多,缺乏隔離性,就會導致併發問題**。我們將在 “[弱隔離級別](#弱隔離級別)” 中討論這些問題,並在 [第十二章](/v1_tw/ch12) 中探討其他方法。 @@ -822,7 +822,7 @@ WHERE room_id = 123 AND 在本章中介紹了很多問題,事務有助於防止這些問題發生。並非所有應用都易受此類問題影響:具有非常簡單訪問模式的應用(例如每次讀寫單條記錄)可能無需事務管理。但是對於更複雜的訪問模式,事務可以大大減少需要考慮的潛在錯誤情景數量。 -如果沒有事務處理,各種錯誤情況(程序崩潰、網路中斷、停電、磁碟已滿、意外併發等)意味著資料可能以各種方式變得不一致。例如,非規範化的資料可能很容易與源資料不同步。如果沒有事務處理,就很難推斷複雜的互動訪問可能對資料庫造成的影響。 +如果沒有事務處理,各種錯誤情況(程序崩潰、網路中斷、停電、磁碟已滿、意外併發等)意味著資料可能以各種方式變得不一致。例如,反正規化的資料可能很容易與源資料不同步。如果沒有事務處理,就很難推斷複雜的互動訪問可能對資料庫造成的影響。 本章深入討論了 **併發控制** 的話題。我們討論了幾個廣泛使用的隔離級別,特別是 **讀已提交**、**快照隔離**(有時稱為可重複讀)和 **可序列化**。並透過研究競爭條件的各種例子,來描述這些隔離等級: diff --git a/content/v1_tw/glossary.md b/content/v1_tw/glossary.md index c7fe3894..b0ae2054 100644 --- a/content/v1_tw/glossary.md +++ b/content/v1_tw/glossary.md @@ -57,9 +57,9 @@ breadcrumbs: false 描述某些東西應有的屬性,但不知道如何實現它的確切步驟。在查詢的上下文中,查詢最佳化器採用宣告性查詢並決定如何最好地執行它。請參閱“[資料查詢語言](/v1_tw/ch2#資料查詢語言)”。 -## **非規範化(denormalize)** +## **反正規化(denormalize)** - 為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。非規範化的值是一種預先計算的查詢結果,像物化檢視。請參閱“[單物件和多物件操作](/v1_tw/ch7#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](/v1_tw/ch11#從同一事件日誌中派生多個檢視)”。 + 為了加速讀取,在標準資料集中引入一些冗餘或重複資料,通常採用快取或索引的形式。反正規化的值是一種預先計算的查詢結果,像物化檢視。請參閱“[單物件和多物件操作](/v1_tw/ch7#單物件和多物件操作)”和“[從同一事件日誌中派生多個檢視](/v1_tw/ch11#從同一事件日誌中派生多個檢視)”。 ## **衍生資料(derived data)** @@ -153,9 +153,9 @@ breadcrumbs: false 計算機上執行的一些軟體的例項,透過網路與其他節點通訊以完成某項任務。 -## **規範化(normalized)** +## **正規化(normalized)** - 以沒有冗餘或重複的方式進行結構化。在規範化資料庫中,當某些資料發生變化時,你只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。請參閱“[多對一和多對多的關係](/v1_tw/ch2#多對一和多對多的關係)”。 + 以沒有冗餘或重複的方式進行結構化。在正規化資料庫中,當某些資料發生變化時,你只需要在一個地方進行更改,而不是在許多不同的地方複製很多次。請參閱“[多對一和多對多的關係](/v1_tw/ch2#多對一和多對多的關係)”。 ## **OLAP(Online Analytic Processing)** diff --git a/content/v1_tw/part-iii.md b/content/v1_tw/part-iii.md index c1278f92..e42612e4 100644 --- a/content/v1_tw/part-iii.md +++ b/content/v1_tw/part-iii.md @@ -21,9 +21,9 @@ breadcrumbs: false * 衍生資料系統(Derived data systems) - **衍生系統** 中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失衍生資料,可以從原始來源重新建立。典型的例子是 **快取(cache)**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。非規範化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常衍生自使用者日誌。 + **衍生系統** 中的資料,通常是另一個系統中的現有資料以某種方式進行轉換或處理的結果。如果丟失衍生資料,可以從原始來源重新建立。典型的例子是 **快取(cache)**:如果資料在快取中,就可以由快取提供服務;如果快取不包含所需資料,則降級由底層資料庫提供。反正規化的值,索引和物化檢視亦屬此類。在推薦系統中,預測彙總資料通常衍生自使用者日誌。 -從技術上講,衍生資料是 **冗餘的(redundant)**,因為它重複了已有的資訊。但是衍生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是非規範化的。可以從單個源頭衍生出多個不同的資料集,使你能從不同的 “視角” 洞察資料。 +從技術上講,衍生資料是 **冗餘的(redundant)**,因為它重複了已有的資訊。但是衍生資料對於獲得良好的只讀查詢效能通常是至關重要的。它通常是反正規化的。可以從單個源頭衍生出多個不同的資料集,使你能從不同的 “視角” 洞察資料。 並不是所有的系統都在其架構中明確區分 **記錄系統** 和 **衍生資料系統**,但是這是一種有用的區分方式,因為它明確了系統中的資料流:系統的哪一部分具有哪些輸入和哪些輸出,以及它們如何相互依賴。