update zh-tw content

This commit is contained in:
Gang Yin 2022-10-10 11:51:04 +08:00
parent 438cd62228
commit f81763bc1e
5 changed files with 83 additions and 81 deletions

View File

@ -143,13 +143,15 @@
5. [詞彙表](glossary.md)、[後記](colophon.md)關於野豬的部分 by [@Chowss](https://github.com/Vonng/ddia/commits?author=Chowss)
6. [繁體中文](https://github.com/Vonng/ddia/pulls)版本與轉換指令碼 by [@afunTW](https://github.com/afunTW)
7. 多處翻譯修正 by [@songzhibin97](https://github.com/Vonng/ddia/commits?author=songzhibin97)
8. 感謝所有作出貢獻,提出意見的朋友們:
8. 多處翻譯修正 by [@MamaShip](https://github.com/Vonng/ddia/commits?author=MamaShip)
9. 感謝所有作出貢獻,提出意見的朋友們:
<details>
<summary><a href="https://github.com/Vonng/ddia/pulls">Pull Requests</a> & <a href="https://github.com/Vonng/ddia/issues">Issues</a></summary>
| ISSUE & Pull Requests | USER | Title |
| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [263](https://github.com/Vonng/ddia/pull/263) | [@zydmayday](https://github.com/zydmayday) | ch5: 修正譯文中的重複單詞 |
| [260](https://github.com/Vonng/ddia/pull/260) | [@haifeiWu](https://github.com/haifeiWu) | ch4: 修正部分不準確的翻譯 |
| [258](https://github.com/Vonng/ddia/pull/258) | [@bestgrc](https://github.com/bestgrc) | ch3: 修正一處翻譯錯誤 |
| [257](https://github.com/Vonng/ddia/pull/257) | [@UnderSam](https://github.com/UnderSam) | ch8: 修正一處拼寫錯誤 |

View File

@ -289,7 +289,7 @@
* 可演化性evolvability
使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為 **可擴extensibility**、**可修改性modifiability** 或 **可塑性plasticity**
使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為 **可擴充套件extensibility**、**可修改性modifiability** 或 **可塑性plasticity**
和之前提到的可靠性、可伸縮性一樣,實現這些目標也沒有簡單的解決方案。不過我們會試著想象具有可操作性,簡單性和可演化性的系統會是什麼樣子。

View File

@ -418,7 +418,7 @@ for (var i = 0; i < liElements.length; i++) {
MapReduce 是一個由 Google 推廣的程式設計模型用於在多臺機器上批次處理大規模的資料【33】。一些 NoSQL 資料儲存(包括 MongoDB 和 CouchDB支援有限形式的 MapReduce作為在多個文件中執行只讀查詢的機制。
MapReduce 將 [第十章](ch10.md) 中有更詳細的描述。現在我們將簡要討論一下 MongoDB 使用的模型。
關於 MapReduce 更詳細的介紹在 [第十章](ch10.md)。現在我們只簡要討論一下 MongoDB 使用的模型。
MapReduce 既不是一個宣告式的查詢語言,也不是一個完全命令式的查詢 API而是處於兩者之間查詢的邏輯用程式碼片段來表示這些程式碼片段會被處理框架重複性呼叫。它基於 `map`(也稱為 `collect`)和 `reduce`(也稱為 `fold``inject`)函式,兩個函式存在於許多函數語言程式設計語言中。
@ -504,7 +504,7 @@ db.observations.aggregate([
]);
```
聚合管道語言與 SQL 的子集具有類似表現力,但是它使用基於 JSON 的語法而不是 SQL 的英語句子式語法;這種差異也許是口味問題。這個故事的寓意是 NoSQL 系統可能會發現自己意外地重新發明了 SQL儘管帶著偽裝
聚合管道語言的表現力與(前述 PostgreSQL 例子的SQL 子集相當,但是它使用基於 JSON 的語法而不是 SQL 那種接近英文句式的語法這種差異也許只是口味問題。這個故事的寓意是NoSQL 系統可能會意外發現自己只是重新發明了一套經過喬裝改扮的 SQL
## 圖資料模型
@ -584,13 +584,13 @@ CREATE INDEX edges_heads ON edges (head_vertex);
2. 給定任何頂點,可以高效地找到它的入邊和出邊,從而遍歷圖,即沿著一系列頂點的路徑前後移動(這就是為什麼 [例 2-2]() 在 `tail_vertex``head_vertex` 列上都有索引的原因)。
3. 透過對不同型別的關係使用不同的標籤,可以在一個圖中儲存幾種不同的資訊,同時仍然保持一個清晰的資料模型。
這些特性為資料建模提供了很大的靈活性,如 [圖 2-5](../img/fig2-5.png) 所示。圖中顯示了一些傳統關係模式難以表達的事情,例如不同國家的不同地區結構(法國有省和美國有不同的州和州國中國的怪事先忽略主權國家和國家錯綜複雜的爛攤子不同的資料粒度Lucy 現在的住所被指定為一個城市,而她的出生地點只是在一個州的級別)。
這些特性為資料建模提供了很大的靈活性,如 [圖 2-5](../img/fig2-5.png) 所示。圖中顯示了一些傳統關係模式難以表達的事情,例如不同國家的不同地區結構(法國有省和大區美國有縣和州國中國的怪事先忽略主權國家和民族錯綜複雜的爛攤子不同的資料粒度Lucy 現在的住所記錄具體到城市,而她的出生地點只是在一個州的級別)。
你可以想象延伸圖還能包括許多關於 Lucy 和 Alain,或其他人的其他更多的事實。例如,你可以用它來表示食物過敏(為每個過敏源增加一個頂點,並增加人與過敏源之間的一條邊來指示一種過敏情況),並連結到過敏源,每個過敏源具有一組頂點用來顯示哪些食物含有哪些物質。然後,你可以寫一個查詢,找出每個人吃什麼是安全的。圖在可演化性是富有優勢的:當嚮應用程式新增功能時,可以輕鬆擴充套件圖以適應應用程式資料結構的變化。
你可以想象該圖還能延伸出許多關於 Lucy 和 Alain 的事實,或其他人的其他更多的事實。例如,你可以用它來表示食物過敏(為每個過敏源增加一個頂點,並增加人與過敏源之間的一條邊來指示一種過敏情況),並連結到過敏源,每個過敏源具有一組頂點用來顯示哪些食物含有哪些物質。然後,你可以寫一個查詢,找出每個人吃什麼是安全的。圖在可演化性方面是富有優勢的:當嚮應用程式新增功能時,可以輕鬆擴充套件圖以適應程式資料結構的變化。
### Cypher 查詢語言
Cypher 是屬性圖的宣告式查詢語言,為 Neo4j 圖形資料庫而發明【37】它是以電影 “駭客帝國” 中的一個角色來命名的,而與密碼術中的密碼無關【38】
Cypher 是屬性圖的宣告式查詢語言,為 Neo4j 圖形資料庫而發明【37】它是以電影 “駭客帝國” 中的一個角色來命名的,而與密碼學中的加密演算法無關【38】
[例 2-3]() 顯示了將 [圖 2-5](../img/fig2-5.png) 的左邊部分插入圖形資料庫的 Cypher 查詢。可以類似地新增圖的其餘部分,為了便於閱讀而省略。每個頂點都有一個像 `USA``Idaho` 這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`Idaho - [WITHIN] ->USA` 建立一條標記為 `WITHIN` 的邊,`Idaho` 為尾節點,`USA` 為頭節點。
@ -632,11 +632,11 @@ RETURN person.name
等價地,也可以從兩個 `Location` 頂點開始反向地查詢。假如 `name` 屬性上有索引,則可以高效地找到代表美國和歐洲的兩個頂點。然後,沿著所有 `WITHIN` 入邊,可以繼續查找出所有在美國和歐洲的位置(州,地區,城市等)。最後,查找出那些可以由 `BORN_IN``LIVES_IN` 入邊到那些位置頂點的人。
通常對於宣告式查詢語言來說,在編寫查詢語句時,不需要指定執行細節:查詢最佳化程式會自動選擇預測效率最高的策略,因此你可以繼續編寫應用程式的其他部分。
通常對於宣告式查詢語言來說,在編寫查詢語句時,不需要指定執行細節:查詢最佳化程式會自動選擇預測效率最高的策略,因此你可以專注於編寫應用程式的其他部分。
### SQL 中的圖查詢
[例 2-2]() 建議在關係資料庫中表示圖資料。但是,如果把圖資料放入關係結構中,我們是否也可以使用 SQL 查詢它?
[例 2-2]() 指出,可以在關係資料庫中表示圖資料。但是,如果圖資料已經以關係結構儲存,我們是否也可以使用 SQL 查詢它?
答案是肯定的,但有些困難。在關係資料庫中,你通常會事先知道在查詢中需要哪些連線。在圖查詢中,你可能需要在找到待查詢的頂點之前,遍歷可變數量的邊。也就是說,連線的數量事先並不確定。
@ -704,7 +704,7 @@ WITH RECURSIVE
1. 原始資料型別中的值,例如字串或數字。在這種情況下,三元組的謂語和賓語相當於主語頂點上的屬性的鍵和值。例如,`(lucy, age, 33)` 就像屬性 `{“age”33}` 的頂點 lucy。
2. 圖中的另一個頂點。在這種情況下,謂語是圖中的一條邊,主語是其尾部頂點,而賓語是其頭部頂點。例如,在 `(lucy, marriedTo, alain)` 中主語和賓語 `lucy``alain` 都是頂點,並且謂語 `marriedTo` 是連線他們的邊的標籤。
[例 2-6]() 顯示了與 [例 2-3]() 相同的資料,以稱為 Turtle 的格式Notation3N3【39】的一個子集形式寫成三元組。
[例 2-6]() 展示了與 [例 2-3]() 相同的資料,以稱為 Turtle 的格式Notation3N3【39】的一個子集寫成三元組。
**例 2-6 圖 2-5 中的資料子集,表示為 Turtle 三元組**
@ -742,13 +742,13 @@ _:namerica a :Location; :name "North America"; :type "continent".
#### 語義網
如果你閱讀更多關於三元組儲存的資訊你可能會被捲入關於語義網的文章漩渦中。三元組儲存資料模型完全獨立於語義網例如Datomic【40】是三元組儲存 [^vii],並沒有聲稱與它有任何關係。但是,由於在很多人眼中這兩者緊密相連,我們應該簡要地討論一下。
如果你深入瞭解關於三元組儲存的資訊,可能會陷入關於**語義網**的討論漩渦中。三元組儲存模型其實是完全獨立於語義網存在的例如Datomic【40】作為一種三元組儲存資料庫 [^vii],從未被用於語義網中。但是,由於在很多人眼中這兩者緊密相連,我們應該簡要地討論一下。
[^vii]: 從技術上講Datomic 使用的是五元組而不是三元組,兩個額外的欄位是用於版本控制的元資料
從本質上講語義網是一個簡單且合理的想法:網站已經將資訊釋出為文字和圖片供人類閱讀,為什麼不將資訊作為機器可讀的資料也釋出給計算機呢?**資源描述框架**RDF【41】的目的是作為不同網站以統一的格式釋出資料的一種機制,允許來自不同網站的資料自動合併成 **一個數據網路** - 一種網際網路範圍內的 “通用語義網資料庫 “
從本質上講語義網是一個簡單且合理的想法:網站已經將資訊釋出為文字和圖片供人類閱讀,為什麼不將資訊作為機器可讀的資料也釋出給計算機呢?(基於三元組模型的)**資源描述框架****RDF**【41】被用作不同網站以統一的格式釋出資料的一種機制,允許來自不同網站的資料自動合併成 **一個數據網路** —— 成為一種網際網路範圍內的 “通用語義網資料庫”
不幸的是,這個語義網在二十一世紀初被過度使用,但到目前為止沒有任何跡象表明已在實踐中實現,這使得許多人嗤之以鼻。它還遭受了過多的令人眼花繚亂的縮略詞,過於複雜的標準提議和狂妄自大的苦果
不幸的是,語義網在二十一世紀初被過度炒作,但到目前為止沒有任何跡象表明已在實踐中應用,這使得許多人嗤之以鼻。它還飽受眼花繚亂的縮略詞、過於複雜的標準提案和狂妄自大的困擾
然而,如果從過去的失敗中汲取教訓,語義網專案還是擁有很多優秀的成果。即使你沒有興趣在語義網上釋出 RDF 資料,三元組這種模型也是一種好的應用程式內部資料模型。
@ -786,13 +786,13 @@ _:namerica a :Location; :name "North America"; :type "continent".
RDF 有一些奇怪之處,因為它是為了在網際網路上交換資料而設計的。三元組的主語,謂語和賓語通常是 URI。例如謂語可能是一個 URI`<http://my-company.com/namespace#within>``<http://my-company.com/namespace#lives_in>`,而不僅僅是 `WITHIN``LIVES_IN`。這個設計背後的原因為了讓你能夠把你的資料和其他人的資料結合起來,如果他們賦予單詞 `within` 或者 `lives_in` 不同的含義,兩者也不會衝突,因為它們的謂語實際上是 `<http://other.org/foo#within>``<http://other.org/foo#lives_in>`
從 RDF 的角度來看URL `<http://my-company.com/namespace>` 不一定需要能解析成什麼東西,它只是一個名稱空間。為避免與 `http://URL` 混淆,本節中的示例使用不可解析的 URI`urnexamplewithin`。幸運的是,你只需在檔案頂部指定一個字首,然後就不用再管了。
從 RDF 的角度來看URL `<http://my-company.com/namespace>` 不一定需要能解析成什麼東西,它只是一個名稱空間。為避免與 `http://URL` 混淆,本節中的示例使用不可解析的 URI`urnexamplewithin`。幸運的是,你只需在檔案頂部對這個字首做一次宣告,後續就不用再管了。
### SPARQL 查詢語言
**SPARQL** 是一種用於三元組儲存的面向 RDF 資料模型的查詢語言【43】它是 SPARQL 協議和 RDF 查詢語言的縮寫,發音為 “sparkle”。SPARQL 早於 Cypher並且由於 Cypher 的模式匹配借鑑於 SPARQL這使得它們看起來非常相似【37】。
與之前相同的查詢 - 查詢從美國轉移到歐洲的人 - 使用 SPARQL 比使用 Cypher 甚至更為簡潔(請參閱 [例 2-9]())。
與之前相同的查詢 —— 查詢從美國移民到歐洲的人 —— 使用 SPARQL 比使用 Cypher 甚至更為簡潔(請參閱 [例 2-9]())。
**例 2-9 與示例 2-4 相同的查詢,用 SPARQL 表示**
@ -812,14 +812,14 @@ SELECT ?personName WHERE {
?person :bornIn / :within* ?location. # SPARQL
```
因為 RDF 不區分屬性和邊,而只是將它們作為謂語,所以可以使用相同的語法來匹配屬性。在下面的表示式中,變數 `usa` 被繫結到任意具有值為字串 `"United States"``name` 屬性的頂點:
因為 RDF 不區分屬性和邊,而只是將它們作為謂語,所以可以使用相同的語法來匹配屬性。在下面的表示式中,變數 `usa` 被繫結到任意 `name` 屬性為字串值 `"United States"` 的頂點:
```
(usa {name:'United States'}) # Cypher
?usa :name "United States". # SPARQL
```
SPARQL 是一種很好的查詢語言 — 儘管 SPARQL 從未實現語義網,但是它仍然是一種應用程式內部使用的強大工具。
SPARQL 是一種很好的查詢語言 —— 儘管它構想的語義網從未實現,但它仍然是一種可用於應用程式內部的強大工具。
> #### 圖形資料庫與網狀模型相比較
>
@ -829,8 +829,8 @@ SPARQL 是一種很好的查詢語言 — 儘管 SPARQL 從未實現語義網,
>
> * 在 CODASYL 中,資料庫有一個模式,用於指定哪種記錄型別可以巢狀在其他記錄型別中。在圖形資料庫中,不存在這樣的限制:任何頂點都可以具有到其他任何頂點的邊。這為應用程式適應不斷變化的需求提供了更大的靈活性。
> * 在 CODASYL 中,達到特定記錄的唯一方法是遍歷其中的一個訪問路徑。在圖形資料庫中,可以透過其唯一 ID 直接引用任何頂點,也可以使用索引來查詢具有特定值的頂點。
> * 在 CODASYL,記錄的後續是一個有序集合,所以資料庫的人不得不維持排序(這會影響儲存佈局),並且插入新記錄到資料庫的應用程式不得不擔心的新記錄在這些集合中的位置。在圖形資料庫中,頂點和邊不是有序的(只能在查詢時對結果進行排序)。
> * 在 CODASYL 中,所有查詢都是命令式的,難以編寫,並且很容易因架構中的變化而受到破壞。在圖形資料庫中,如果需要,可以在命令式程式碼中編寫遍歷,但大多數圖形資料庫也支援高階宣告式查詢語言,如 Cypher 或 SPARQL。
> * 在 CODASYL 中,記錄的子專案是一個有序集合,所以資料庫必須去管理它們的次序(這會影響儲存佈局),並且應用程式在插入新記錄到資料庫時必須關注新記錄在這些集合中的位置。在圖形資料庫中,頂點和邊是無序的(只能在查詢時對結果進行排序)。
> * 在 CODASYL 中,所有查詢都是命令式的,難以編寫,並且很容易因架構變化而受到破壞。在圖形資料庫中,你可以在命令式程式碼中手寫遍歷過程,但大多數圖形資料庫都支援高階宣告式查詢,如 Cypher 或 SPARQL。
>
>
@ -838,7 +838,7 @@ SPARQL 是一種很好的查詢語言 — 儘管 SPARQL 從未實現語義網,
**Datalog** 是比 SPARQL、Cypher 更古老的語言,在 20 世紀 80 年代被學者廣泛研究【44,45,46】。它在軟體工程師中不太知名但是它是重要的因為它為以後的查詢語言提供了基礎。
在實踐中Datalog 被用於少數的資料系統中:例如,它是 Datomic 【40】的查詢語言Cascalog 【47】是一種用於查詢 Hadoop 大資料集的 Datalog 實現 [^viii]。
實踐中Datalog 在有限的幾個資料系統中使用:例如,它是 Datomic 【40】的查詢語言Cascalog 【47】是一種用於查詢 Hadoop 大資料集的 Datalog 實現 [^viii]。
[^viii]: Datomic 和 Cascalog 使用 Datalog 的 Clojure S 表示式語法。在下面的例子中使用了一個更容易閱讀的 Prolog 語法,但兩者沒有任何功能差異。
@ -862,7 +862,7 @@ name(lucy, 'Lucy').
born_in(lucy, idaho).
```
既然已經定義了資料,我們可以像之前一樣編寫相同的查詢,如 [例 2-11]() 所示。它看起來有點不同於 Cypher 或 SPARQL 的等價物但是請不要放棄它。Datalog 是 Prolog 的一個子集,如果你學過電腦科學,你可能已經見過
既然已經定義了資料,我們可以像之前一樣編寫相同的查詢,如 [例 2-11]() 所示。它看起來與 Cypher 或 SPARQL 的語法差異較大但請不要抗拒它。Datalog 是 Prolog 的一個子集,如果你是電腦科學專業的學生,可能已經見過 Prolog
**例 2-11 與示例 2-4 相同的查詢,用 Datalog 表示**
@ -881,7 +881,7 @@ migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */
?- migrated(Who, 'United States', 'Europe'). /* Who = 'Lucy'. */
```
Cypher 和 SPARQL 使用 SELECT 立即跳轉,但是 Datalog 一次只進行一小步。我們定義 **規則**,以將新謂語告訴資料庫:在這裡,我們定義了兩個新的謂語,`within_recursive` 和 `migrated`。這些謂語不是儲存在資料庫中的三元組中,而是它們是從資料或其他規則派生而來的。規則可以引用其他規則,就像函式可以呼叫其他函式或者遞迴地呼叫自己一樣。像這樣,複雜的查詢可以一次構建其中的一小塊
Cypher 和 SPARQL 使用 SELECT 立即跳轉,但是 Datalog 一次只進行一小步。我們定義 **規則**,以將新謂語告訴資料庫:在這裡,我們定義了兩個新的謂語,`within_recursive` 和 `migrated`。這些謂語不是儲存在資料庫中的三元組中,而是從資料或其他規則派生而來的。規則可以引用其他規則,就像函式可以呼叫其他函式或者遞迴地呼叫自己一樣。像這樣,複雜的查詢可以藉由小的磚瓦構建起來
在規則中,以大寫字母開頭的單詞是變數,謂語則用 Cypher 和 SPARQL 的方式一樣來匹配。例如,`name(Location, Name)` 透過變數繫結 `Location = namerica``Name ='North America'` 可以匹配三元組 `name(namerica, 'North America')`
@ -906,23 +906,23 @@ Cypher 和 SPARQL 使用 SELECT 立即跳轉,但是 Datalog 一次只進行一
## 本章小結
資料模型是一個巨大的課題,在本章中,我們快速瀏覽了各種不同的模型。我們沒有足夠的空間來詳細介紹每個模型的細節,但是希望這個概述足以激起你的興趣,以更多地瞭解最適合你的應用需求的模型。
資料模型是一個巨大的課題,在本章中,我們快速瀏覽了各種不同的模型。我們沒有足夠的篇幅來詳述每個模型的細節,但是希望這個概述足以激起你的興趣,以更多地瞭解最適合你的應用需求的模型。
在歷史上,資料最開始被表示為一棵大樹(層次資料模型),但是這不利於表示多對多的關係,所以發明了關係模型來解決這個問題。最近,開發人員發現一些應用程式也不適合採用關係模型。新的非關係型 “NoSQL” 資料儲存在兩個主要方向上存在分歧
在歷史上,資料最開始被表示為一棵大樹(層次資料模型),但是這不利於表示多對多的關係,所以發明了關係模型來解決這個問題。最近,開發人員發現一些應用程式也不適合採用關係模型。新的非關係型 “NoSQL” 資料儲存分化為兩個主要方向
1. **文件資料庫** 的應用場景是:資料通常是自我包含的,而且文件之間的關係非常稀少。
2. **圖形資料庫** 用於相反的場景:任意事物都可能與任何事物相關聯。
1. **文件資料庫** 主要關注自我包含的資料文件,而且文件之間的關係非常稀少。
2. **圖形資料庫** 用於相反的場景:任意事物之間都可能存在潛在的關聯。
這三種模型(文件,關係和圖形)在今天都被廣泛使用,並且在各自的領域都發揮很好。一個模型可以用另一個模型來模擬 — 例如,圖資料可以在關係資料庫中表示 — 但結果往往是糟糕的。這就是為什麼我們有著針對不同目的的不同系統,而不是一個單一的萬能解決方案。
這三種模型(文件,關係和圖形)在今天都被廣泛使用,並且在各自的領域都發揮很好。一個模型可以用另一個模型來模擬 — 例如,圖資料可以在關係資料庫中表示 — 但結果往往是糟糕的。這就是為什麼我們有著針對不同目的的不同系統,而不是一個單一的萬能解決方案。
文件資料庫和圖資料庫有一個共同點,那就是它們通常不會為儲存的資料強制一個模式,這可以使應用程式更容易適應不斷變化的需求。但是應用程式很可能仍會假定資料具有一定的結構;這只是模式是明確的(寫入時強制)還是隱含的(讀取時處理)的問題
文件資料庫和圖資料庫有一個共同點,那就是它們通常不會將儲存的資料強制約束為特定模式,這可以使應用程式更容易適應不斷變化的需求。但是應用程式很可能仍會假定資料具有一定的結構;區別僅在於模式是**明確的**(寫入時強制)還是**隱含的**(讀取時處理)
每個資料模型都具有各自的查詢語言或框架我們討論了幾個例子SQLMapReduceMongoDB 的聚合管道CypherSPARQL 和 Datalog。我們也談到了 CSS 和 XSL/XPath它們不是資料庫查詢語言而包含有趣的相似之處。
雖然我們已經覆蓋了很多層面,但仍然有許多資料模型沒有提到。舉幾個簡單的例子:
* 使用基因組資料的研究人員通常需要執行 **序列相似性搜尋**,這意味著需要一個很長的字串(代表一個 DNA 分子),並在一個擁有類似但不完全相同的字串的大型資料庫中尋找匹配。這裡所描述的資料庫都不能處理這種用法,這就是為什麼研究人員編寫了像 GenBank 這樣的專門的基因組資料庫軟體的原因【48】。
* 粒子物理學家數十年來一直在進行大資料型別的大規模資料分析像大型強子對撞機LHC這樣的專案現在可以工作在數百億兆位元組的範圍內在這樣的規模下需要定製解決方案來阻止硬體成本的失控【49】。
* 使用基因組資料的研究人員通常需要執行 **序列相似性搜尋**,這意味著需要一個很長的字串(代表一個 DNA 序列),並在一個擁有類似但不完全相同的字串的大型資料庫中尋找匹配。這裡所描述的資料庫都不能處理這種用法,這就是為什麼研究人員編寫了像 GenBank 這樣的專門的基因組資料庫軟體的原因【48】。
* 粒子物理學家數十年來一直在進行大資料型別的大規模資料分析像大型強子對撞機LHC這樣的專案現在會處理數百 PB 的資料在這樣的規模下需要定製解決方案來阻止硬體成本的失控【49】。
* **全文搜尋** 可以說是一種經常與資料庫一起使用的資料模型。資訊檢索是一個很大的專業課題,我們不會在本書中詳細介紹,但是我們將在第三章和第三部分中介紹搜尋索引。
讓我們暫時將其放在一邊。在 [下一章](ch3.md) 中,我們將討論在 **實現** 本章描述的資料模型時會遇到的一些權衡。

View File

@ -78,7 +78,7 @@ $ cat database
### 雜湊索引
讓我們從 **鍵值資料key-value Data** 的索引開始。這不是你可以索引的唯一資料型別,但鍵值資料是很常見的。對於更複雜的索引來說,這也是一個有用的構建模組
讓我們從 **鍵值資料key-value Data** 的索引開始。這不是你可以索引的唯一資料型別,但鍵值資料是很常見的。在引入更複雜的索引之前,它是重要的第一步
鍵值儲存與在大多數程式語言中可以找到的 **字典dictionary** 型別非常相似,通常字典都是用 **雜湊對映hash map****散列表hash table** 實現的。雜湊對映在許多演算法教科書中都有描述【1,2】所以這裡我們不會討論它的工作細節。既然我們已經可以用雜湊對映來表示 **記憶體中** 的資料結構,為什麼不使用它來索引 **硬碟上** 的資料呢?
@ -92,7 +92,7 @@ $ cat database
像 Bitcask 這樣的儲存引擎非常適合每個鍵的值經常更新的情況。例如鍵可能是某個貓咪影片的網址URL而值可能是該影片被播放的次數每次有人點選播放按鈕時遞增。在這種型別的工作負載中有很多寫操作但是沒有太多不同的鍵 —— 每個鍵有很多的寫操作,但是將所有鍵儲存在記憶體中是可行的。
直到現在,我們只是追加寫入一個檔案 —— 所以如何避免最終用完硬碟空間一種好的解決方案是將日誌分為特定大小的段segment當日志增長到特定尺寸時關閉當前段檔案並開始寫入一個新的段檔案。然後我們就可以對這些段進行 **壓縮compaction**,如 [圖 3-2](../img/fig3-2.png) 所示。這裡的壓縮意味著在日誌中丟棄重複的鍵,只保留每個鍵的最近更新。
到目前為止,我們只是在追加寫入一個檔案 —— 所以如何避免最終用完硬碟空間?一種好的解決方案是,將日誌分為特定大小的 **segment**,當日志增長到特定尺寸時關閉當前段檔案,並開始寫入一個新的段檔案。然後,我們就可以對這些段進行 **壓縮compaction**,如 [圖 3-2](../img/fig3-2.png) 所示。這裡的壓縮意味著在日誌中丟棄重複的鍵,只保留每個鍵的最近更新。
![](../img/fig3-2.png)
@ -136,7 +136,7 @@ $ cat database
但是,散列表索引也有其侷限性:
* 散列表必須能放進記憶體。如果你有非常多的鍵,那真是倒楣。原則上可以在硬碟上維護一個雜湊對映,不幸的是硬碟雜湊對映很難表現優秀。它需要大量的隨機訪問 I/O當它用滿時想要再增長是很昂貴的,並且雜湊衝突的處理也需要很煩瑣的邏輯【5】。
* 散列表必須能放進記憶體。如果你有非常多的鍵,那真是倒楣。原則上可以在硬碟上維護一個雜湊對映,不幸的是硬碟雜湊對映很難表現優秀。它需要大量的隨機訪問 I/O而後者耗盡時想要再擴充是很昂貴的,並且需要很煩瑣的邏輯去解決雜湊衝突【5】。
* 範圍查詢效率不高。例如,你無法輕鬆掃描 kitty00000 和 kitty99999 之間的所有鍵 —— 你必須在雜湊對映中單獨查詢每個鍵。
在下一節中,我們將看到一個沒有這些限制的索引結構。
@ -191,28 +191,28 @@ $ cat database
這裡描述的演算法本質上是 LevelDB【6】和 RocksDB【7】這些鍵值儲存引擎庫所使用的技術這些儲存引擎被設計嵌入到其他應用程式中。除此之外LevelDB 可以在 Riak 中用作 Bitcask 的替代品。在 Cassandra 和 HBase 中也使用了類似的儲存引擎【8】而且他們都受到了 Google 的 Bigtable 論文【9】引入了術語 SSTable 和 memtable )的啟發。
最初這種索引結構是由 Patrick O'Neil 等人描述的,且被命名為日誌結構合併樹(或 LSM 樹【10】它是基於更早之前的日誌結構檔案系統【11】來構建的。基於這種合併和壓縮排序檔案原理的儲存引擎通常被稱為 LSM 儲存引擎。
這種索引結構最早由 Patrick O'Neil 等人發明,且被命名為日誌結構合併樹(或 LSM 樹【10】它是基於更早之前的日誌結構檔案系統【11】來構建的。基於這種合併和壓縮排序檔案原理的儲存引擎通常被稱為 LSM 儲存引擎。
Lucene 是 Elasticsearch 和 Solr 使用的一種全文搜尋的索引引擎它使用類似的方法來儲存它的關鍵詞詞典【12,13】。全文索引比鍵值索引複雜得多但是基於類似的想法在搜尋查詢中給出一個單詞,找到提及單詞的所有文件(網頁,產品描述等)。這是透過鍵值結構實現的,其中鍵是單詞(或 **詞語**,即 term值是所有包含該單詞的文件的 ID 列表(記錄列表)。在 Lucene 中,從詞語到記錄列表的這種對映儲存在類似於 SSTable 的有序檔案中並根據需要在後臺合併【14】。
Lucene,是一種全文搜尋的索引引擎,在 Elasticsearch 和 Solr 被使用它使用類似的方法來儲存它的關鍵詞詞典【12,13】。全文索引比鍵值索引複雜得多但是基於類似的想法在搜尋查詢中,由一個給定的單詞,找到提及單詞的所有文件(網頁,產品描述等)。這也是透過鍵值結構實現的:其中鍵是 **單詞term**,值是所有包含該單詞的文件的 ID 列表(**postings list**)。在 Lucene 中,從詞語到記錄列表的這種對映儲存在類似於 SSTable 的有序檔案中,並根據需要在後臺執行合併【14】。
#### 效能最佳化
與往常一樣要讓儲存引擎在實踐中表現良好涉及到大量設計細節。例如當查詢資料庫中不存在的鍵時LSM 樹演算法可能會很慢你必須先檢查記憶體表然後檢視從最近的到最舊的所有的段可能還必須從硬碟讀取每一個段檔案然後才能確定這個鍵不存在。為了最佳化這種訪問儲存引擎通常使用額外的布隆過濾器Bloom filters【15】。 (布隆過濾器是用於近似集合內容的高效記憶體資料結構,它可以告訴你資料庫中是不是不存在某個鍵,從而為不存在的鍵節省掉許多不必要的硬碟讀取操作。)
與往常一樣要讓儲存引擎在實踐中表現良好涉及到大量設計細節。例如當查詢資料庫中不存在的鍵時LSM 樹演算法可能會很慢你必須先檢查記憶體表然後檢視從最近的到最舊的所有的段可能還必須從硬碟讀取每一個段檔案然後才能確定這個鍵不存在。為了最佳化這種訪問儲存引擎通常使用額外的布隆過濾器Bloom filters【15】。 (布隆過濾器是一種節省記憶體的資料結構,用於近似表達集合的內容,它可以告訴你資料庫中是否存在某個鍵,從而為不存在的鍵節省掉許多不必要的硬碟讀取操作。)
還有一些不同的策略來確定 SSTables 被壓縮和合並的順序和時間。最常見的選擇是 size-tiered 和 leveled compaction。LevelDB 和 RocksDB 使用 leveled compactionLevelDB 因此得名HBase 使用 size-tieredCassandra 同時支援這兩種【16】。對於 sized-tiered較新和較小的 SSTables 相繼被合併到較舊的和較大的 SSTable 中。對於 leveled compactionkey 範圍被拆分到較小的 SSTables而較舊的資料被移動到單獨的層級level這使得壓縮compaction能夠更加增量地進行並且使用較少的硬碟空間。
還有一些不同的策略來確定 SSTables 被壓縮和合並的順序和時間。最常見的選擇是 size-tiered 和 leveled compaction。LevelDB 和 RocksDB 使用 leveled compactionLevelDB 因此得名HBase 使用 size-tieredCassandra 同時支援這兩種【16】。對於 sized-tiered較新和較小的 SSTables 相繼被合併到較舊的和較大的 SSTable 中。對於 leveled compactionkey (按照分佈範圍被拆分到較小的 SSTables而較舊的資料被移動到單獨的層級level這使得壓縮compaction能夠更加增量地進行並且使用較少的硬碟空間。
即使有許多微妙的東西LSM 樹的基本思想 —— 儲存一系列在後臺合併的 SSTables —— 簡單而有效。即使資料集比可用記憶體大得多,它仍能繼續正常工作。由於資料按排序順序儲存,你可以高效地執行範圍查詢(掃描所有從某個最小值到某個最大值之間的所有鍵),並且因為硬碟寫入是連續的,所以 LSM 樹可以支援非常高的寫入吞吐量。
### B樹
前面討論的日誌結構索引正處在逐漸被接受的階段,但它們並不是最常見的索引型別。使用最廣泛的索引結構和日誌結構索引相當不同,它就是我們接下來要討論的 B 樹。
前面討論的日誌結構索引看起來已經相當可用了,但它們卻不是最常見的索引型別。使用最廣泛的索引結構和日誌結構索引相當不同,它就是我們接下來要討論的 B 樹。
從 1970 年被引入【17】僅不到 10 年後就變得 “無處不在”【18】B 樹很好地經受了時間的考驗。在幾乎所有的關係資料庫中,它們仍然是標準的索引實現,許多非關係資料庫也會使用到 B 樹。
像 SSTables 一樣B 樹保持按鍵排序的鍵值對,這允許高效的鍵值查詢和範圍查詢。但這也就是有的相似之處了B 樹有著非常不同的設計理念。
像 SSTables 一樣B 樹保持按鍵排序的鍵值對,這允許高效的鍵值查詢和範圍查詢。但這也就是有的相似之處了B 樹有著非常不同的設計理念。
我們前面看到的日誌結構索引將資料庫分解為可變大小的段通常是幾兆位元組或更大的大小並且總是按順序寫入段。相比之下B 樹將資料庫分解成固定大小的塊block或頁page傳統上大小為 4KB有時會更大並且一次只能讀取或寫入一個頁面。這種設計更接近於底層硬體因為硬碟空間也是按固定大小的塊來組織的。
我們前面看到的日誌結構索引將資料庫分解為可變大小的段通常是幾兆位元組或更大的大小並且總是按順序寫入段。相比之下B 樹將資料庫分解成固定大小的 **block** **分page**,傳統上大小為 4KB有時會更大並且一次只能讀取或寫入一個頁面。這種設計更接近於底層硬體因為硬碟空間也是按固定大小的塊來組織的。
每個頁面都可以使用地址或位置來標識,這允許一個頁面引用另一個頁面 —— 類似於指標,但在硬碟而不是在記憶體中。我們可以使用這些頁面引用來構建一個頁面樹,如 [圖 3-6](../img/fig3-6.png) 所示。
@ -220,13 +220,13 @@ Lucene 是 Elasticsearch 和 Solr 使用的一種全文搜尋的索引引擎,
**圖 3-6 使用 B 樹索引查詢一個鍵**
一個頁面會被指定為 B 樹的根;在索引中查詢一個鍵時,就從這裡開始。該頁面包含幾個鍵和對子頁面的引用。每個子頁面負責一段連續範圍的鍵,引用之間的鍵,指明瞭引用子頁面的鍵範圍
一個頁面會被指定為 B 樹的根;在索引中查詢一個鍵時,就從這裡開始。該頁面包含幾個鍵和對子頁面的引用。每個子頁面負責一段連續範圍的鍵,根頁面上每兩個引用之間的鍵,表示相鄰子頁面管理的鍵的範圍(邊界)
在 [圖 3-6](../img/fig3-6.png) 的例子中,我們正在尋找鍵 251 ,所以我們知道我們需要跟蹤邊界 200 和 300 之間的頁面引用。這將我們帶到一個類似的頁面,進一步將 200 到 300 的範圍拆分到子範圍。
最終我們將到達某個包含單個鍵的頁面葉子頁面leaf page該頁面或者直接包含每個鍵的值或者包含了對可以找到值的頁面的引用。
在 B 樹的一個頁面中對子頁面的引用的數量稱為分支因子。例如,在 [圖 3-6](../img/fig3-6.png) 中,分支因子是 6。在實踐中分支因子取決於儲存頁面引用和範圍邊界所需的空間,但通常是幾百
在 B 樹的一個頁面中對子頁面的引用的數量稱為 **分支因子branching factor**。例如,在 [圖 3-6](../img/fig3-6.png) 中,分支因子是 6。在實踐中分支因子的大小取決於儲存頁面引用和範圍邊界所需的空間,但這個值通常是幾百。
如果要更新 B 樹中現有鍵的值,需要搜尋包含該鍵的葉子頁面,更改該頁面中的值,並將該頁面寫回到硬碟(對該頁面的任何引用都將保持有效)。如果你想新增一個新的鍵,你需要找到其範圍能包含新鍵的頁面,並將其新增到該頁面。如果頁面中沒有足夠的可用空間容納新鍵,則將其分成兩個半滿頁面,並更新父頁面以反映新的鍵範圍分割槽,如 [圖 3-7](../img/fig3-7.png) 所示 [^ii]。
@ -244,39 +244,39 @@ B 樹的基本底層寫操作是用新資料覆寫硬碟上的頁面,並假定
你可以把覆寫硬碟上的頁面對應為實際的硬體操作。在磁性硬碟驅動器上,這意味著將磁頭移動到正確的位置,等待旋轉盤上的正確位置出現,然後用新的資料覆寫適當的扇區。在固態硬碟上,由於 SSD 必須一次擦除和重寫相當大的儲存晶片塊所以會發生更複雜的事情【19】。
而且,一些操作需要覆寫幾個不同的頁面。例如,如果因為插入導致頁面過滿而拆分頁面,則需要寫入新拆分的兩個頁面,並覆寫其父頁面以更新對兩個子頁面的引用。這是一個危險的操作,因為如果資料庫在僅有部分頁面被寫入時崩潰,那麼最終將導致一個損壞的索引(例如,可能有一個孤兒頁面不是任何父項的子項
而且,一些操作需要覆寫幾個不同的頁面。例如,如果因為插入導致頁面過滿而拆分頁面,則需要寫入新拆分的兩個頁面,並覆寫其父頁面以更新對兩個子頁面的引用。這是一個危險的操作,因為如果資料庫在系列操作進行到一半時崩潰,那麼最終將導致一個損壞的索引(例如,可能有一個孤兒頁面沒有被任何頁面引用
為了使資料庫能處理異常崩潰的場景B 樹實現通常會帶有一個額外的硬碟資料結構:**預寫式日誌**WAL即 write-ahead log也稱為 **重做日誌**,即 redo log。這是一個僅追加的檔案每個 B 樹的修改在其能被應用到樹本身的頁面之前都必須先寫入到該檔案。當資料庫在崩潰後恢復時,這個日誌將被用來使 B 樹恢復到一致的狀態【5,20】。
另外還有一個更新頁面的複雜情況是,如果多個執行緒要同時訪問 B 樹,則需要仔細的併發控制 —— 否則執行緒可能會看到樹處於不一致的狀態。這通常是透過使用 **鎖存器**latches輕量級鎖保護樹的資料結構來完成。日誌結構化的方法在這方面更簡單因為它們在後臺進行所有的合併而不會干擾新接收到的查詢並且能夠時不時地將舊的段原子交換為新的段
另外還有一個更新頁面的複雜情況是,如果多個執行緒要同時訪問 B 樹,則需要仔細的併發控制 —— 否則執行緒可能會看到樹處於不一致的狀態。這通常是透過使用 **鎖存器**latches輕量級鎖保護樹的資料結構來完成。日誌結構化的方法在這方面更簡單因為它們在後臺進行所有的合併而不會干擾新接收到的查詢並且能夠時不時地將段檔案切換為新的(該切換是原子操作)
#### B樹的最佳化
由於 B 樹已經存在了很久,所以並不奇怪這麼多年下來有很多最佳化的設計被開發出來,僅舉幾例:
* 一些資料庫(如 LMDB使用寫時複製方案【21】,而不是覆蓋頁面並維護 WAL 以支援崩潰恢復。修改的頁面被寫入到不同的位置,並且還在樹中建立了父頁面的新版本,以指向新的位置。這種方法對於併發控制也很有用,我們將在 “[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)” 中看到。
* 不同於覆寫頁面並維護 WAL 以支援崩潰恢復,一些資料庫(如 LMDB使用寫時複製方案【21】。經過修改的頁面被寫入到不同的位置,並且還在樹中建立了父頁面的新版本,以指向新的位置。這種方法對於併發控制也很有用,我們將在 “[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)” 中看到。
* 我們可以透過不儲存整個鍵,而是縮短其大小,來節省頁面空間。特別是在樹內部的頁面上,鍵只需要提供足夠的資訊來充當鍵範圍之間的邊界。在頁面中包含更多的鍵允許樹具有更高的分支因子,因此也就允許更少的層級 [^iii]。
* 通常,頁面可以放置在硬碟上的任何位置;沒有什麼要求相鄰鍵範圍的頁面也放在硬碟上相鄰的區域。如果某個查詢需要按照排序順序掃描大部分的鍵範圍,那麼這種按頁面儲存的佈局可能會效率低下,因為每次頁面讀取可能都需要進行硬碟查詢。因此,許多 B 樹的實現在佈局樹時會盡量使葉子頁面按順序出現在硬碟上。但是,隨著樹的增長,要維持這個順序是很困難的。相比之下,由於 LSM 樹在合併過程中一次又一次地重寫儲存的大部分,所以它們更容易使順序鍵在硬碟上彼此靠近
* 額外的指標被新增到樹中。例如,每個葉子頁面可以引用其左邊和右邊的兄弟頁面,使得不用跳回父頁面就能按順序對鍵進行掃描。
* B 樹的變體如分形樹fractal tree【22】借用一些日誌結構的思想來減少硬碟查詢而且它們與分形無關
* 通常,頁面可以放置在硬碟上的任何位置;沒有什麼要求相鄰鍵範圍的頁面也放在硬碟上相鄰的區域。如果某個查詢需要按照排序順序掃描大部分的鍵範圍,那麼這種按頁面儲存的佈局可能會效率低下,因為每個頁面的讀取都需要執行一次硬碟查詢。因此,許多 B 樹的實現在佈局樹時會盡量使葉子頁面按順序出現在硬碟上。但是,隨著樹的增長,要維持這個順序是很困難的。相比之下,由於 LSM 樹在合併過程中一次性重寫一大段儲存,所以它們更容易使順序鍵在硬碟上連續儲存
* 額外的指標被新增到樹中。例如,每個葉子頁面可以引用其左邊和右邊的兄弟頁面,使得不用跳回父頁面就能按順序對鍵進行掃描。
* B 樹的變體如 **分形樹fractal trees**【22】借用一些日誌結構的思想來減少硬碟查詢(而且它們與分形無關)。
[^iii]: 這個變種有時被稱為 B+ 樹,但因為這個最佳化已被廣泛使用,所以經常無法區分於其它的 B 樹變種。
### 比較B樹和LSM樹
儘管 B 樹實現通常比 LSM 樹實現更成熟,但 LSM 樹由於其效能特點也非常有趣。根據經驗,通常 LSM 樹的寫入速度更快,而 B 樹的讀取速度更快【23】。 LSM 樹上的讀取通常比較慢因為它們必須檢查幾種不同的資料結構和不同壓縮Compaction層級的 SSTables。
儘管 B 樹實現通常比 LSM 樹實現更成熟,但 LSM 樹由於效能特徵也非常有趣。根據經驗,通常 LSM 樹的寫入速度更快,而 B 樹的讀取速度更快【23】。 LSM 樹上的讀取通常比較慢因為它們必須檢查幾種不同的資料結構和不同壓縮Compaction層級的 SSTables。
然而,基準測試的結果通常和工作負載的細節相關。你需要用你特有的工作負載來測試系統,以便進行有效的比較。在本節中,我們將簡要討論一些在衡量儲存引擎效能時值得考慮的事情。
#### LSM樹的優點
B 樹索引中的每塊資料都必須至少寫入兩次一次寫入預先寫入日誌WAL一次寫入樹頁面本身如果有分頁還需要再寫入一次。即使在該頁面中只有幾個位元組發生了變化也需要接受寫入整個頁面的開銷。有些儲存引擎甚至會覆寫同一個頁面兩次以免在電源故障的情況下導致頁面部分更新【24,25】。
B 樹索引中的每塊資料都必須至少寫入兩次一次寫入預先寫入日誌WAL一次寫入樹頁面本身如果有分頁還需要再寫入一次。即使在該頁面中只有幾個位元組發生了變化也需要接受寫入整個頁面的開銷。有些儲存引擎甚至會覆寫同一個頁面兩次以免在電源故障的情況下頁面未完整更新【24,25】。
由於反覆壓縮和合並 SSTables日誌結構索引也會多次重寫資料。這種影響 —— 在資料庫的生命週期中每次寫入資料庫導致對硬碟的多次寫入 —— 被稱為 **寫放大write amplification**。需要特別注意的是固態硬碟,固態硬碟的快閃記憶體壽命在覆寫有限次數後就會耗盡。
由於反覆壓縮和合並 SSTables日誌結構索引也會多次重寫資料。這種影響 —— 在資料庫的生命週期中每筆資料導致對硬碟的多次寫入 —— 被稱為 **寫入放大write amplification**。使用固態硬碟的機器需要額外關注這點,固態硬碟的快閃記憶體壽命在覆寫有限次數後就會耗盡。
在寫入繁重的應用程式中,效能瓶頸可能是資料庫可以寫入硬碟的速度。在這種情況下,寫放大會導致直接的效能代價:儲存引擎寫入硬碟的次數越多,可用硬碟頻寬內它能處理的每秒寫入次數就越少。
LSM 樹通常能夠比 B 樹支援更高的寫入吞吐量,部分原因是它們有時具有較低的寫放大(儘管這取決於儲存引擎的配置和工作負載),部分是因為它們順序地寫入緊湊的 SSTable 檔案而不是必須覆寫樹中的幾個頁面【26】。這種差異在磁性硬碟驅動器上尤其重要,其順序寫入比隨機寫入要快得多。
LSM 樹通常能夠比 B 樹支援更高的寫入吞吐量,部分原因是它們有時具有較低的寫放大(儘管這取決於儲存引擎的配置和工作負載),部分是因為它們順序地寫入緊湊的 SSTable 檔案而不是必須覆寫樹中的幾個頁面【26】。這種差異在機械硬碟上尤其重要,其順序寫入比隨機寫入要快得多。
LSM 樹可以被壓縮得更好,因此通常能比 B 樹在硬碟上產生更小的檔案。B 樹儲存引擎會由於碎片化fragmentation而留下一些未使用的硬碟空間當頁面被拆分或某行不能放入現有頁面時頁面中的某些空間仍未被使用。由於 LSM 樹不是面向頁面的,並且會透過定期重寫 SSTables 以去除碎片所以它們具有較低的儲存開銷特別是當使用分層壓縮leveled compaction時【27】。
@ -292,7 +292,7 @@ LSM 樹可以被壓縮得更好,因此通常能比 B 樹在硬碟上產生更
B 樹的一個優點是每個鍵只存在於索引中的一個位置,而日誌結構化的儲存引擎可能在不同的段中有相同鍵的多個副本。這個方面使得 B 樹在想要提供強大的事務語義的資料庫中很有吸引力:在許多關係資料庫中,事務隔離是透過在鍵範圍上使用鎖來實現的,在 B 樹索引中這些鎖可以直接附加到樹上【5】。在 [第七章](ch7.md) 中,我們將更詳細地討論這一點。
B 樹在資料庫架構中是非常根深蒂固的,為許多工作負載都提供了始終如一的良好效能,所以它們不可能很快就會消失。在新的資料儲存中,日誌結構化索引變得越來越流行。沒有快速和容易的規則來確定哪種型別的儲存引擎對你的場景更好,所以值得去透過一些測試來得到相關的經驗。
B 樹在資料庫架構中是非常根深蒂固的,為許多工作負載都提供了始終如一的良好效能,所以它們不可能在短期內消失。在新的資料庫中,日誌結構化索引變得越來越流行。沒有簡單易行的辦法來判斷哪種型別的儲存引擎對你的使用場景更好,所以需要透過一些測試來得到相關經驗。
### 其他索引結構
@ -300,7 +300,7 @@ B 樹在資料庫架構中是非常根深蒂固的,為許多工作負載都提
次級索引secondary indexes也很常見。在關係資料庫中你可以使用 `CREATE INDEX` 命令在同一個表上建立多個次級索引而且這些索引通常對於有效地執行聯接join而言至關重要。例如在 [第二章](ch2.md) 中的 [圖 2-1](../img/fig2-1.png) 中,很可能在 `user_id` 列上有一個次級索引,以便你可以在每個表中找到屬於同一使用者的所有行。
次級索引可以很容易地從鍵值索引構建。次級索引主要的不同是鍵不是唯一的,即可能有許多行(文件,頂點)具有相同的鍵。這可以透過兩種方式來解決:或者將匹配行識別符號的列表作為索引裡的值就像全文索引中的記錄列表或者透過向每個鍵新增行識別符號來使鍵唯一。無論哪種方式B 樹和日誌結構索引都可以用作次級索引。
次級索引可以很容易地從鍵值索引構建。次級索引主要的不同是鍵不是唯一的即可能有許多行文件頂點具有相同的鍵。這可以透過兩種方式來解決將匹配行識別符號的列表作為索引裡的值就像全文索引中的記錄列表或者透過向每個鍵新增行識別符號來使鍵唯一。無論哪種方式B 樹和日誌結構索引都可以用作次級索引。
#### 將值儲存在索引中
@ -312,7 +312,7 @@ B 樹在資料庫架構中是非常根深蒂固的,為許多工作負載都提
**聚集索引**(在索引中儲存所有的行資料)和 **非聚集索引**(僅在索引中儲存對資料的引用)之間的折衷被稱為 **覆蓋索引covering index****包含列的索引index with included columns**其在索引記憶體儲表的一部分列【33】。這允許透過單獨使用索引來處理一些查詢這種情況下可以說索引 **覆蓋cover** 了查詢【32】。
與任何型別的資料重複一樣,聚集索引和覆蓋索引可以加快讀取速度,但是它們需要額外的儲存空間,並且會增加寫入開銷。資料庫還需要額外的努力來執行事務保證,因為應用程式不應看到任何因為重複而導致的不一致。
與任何型別的資料重複一樣,聚集索引和覆蓋索引可以加快讀取速度,但是它們需要額外的儲存空間,並且會增加寫入開銷。資料庫還需要額外的努力來執行事務保證,因為應用程式不應看到任何因為使用副本而導致的不一致。
#### 多列索引
@ -329,15 +329,15 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
一個標準的 B 樹或者 LSM 樹索引不能夠高效地處理這種查詢:它可以返回一個緯度範圍內的所有餐館(但經度可能是任意值),或者返回在同一個經度範圍內的所有餐館(但緯度可能是北極和南極之間的任意地方),但不能同時滿足兩個條件。
一種選擇是使用空間填充曲線將二維位置轉換為單個數字,然後使用常規 B 樹索引【34】。更普遍的是使用特殊化的空間索引例如 R 樹。例如PostGIS 使用 PostgreSQL 的通用 GiST 工具【35】將地理空間索引實現為 R 樹。這裡我們沒有足夠的地方來描述 R 樹,但是有大量的文獻可供參考。
一種選擇是使用 **空間填充曲線space-filling curve** 將二維位置轉換為單個數字,然後使用常規 B 樹索引【34】。更普遍的是使用特殊化的空間索引例如 R 樹。例如PostGIS 使用 PostgreSQL 的通用 GiST 工具【35】將地理空間索引實現為 R 樹。這裡我們沒有足夠的地方來描述 R 樹,但是有大量的文獻可供參考。
有趣的是,多維索引不僅可以用於地理位置。例如,在電子商務網站上可以使用建立在(紅,綠,藍)維度上的三維索引來搜尋特定顏色範圍內的產品,也可以在天氣觀測資料庫中建立(日期,溫度)的二維索引,以便有效地搜尋 2013 年內的溫度在 25 至 30°C 之間的所有觀測資料。如果使用一維索引,你將不得不掃描 2013 年的所有記錄(不管溫度如何),然後透過溫度進行過濾,或者反之亦然。 二維索引可以同時透過時間戳和溫度來收窄資料集。這個技術被 HyperDex 所使用【36】。
有趣的是,多維索引不僅可以用於地理位置。例如,在電子商務網站上可以使用建立在(紅,綠,藍)維度上的三維索引來搜尋特定顏色範圍內的產品,也可以在天氣觀測資料庫中建立(日期,溫度)的二維索引,以便有效地搜尋 2013 年內的溫度在 25 至 30°C 之間的所有觀測資料。如果使用一維索引,你將不得不掃描 2013 年的所有記錄(不管溫度如何),然後透過溫度進行過濾,或者反之亦然。二維索引可以同時透過時間戳和溫度來收窄資料集。這個技術被 HyperDex 所使用【36】。
#### 全文搜尋和模糊索引
到目前為止所討論的所有索引都假定你有確切的資料,並允許你查詢鍵的確切值或具有排序順序的鍵的值範圍。他們不允許你做的是搜尋類似的鍵,如拼寫錯誤的單詞。這種模糊的查詢需要不同的技術。
到目前為止所討論的所有索引都假定你有確切的資料,並允許你查詢鍵的確切值或具有排序順序的鍵的值範圍。他們不允許你做的是搜尋**類似**的鍵,如拼寫錯誤的單詞。這種模糊的查詢需要不同的技術。
例如,全文搜尋引擎通常允許搜尋一個單詞擴充套件為包括該單詞的同義詞,忽略單詞的語法變體,搜尋在相同文件中彼此靠近的單詞的出現並且支援各種其他取決於文字的語言分析功能。為了處理文件或查詢中的拼寫錯誤Lucene 能夠在一定的編輯距離(編輯距離 1 意味著新增刪除或替換了一個字母內搜尋文字【37】
例如,全文搜尋引擎通常允許搜尋目標從一個單詞擴充套件為包括該單詞的同義詞,忽略單詞的語法變體,搜尋在相同文件中的近義詞並且支援各種其他取決於文字的語言分析功能。為了處理文件或查詢中的拼寫錯誤Lucene 能夠在一定的編輯距離內搜尋文字【37】編輯距離 1 意味著單詞內發生了 1 個字母的新增、刪除或替換)
正如 “[用 SSTables 製作 LSM 樹](#用SSTables製作LSM樹)” 中所提到的Lucene 為其詞典使用了一個類似於 SSTable 的結構。這個結構需要一個小的記憶體索引,告訴查詢需要在排序檔案中哪個偏移量查詢鍵。在 LevelDB 中,這個記憶體中的索引是一些鍵的稀疏集合,但在 Lucene 中,記憶體中的索引是鍵中字元的有限狀態自動機,類似於 trie 【38】。這個自動機可以轉換成 Levenshtein 自動機它支援在給定的編輯距離內有效地搜尋單詞【39】。
@ -351,7 +351,7 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
某些記憶體中的鍵值儲存(如 Memcached僅用於快取在重新啟動計算機時丟失的資料是可以接受的。但其他記憶體資料庫的目標是永續性可以透過特殊的硬體例如電池供電的 RAM來實現也可以將更改日誌寫入硬碟還可以將定時快照寫入硬碟或者將記憶體中的狀態複製到其他機器上。
記憶體資料庫重新啟動時,需要從硬碟或透過網路從副本重新載入其狀態(除非使用特殊的硬體)。儘管寫入硬碟,它仍然是一個記憶體資料庫,因為硬碟僅出於永續性目的進行日誌追加,讀取請求完全由記憶體來處理。寫入硬碟同時還有運維上的好處:硬碟上的檔案可以很容易地由外部實用程式進行備份、檢查和分析。
記憶體資料庫重新啟動時,需要從硬碟或透過網路從副本重新載入其狀態(除非使用特殊的硬體)。儘管寫入硬碟,它仍然是一個記憶體資料庫,因為硬碟僅出於永續性目的進行日誌追加,讀取請求完全由記憶體來處理。寫入硬碟同時還有運維上的好處:硬碟上的檔案可以很容易地由外部程式進行備份、檢查和分析。
諸如 VoltDB、MemSQL 和 Oracle TimesTen 等產品是具有關係模型的記憶體資料庫供應商聲稱透過消除與管理硬碟上的資料結構相關的所有開銷他們可以提供巨大的效能改進【41,42】。 RAM Cloud 是一個開源的記憶體鍵值儲存器具有永續性對記憶體和硬碟上的資料都使用日誌結構化方法【43】。 Redis 和 Couchbase 透過非同步寫入硬碟提供了較弱的永續性。
@ -408,7 +408,7 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
幾乎所有的大型企業都有資料倉庫,但在小型企業中幾乎聞所未聞。這可能是因為大多數小公司沒有這麼多不同的 OLTP 系統,大多數小公司只有少量的資料 —— 可以在傳統的 SQL 資料庫中查詢,甚至可以在電子表格中分析。在一家大公司裡,要做一些在一家小公司很簡單的事情,需要很多繁重的工作。
使用單獨的資料倉庫,而不是直接查詢 OLTP 系統進行分析的一大優勢是資料倉庫可針對分析訪問模式進行最佳化。事實證明,本章前半部分討論的索引演算法對於 OLTP 來說工作得很好,但對於處理分析查詢並不是很好。在本章的其餘部分中,我們將研究為分析而最佳化的儲存引擎。
使用單獨的資料倉庫,而不是直接查詢 OLTP 系統進行分析的一大優勢是資料倉庫可針對分析類的訪問模式進行最佳化。事實證明,本章前半部分討論的索引演算法對於 OLTP 來說工作得很好,但對於處理分析查詢並不是很好。在本章的其餘部分中,我們將研究為分析而最佳化的儲存引擎。
#### OLTP資料庫和資料倉庫之間的分歧
@ -416,7 +416,7 @@ SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079
表面上,一個數據倉庫和一個關係型 OLTP 資料庫看起來很相似,因為它們都有一個 SQL 查詢介面。然而,系統的內部看起來可能完全不同,因為它們針對非常不同的查詢模式進行了最佳化。現在許多資料庫供應商都只是重點支援事務處理負載和分析工作負載這兩者中的一個,而不是都支援。
一些資料庫(例如 Microsoft SQL Server 和 SAP HANA支援在同一產品中進行事務處理和資料倉庫。但是它們也正日益成為兩個獨立的儲存和查詢引擎,只是這些引擎正好可以透過一個通用的 SQL 介面訪問【49,50,51】。
一些資料庫(例如 Microsoft SQL Server 和 SAP HANA支援在同一產品中進行事務處理和資料倉庫。但是它們也正日益發展為兩套獨立的儲存和查詢引擎,只是這些引擎正好可以透過一個通用的 SQL 介面訪問【49,50,51】。
Teradata、Vertica、SAP HANA 和 ParAccel 等資料倉庫供應商通常使用昂貴的商業許可證銷售他們的系統。 Amazon RedShift 是 ParAccel 的託管版本。最近,大量的開源 SQL-on-Hadoop 專案已經出現,它們還很年輕,但是正在與商業資料倉庫系統競爭,包括 Apache Hive、Spark SQL、Cloudera Impala、Facebook Presto、Apache Tajo 和 Apache Drill【52,53】。其中一些基於了谷歌 Dremel 的想法【54】。
@ -449,7 +449,7 @@ Teradata、Vertica、SAP HANA 和 ParAccel 等資料倉庫供應商通常使用
如果事實表中有萬億行和數 PB 的資料,那麼高效地儲存和查詢它們就成為一個具有挑戰性的問題。維度表通常要小得多(數百萬行),所以在本節中我們將主要關注事實表的儲存。
儘管事實表通常超過 100 列,但典型的資料倉庫查詢一次只會訪問其中 4 個或 5 個列( “`SELECT *`” 查詢很少用於分析【51】。以 [例 3-1]() 中的查詢為例:它訪問了大量的行(在 2013 日曆年中每次都有人購買水果或糖果),但只需訪問 `fact_sales` 表的三列:`date_key, product_sk, quantity`。該查詢忽略了所有其他的列。
儘管事實表通常超過 100 列,但典型的資料倉庫查詢一次只會訪問其中 4 個或 5 個列( “`SELECT *`” 查詢很少用於分析【51】。以 [例 3-1]() 中的查詢為例:它訪問了大量的行(在 2013 年中所有購買了水果或糖果的記錄),但只需訪問 `fact_sales` 表的三列:`date_key, product_sk, quantity`。該查詢忽略了所有其他的列。
**例 3-1 分析人們是否更傾向於在一週的某一天購買新鮮水果或糖果**
@ -497,7 +497,7 @@ GROUP BY
通常情況下,一列中不同值的數量與行數相比要小得多(例如,零售商可能有數十億的銷售交易,但只有 100,000 個不同的產品)。現在我們可以拿一個有 n 個不同值的列,並把它轉換成 n 個獨立的點陣圖:每個不同值對應一個位圖,每行對應一個位元位。如果該行具有該值,則該位為 1否則為 0。
如果 n 非常小(例如,國家 / 地區列可能有大約 200 個不同的值),則這些點陣圖可以將每行儲存成一個位元位。但是,如果 n 更大,大部分點陣圖中將會有很多的零(我們說它們是稀疏的)。在這種情況下,點陣圖可以另外再進行遊程編碼,如 [圖 3-11](fig3-11.png) 底部所示。這可以使列的編碼非常緊湊。
如果 n 非常小(例如,國家 / 地區列可能有大約 200 個不同的值),則這些點陣圖可以將每行儲存成一個位元位。但是,如果 n 更大,大部分點陣圖中將會有很多的零(我們說它們是稀疏的)。在這種情況下,點陣圖可以另外再進行遊程編碼run-length encoding一種無損資料壓縮技術,如 [圖 3-11](fig3-11.png) 底部所示。這可以使列的編碼非常緊湊。
這些點陣圖索引非常適合資料倉庫中常見的各種查詢。例如:
@ -522,28 +522,28 @@ WHERE product_sk = 31 AND store_sk = 3
#### 記憶體頻寬和向量化處理
對於需要掃描數百萬行的資料倉庫查詢來說,一個巨大的瓶頸是從硬盤獲取資料到記憶體的頻寬。但是,這不是唯一的瓶頸。分析型資料庫的開發人員還需要有效地利用主儲存器到 CPU 快取的頻寬,避免 CPU 指令處理流水線中的分支預測錯誤和氣泡,以及在現代 CPU 上使用單指令多資料SIMD指令【59,60】。
對於需要掃描數百萬行的資料倉庫查詢來說,一個巨大的瓶頸是從硬盤獲取資料到記憶體的頻寬。但是,這不是唯一的瓶頸。分析型資料庫的開發人員還需要有效地利用記憶體到 CPU 快取的頻寬,避免 CPU 指令處理流水線中的分支預測錯誤和閒置等待,以及在現代 CPU 上使用單指令多資料SIMD指令來加速運算【59,60】。
除了減少需要從硬碟載入的資料量以外,列式儲存佈局也可以有效利用 CPU 週期。例如,查詢引擎可以將大量壓縮的列資料放在 CPU 的 L1 快取中,然後在緊密的迴圈(即沒有函式呼叫)中遍歷。相比較每個記錄的處理都需要大量函式呼叫和條件判斷的程式碼CPU 執行這樣一個迴圈要快得多。列壓縮允許列中的更多行被放進相同數量的 L1 快取。前面描述的按位 “與” 和 “或” 運算子可以被設計為直接在這樣的壓縮列資料塊上操作。這種技術被稱為向量化處理【58,49】。
除了減少需要從硬碟載入的資料量以外,列式儲存佈局也可以有效利用 CPU 週期。例如,查詢引擎可以將一整塊壓縮好的列資料放進 CPU 的 L1 快取中,然後在緊密的迴圈(即沒有函式呼叫)中遍歷。相比於每條記錄的處理都需要大量函式呼叫和條件判斷的程式碼CPU 執行這樣一個迴圈要快得多。列壓縮允許列中的更多行被同時放進容量有限的 L1 快取。前面描述的按位 “與” 和 “或” 運算子可以被設計為直接在這樣的壓縮列資料塊上操作。這種技術被稱為向量化處理vectorized processing【58,49】。
### 列式儲存中的排序順序
在列式儲存中,儲存行的順序並不一定很重要。按插入順序儲存它們是最簡單的,因為插入一個新行只需要追加到每個列檔案。但是,我們可以選擇增加一個特定的順序,就像我們之前對 SSTables 所做的那樣,並將其用作索引機制。
在列式儲存中,儲存行的順序並不關鍵。按插入順序儲存它們是最簡單的,因為插入一個新行只需要追加到每個列檔案。但是,我們也可以選擇按某種順序來排列資料,就像我們之前對 SSTables 所做的那樣,並將其用作索引機制。
注意,每列獨自排序是沒有意義的,因為那樣我們就沒法知道不同列中的哪些項屬於同一行。我們只能在知道一列中的第 k 項與另一列中的第 k 項屬於同一行的情況才能重建出完整的行。
注意,對每列分別執行排序是沒有意義的,因為那樣就沒法知道不同列中的哪些項屬於同一行。我們只能在明確一列中的第 k 項與另一列中的第 k 項屬於同一行的情況下,才能重建出完整的行。
相反,即使按列式儲存資料,也需要一次對整行進行排序。資料庫的管理員可以根據他們對常用查詢的瞭解來選擇表格應該被排序的列。例如,如果查詢通常以日期範圍為目標,例如上個月,則可以將 `date_key` 作為第一個排序鍵。這樣查詢最佳化器就可以只掃描上個月的行了,這比掃描所有行要快得多。
相反,資料的排序需要對一整行統一操作,即使它們的儲存方式是按列的。資料庫管理員可以根據他們對常用查詢的瞭解,來選擇表格中用來排序的列。例如,如果查詢通常以日期範圍為目標,例如上個月,則可以將 `date_key` 作為第一個排序鍵。這樣查詢最佳化器就可以只掃描近1個月範圍的行了,這比掃描所有行要快得多。
對於第一排序列中具有相同值的行,可以用第二排序列來進一步排序。例如,如果 `date_key` 是 [圖 3-10](../img/fig3-10.png) 中的第一個排序關鍵字,那麼 `product_sk` 可能是第二個排序關鍵字,以便同一天的同一產品的所有銷售都將在儲存中組合在一起。這將有助於需要在特定日期範圍內按產品對銷售進行分組或過濾的查詢。
對於第一排序列中具有相同值的行,可以用第二排序列來進一步排序。例如,如果 `date_key` 是 [圖 3-10](../img/fig3-10.png) 中的第一個排序關鍵字,那麼 `product_sk` 可能是第二個排序關鍵字,以便同一天的同一產品的所有銷售資料都被儲存在相鄰位置。這將有助於需要在特定日期範圍內按產品對銷售進行分組或過濾的查詢。
序順序的另一個好處是它可以幫助壓縮列。如果主要排序列沒有太多個不同的值,那麼在排序之後,它將具有很長的序列,其中相同的值連續重複多次。一個簡單的遊程編碼(就像我們用於 [圖 3-11](../img/fig3-11.png) 中的點陣圖一樣)可以將該列壓縮到幾千位元組 —— 即使表中有數十億行。
按順序排序的另一個好處是它可以幫助壓縮列。如果主要排序列沒有太多個不同的值,那麼在排序之後,將會得到一個相同的值連續重複多次的序列。一個簡單的遊程編碼(就像我們用於 [圖 3-11](../img/fig3-11.png) 中的點陣圖一樣)可以將該列壓縮到幾 KB —— 即使表中有數十億行。
第一個排序鍵的壓縮效果最強。第二和第三個排序鍵會更混亂,因此不會有這麼長的連續的重複值。排序優先順序更低的列以基本上隨機的順序出現,所以它們可能不會被壓縮。但前幾列排序在整體上仍然是有好處的。
第一個排序鍵的壓縮效果最強。第二和第三個排序鍵會更混亂,因此不會有這麼長的連續的重複值。排序優先順序更低的列以幾乎隨機的順序出現,所以可能不會被壓縮。但對前幾列做排序在整體上仍然是有好處的。
#### 幾個不同的排序順序
C-Store 中引入了這個想法的一個巧妙擴充套件,並在商業資料倉庫 Vertica 中被採用【61,62】。不同的查詢受益於不同的排序順序,為什麼不以幾種不同的方式來儲存相同的資料呢?無論如何,資料需要複製到多臺機器,這樣,如果一臺機器發生故障,你不會丟失資料。你可能還需要儲存以不同方式排序的冗餘資料,以便在處理查詢時,可以使用最適合查詢模式的版本。
對這個想法,有一個巧妙的擴充套件被 C-Store 發現,並在商業資料倉庫 Vertica 中被採用【61,62】既然不同的查詢受益於不同的排序順序,為什麼不以幾種不同的方式來儲存相同的資料呢?反正資料都需要做備份,以防單點故障時丟失資料。因此你可以用不同排序方式來儲存冗餘資料,以便在處理查詢時,呼叫最適合查詢模式的版本。
在一個列式儲存中有多個排序順序有點類似於在一個面向行的儲存中有多個次級索引。但最大的區別在於面向行的儲存將每一行儲存在一個地方(在堆檔案或聚集索引中),次級索引只包含指向匹配行的指標。在列式儲存中,通常在其他地方沒有任何指向資料的指標,只有包含值的列。
@ -555,17 +555,17 @@ C-Store 中引入了這個想法的一個巧妙擴充套件,並在商業資料
幸運的是本章前面已經看到了一個很好的解決方案LSM 樹。所有的寫操作首先進入一個記憶體中的儲存,在這裡它們被新增到一個已排序的結構中,並準備寫入硬碟。記憶體中的儲存是面向行還是列的並不重要。當已經積累了足夠的寫入資料時,它們將與硬碟上的列檔案合併,並批次寫入新檔案。這基本上是 Vertica 所做的【62】。
查詢需要檢查硬碟上的列資料和最近在記憶體中的寫入,並將兩者結合起來。但是,查詢最佳化器對使用者隱藏了這個細節。從分析師的角度來看,透過插入、更新或刪除操作進行修改的資料會立即反映在後續的查詢中。
查詢操作需要檢查硬碟上的列資料和記憶體中的最近寫入,並將兩者起來。但是,查詢最佳化器對使用者隱藏了這個細節。從分析師的角度來看,透過插入、更新或刪除操作進行修改的資料會立即反映在後續的查詢中。
### 聚合:資料立方體和物化檢視
不是每個資料倉庫都必定是一個列式儲存傳統的面向行的資料庫和其他一些架構也被使用。然而列式儲存可以顯著加快專門的分析查詢所以它正在迅速變得流行起來【51,63】。
非所有資料倉庫都需要採用列式儲存傳統的面向行的資料庫和其他一些架構也被使用。然而列式儲存可以顯著加快專門的分析查詢所以它正在迅速變得流行起來【51,63】。
資料倉庫的另一個值得一提的方面是物化彙總materialized aggregates。如前所述資料倉庫查詢通常涉及一個聚合函式如 SQL 中的 COUNT、SUM、AVG、MIN 或 MAX。如果相同的聚合被許多不同的查詢使用那麼每次都透過原始資料來處理可能太浪費了。為什麼不將一些查詢使用最頻繁的計數或總和快取起來
資料倉庫的另一個值得一提的方面是物化聚合materialized aggregates。如前所述資料倉庫查詢通常涉及一個聚合函式如 SQL 中的 COUNT、SUM、AVG、MIN 或 MAX。如果相同的聚合被許多不同的查詢使用那麼每次都透過原始資料來處理可能太浪費了。為什麼不將一些查詢使用最頻繁的計數或總和快取起來
建立這種快取的一種方式是物化檢視Materialized View。在關係資料模型中它通常被定義為一個標準虛擬檢視一個類似於表的物件其內容是一些查詢的結果。不同的是物化檢視是查詢結果的實際副本會被寫入硬碟而虛擬檢視只是編寫查詢的一個捷徑。從虛擬檢視讀取時SQL 引擎會將其展開到檢視的底層查詢中,然後再處理展開的查詢。
當底層資料發生變化時,物化檢視需要更新,因為它是資料的非規範化副本。資料庫可以自動完成該操作,但是這樣的更新使得寫入成本更高,這就是在 OLTP 資料庫中不經常使用物化檢視的原因。在讀取繁重的資料倉庫中,它們可能更有意義(它們是否實際上改善了讀取效能取決於個別情況)。
當底層資料發生變化時,物化檢視需要更新,因為它是資料的非規範化副本。資料庫可以自動完成該操作,但是這樣的更新使得寫入成本更高,這就是在 OLTP 資料庫中不經常使用物化檢視的原因。在讀取繁重的資料倉庫中,它們可能更有意義(它們是否實際上改善了讀取效能取決於使用場景)。
物化檢視的常見特例稱為資料立方體或 OLAP 立方【64】。它是按不同維度分組的聚合網格。[圖 3-12](../img/fig3-12.png) 顯示了一個例子。
@ -573,7 +573,7 @@ C-Store 中引入了這個想法的一個巧妙擴充套件,並在商業資料
**圖 3-12 資料立方的兩個維度,透過求和聚合**
想象一下,現在每個事實都只有兩個維度表的外來鍵 —— 在 [圖 3-12](../img/fig-3-12.png) 中分別是日期和產品。你現在可以繪製一個二維表格,一個軸線上是日期,另一個軸線上是產品。每個單元格包含具有該日期 - 產品組合的所有事實的屬性(例如 `net_price`)的聚(例如 `SUM`)。然後,你可以沿著每行或每列應用相同的彙總,並獲得減少了一個維度的彙總(按產品的銷售額,無論日期,或者按日期的銷售額,無論產品)。
想象一下,現在每個事實都只有兩個維度表的外來鍵 —— 在 [圖 3-12](../img/fig-3-12.png) 中分別是日期和產品。你現在可以繪製一個二維表格,一個軸線上是日期,另一個軸線上是產品。每個單元格包含具有該日期 - 產品組合的所有事實的屬性(例如 `net_price`)的聚(例如 `SUM`)。然後,你可以沿著每行或每列應用相同的彙總,並獲得減少了一個維度的彙總(按產品的銷售額,無論日期,或者按日期的銷售額,無論產品)。
一般來說,事實往往有兩個以上的維度。在圖 3-9 中有五個維度:日期、產品、商店、促銷和客戶。要想象一個五維超立方體是什麼樣子是很困難的,但是原理是一樣的:每個單元格都包含特定日期 - 產品 - 商店 - 促銷 - 客戶組合的銷售額。這些值可以在每個維度上求和彙總。
@ -589,7 +589,7 @@ C-Store 中引入了這個想法的一個巧妙擴充套件,並在商業資料
在高層次上,我們看到儲存引擎分為兩大類:針對 **事務處理OLTP** 最佳化的儲存引擎和針對 **線上分析OLAP** 最佳化的儲存引擎。這兩類使用場景的訪問模式之間有很大的區別:
* OLTP 系統通常面向終端使用者,這意味著系統可能會收到大量的請求。為了處理負載,應用程式在每個查詢中通常只訪問少量的記錄。應用程式使用某種鍵來請求記錄,儲存引擎使用索引來查詢所請求的鍵的資料。硬碟查詢時間往往是這裡的瓶頸。
* 資料倉庫和類似的分析系統會低調一些,因為它們主要由業務分析人員使用,而不是終端使用者。它們的查詢量要比 OLTP 系統少得多,但通常每個查詢開銷高昂,需要在短時間內掃描數百萬條記錄。硬碟頻寬(而不是查詢時間)往往是瓶頸,列式儲存是針對這種工作負載的日益流行的解決方案。
* 資料倉庫和類似的分析系統會少見一些,因為它們主要由業務分析人員使用,而不是終端使用者。它們的查詢量要比 OLTP 系統少得多,但通常每個查詢開銷高昂,需要在短時間內掃描數百萬條記錄。硬碟頻寬(而不是查詢時間)往往是瓶頸,列式儲存是針對這種工作負載的日益流行的解決方案。
在 OLTP 這一邊,我們能看到兩派主流的儲存引擎:
@ -602,7 +602,7 @@ C-Store 中引入了這個想法的一個巧妙擴充套件,並在商業資料
然後,我們暫時放下了儲存引擎的內部細節,查看了典型資料倉庫的高階架構,並說明了為什麼分析工作負載與 OLTP 差別很大:當你的查詢需要在大量行中順序掃描時,索引的重要性就會降低很多。相反,非常緊湊地編碼資料變得非常重要,以最大限度地減少查詢需要從硬碟讀取的資料量。我們討論了列式儲存如何幫助實現這一目標。
作為一名應用程式開發人員,如果你掌握了有關儲存引擎內部的知識,那麼你就能更好地瞭解哪種工具最適合你的特定應用程式。如果你需要調整資料庫的調整引數,這種理解可以讓你設想一個更高或更低的值可能會產生什麼效果。
作為一名應用程式開發人員,如果你掌握了有關儲存引擎內部的知識,那麼你就能更好地瞭解哪種工具最適合你的特定應用程式。當你調整資料庫的最佳化引數時,這種理解讓你能夠設想增減某個值會產生怎樣的效果。
儘管本章不能讓你成為一個特定儲存引擎的調參專家,但它至少大概率使你有了足夠的概念與詞彙儲備去讀懂你所選擇的資料庫的文件。

View File

@ -407,7 +407,7 @@
> #### 自動衝突解決
>
> 衝突解決規則可能很容易變得變得越來越複雜自定義程式碼可能也很容易出錯。亞馬遜是一個經常被引用的例子由於衝突解決處理程式而產生了令人意外的效果一段時間以來購物車上的衝突解決邏輯將保留新增到購物車的物品但不包括從購物車中移除的物品。因此顧客有時會看到物品重新出現在他們的購物車中即使他們之前已經被移走【37】。
> 衝突解決規則可能很容易變得越來越複雜自定義程式碼可能也很容易出錯。亞馬遜是一個經常被引用的例子由於衝突解決處理程式而產生了令人意外的效果一段時間以來購物車上的衝突解決邏輯將保留新增到購物車的物品但不包括從購物車中移除的物品。因此顧客有時會看到物品重新出現在他們的購物車中即使他們之前已經被移走【37】。
>
> 已經有一些有趣的研究來自動解決由於資料修改引起的衝突。有幾項研究值得一提:
>