mirror of
https://github.com/Vonng/ddia.git
synced 2025-09-26 23:09:18 +08:00
Compare commits
4 Commits
685825b316
...
29cd64cb13
Author | SHA1 | Date | |
---|---|---|---|
![]() |
29cd64cb13 | ||
![]() |
e8cfb77135 | ||
![]() |
809dc2c16d | ||
![]() |
1c17625f53 |
@ -49,6 +49,10 @@ def convert_file(src_filepath, dst_filepath, src_folder, dst_folder, cfg='s2twp.
|
||||
.replace('線上性一致', '在線性一致') # 优先按"在线"解析了?
|
||||
.replace('復雜', '複雜')
|
||||
.replace('討論瞭', '討論了')
|
||||
.replace('瞭解釋', '了解釋')
|
||||
.replace('瞭如', '了如') # 引入了如, 實現了如, 了如何, 了如果, 了如此
|
||||
.replace('了如指掌', '瞭如指掌') # 针对上一行的例外情况
|
||||
.replace('明瞭', '明了') # 闡明了, 聲明了, 指明了
|
||||
.replace('倒黴', '倒楣')
|
||||
.replace('區域性性', '區域性')
|
||||
.replace('下麵條件', '下面條件') # 优先按"面条"解析了?
|
||||
|
@ -120,34 +120,34 @@ breadcrumbs: false
|
||||
|
||||
相比之下,**資料倉庫** 是一個單獨的資料庫,分析師可以隨心所欲地查詢,而不會影響 OLTP 操作 [^7]。正如我們將在[第 4 章](/tw/ch4#ch_storage)中看到的,資料倉庫通常以與 OLTP 資料庫非常不同的方式儲存資料,以最佳化分析中常見的查詢型別。
|
||||
|
||||
資料倉庫包含公司中所有各種 OLTP 系統中資料的只讀副本。資料從 OLTP 資料庫中提取(使用定期資料轉儲或連續更新流),轉換為分析友好的模式,清理,然後載入到資料倉庫中。這種將資料匯入資料倉庫的過程稱為 **提取-轉換-載入**(ETL),如[圖 1-1](/tw/ch1#fig_dwh_etl) 所示。有時 **轉換** 和 **載入** 步驟的順序會互換(即,在載入後在資料倉庫中進行轉換),從而產生 **ELT**。
|
||||
資料倉庫包含公司中所有各種 OLTP 系統中資料的只讀副本。資料從 OLTP 資料庫中提取(使用定期資料轉儲或連續更新流),轉換為分析友好的模式,進行清理,然後載入到資料倉庫中。這種將資料匯入資料倉庫的過程稱為 **提取-轉換-載入**(ETL),如[圖 1-1](/tw/ch1#fig_dwh_etl) 所示。有時 **轉換** 和 **載入** 步驟的順序會互換(即,先載入,再在資料倉庫中進行轉換),從而產生 **ELT**。
|
||||
|
||||
{{< figure src="/fig/ddia_0101.png" id="fig_dwh_etl" caption="圖 1-1. ETL 到資料倉庫的簡化概述。" class="w-full my-4" >}}
|
||||
|
||||
在某些情況下,ETL 過程的資料來源是外部 SaaS 產品,如客戶關係管理(CRM)、電子郵件營銷或信用卡處理系統。在這些情況下,你無法直接訪問原始資料庫,因為它只能透過軟體供應商的 API 訪問。將這些外部系統的資料匯入你自己的資料倉庫可以實現透過 SaaS API 無法實現的分析。SaaS API 的 ETL 通常由專門的資料聯結器服務(如 Fivetran、Singer 或 AirByte)實現。
|
||||
|
||||
一些資料庫系統提供 **混合事務/分析處理**(HTAP),旨在在單個系統中啟用 OLTP 和分析,而無需從一個系統 ETL 到另一個系統 [^8] [^9]。然而,許多 HTAP 系統內部由一個 OLTP 系統與一個單獨的分析系統耦合組成,隱藏在公共介面後面——因此兩者之間的區別對於理解這些系統如何工作仍然很重要。
|
||||
一些資料庫系統提供 **混合事務/分析處理**(HTAP),目標是在單個系統中同時支援 OLTP 和分析,而無需從一個系統 ETL 到另一個系統 [^8] [^9]。然而,許多 HTAP 系統內部由一個 OLTP 系統與一個單獨的分析系統耦合組成,隱藏在公共介面後面——因此兩者之間的區別對於理解這些系統如何工作仍然很重要。
|
||||
|
||||
此外,儘管 HTAP 存在,但由於目標和要求不同,事務型系統和分析型系統之間的分離是常見的。特別是,每個事務型系統擁有自己的資料庫被認為是良好的做法(參見["微服務與 Serverless"](/tw/ch1#sec_introduction_microservices)),導致數百個單獨的事務型資料庫;另一方面,企業通常有一個單一的資料倉庫,以便業務分析師可以在單個查詢中組合來自多個事務型系統的資料。
|
||||
此外,儘管 HTAP 存在,但由於目標和要求不同,事務型系統和分析型系統之間的分離是常見的。特別是,讓每個事務型系統擁有自己的資料庫被認為是良好的做法(參見["微服務與 Serverless"](/tw/ch1#sec_introduction_microservices)),這將導致數百個單獨的事務型資料庫;另一方面,企業通常有一個單一的資料倉庫,以便業務分析師可以在單個查詢中組合來自多個事務型系統的資料。
|
||||
|
||||
因此,HTAP 不會取代資料倉庫。相反,它在同一應用程式需要既執行掃描大量行的分析查詢,又以低延遲讀取和更新單個記錄的場景中很有用。例如,欺詐檢測可能涉及此類工作負載 [^10]。
|
||||
因此,HTAP 不會取代資料倉庫。相反,它在同一應用程式既需要執行掃描大量行的分析查詢,又需要以低延遲讀取和更新單個記錄的場景中很有用。例如,欺詐檢測可能涉及此類工作負載 [^10]。
|
||||
|
||||
事務型系統和分析型系統之間的分離是更廣泛趨勢的一部分:隨著工作負載變得更加苛刻,系統變得更加專業化並針對特定工作負載進行最佳化。通用系統可以舒適地處理小資料量,但規模越大,系統往往變得越專業化 [^11]。
|
||||
|
||||
#### 從資料倉庫到資料湖 {#from-data-warehouse-to-data-lake}
|
||||
|
||||
資料倉庫通常使用透過 SQL 查詢的 **關係** 資料模型(參見[第 3 章](/tw/ch3#ch_datamodels)),可能使用專門的商業智慧軟體。這個模型很適合業務分析師需要進行的查詢型別,但不太適合資料科學家的需求,他們可能需要執行以下任務:
|
||||
資料倉庫通常使用透過 SQL 進行查詢的 **關係** 資料模型(參見[第 3 章](/tw/ch3#ch_datamodels)),可能使用專門的商業智慧軟體。這個模型很適合業務分析師需要進行的查詢型別,但不太適合資料科學家的需求,他們可能需要執行以下任務:
|
||||
|
||||
* 將資料轉換為適合訓練機器學習模型的形式;這通常需要將資料庫表的行和列轉換為稱為 **特徵** 的數值向量或矩陣。以最大化訓練模型效能的方式執行這種轉換的過程稱為 **特徵工程**,它通常需要難以用 SQL 表達的自定義程式碼。
|
||||
* 獲取文字資料(例如,產品評論)並使用自然語言處理技術嘗試從中提取結構化資訊(例如,作者的情感或他們提到的主題)。同樣,他們可能需要使用計算機視覺技術從照片中提取結構化資訊。
|
||||
|
||||
儘管已經努力將機器學習運算元新增到 SQL 資料模型 [^12] 並在關係基礎上構建高效的機器學習系統 [^13],但許多資料科學家不喜歡在資料倉庫等關係資料庫中工作。相反,許多人更喜歡使用 Python 資料分析庫(如 pandas 和 scikit-learn)、統計分析語言(如 R)和分散式分析框架(如 Spark)[^14]。我們將在["資料框、矩陣和陣列"](/tw/ch3#sec_datamodels_dataframes)中進一步討論這些。
|
||||
儘管已經有人在努力將機器學習運算元新增到 SQL 資料模型 [^12] 並在關係基礎上構建高效的機器學習系統 [^13],但許多資料科學家不喜歡在資料倉庫等關係資料庫中工作。相反,許多人更喜歡使用 Python 資料分析庫(如 pandas 和 scikit-learn)、統計分析語言(如 R)和分散式分析框架(如 Spark)[^14]。我們將在["資料框、矩陣和陣列"](/tw/ch3#sec_datamodels_dataframes)中進一步討論這些。
|
||||
|
||||
因此,組織面臨著以適合資料科學家使用的形式提供資料的需求。答案是 **資料湖**:一個集中的資料儲存庫,儲存任何可能對分析有用的資料副本,透過 ETL 過程從事務型系統獲得。與資料倉庫的區別在於,資料湖只是包含檔案,而不強制任何特定的檔案格式或資料模型。資料湖中的檔案可能是資料庫記錄的集合,使用 Avro 或 Parquet 等檔案格式編碼(參見[第 5 章](/tw/ch5#ch_encoding)),但它們同樣可以包含文字、影像、影片、感測器讀數、稀疏矩陣、特徵向量、基因組序列或任何其他型別的資料 [^15]。除了更靈活之外,這通常也比關係資料儲存更便宜,因為資料湖可以使用商品化的檔案儲存,如物件儲存(參見["雲原生系統架構"](/tw/ch1#sec_introduction_cloud_native))。
|
||||
|
||||
ETL 過程已經泛化為 **資料管道**,在某些情況下,資料湖已成為從事務型系統到資料倉庫路徑上的中間站。資料湖包含事務型系統產生的"原始"形式的資料,沒有轉換為關係資料倉庫模式。這種方法的優勢在於,每個資料消費者都可以將原始資料轉換為最適合其需求的形式。它被稱為 **壽司原則**:"原始資料更好"[^16]。
|
||||
ETL 過程已經泛化為 **資料管道**,在某些情況下,資料湖已成為從事務型系統到資料倉庫路徑上的中間站。資料湖包含事務型系統產生的“原始”形式的資料,沒有轉換為關係資料倉庫模式。這種方法的優勢在於,每個資料消費者都可以將原始資料轉換為最適合其需求的形式。它被稱為 **壽司原則**:“原始資料更好”[^16]。
|
||||
|
||||
除了從資料湖載入資料到單獨的資料倉庫之外,還可以直接在資料湖中的檔案上執行典型的資料倉庫工作負載(SQL 查詢和業務分析),以及資料科學/機器學習工作負載。這種架構被稱為 **資料湖倉**,它需要一個查詢執行引擎和一個元資料(例如,模式管理)層來擴充套件資料湖的檔案儲存 [^17]。
|
||||
除了從資料湖載入資料到單獨的資料倉庫之外,還可以直接在資料湖中的檔案上執行典型的資料倉庫工作負載(SQL 查詢和業務分析),以及資料科學和機器學習的工作負載。這種架構被稱為 **資料湖倉**,它需要一個查詢執行引擎和一個元資料(例如,模式管理)層來擴充套件資料湖的檔案儲存 [^17]。
|
||||
|
||||
Apache Hive、Spark SQL、Presto 和 Trino 是這種方法的例子。
|
||||
|
||||
@ -157,7 +157,7 @@ Apache Hive、Spark SQL、Presto 和 Trino 是這種方法的例子。
|
||||
|
||||
此外,分析資料越來越多地不僅作為檔案和關係表提供,還作為事件流(參見[待補充連結])。使用基於檔案的資料分析,你可以定期(例如,每天)重新執行分析以響應資料的變化,但流處理允許分析系統以秒級的速度響應事件。根據應用程式及其時間敏感性,流處理方法可能很有價值,例如識別和阻止潛在的欺詐或濫用活動。
|
||||
|
||||
在某些情況下,分析系統的輸出被提供給事務型系統(這個過程有時被稱為 **反向 ETL** [^19])。例如,在分析系統中訓練的機器學習模型可能會部署到生產環境中,以便為終端使用者生成推薦,例如"購買了 X 的人也購買了 Y"。這種分析系統的部署輸出也被稱為 **資料產品** [^20]。機器學習模型可以使用 TFX、Kubeflow 或 MLflow 等專門工具部署到事務型系統。
|
||||
在某些情況下,分析系統的輸出被提供給事務型系統(這個過程有時被稱為 **反向 ETL** [^19])。例如,在分析系統中訓練的機器學習模型可能會部署到生產環境中,以便為終端使用者生成推薦,例如“購買了 X 的人也購買了 Y”。這種分析系統的部署輸出也被稱為 **資料產品** [^20]。機器學習模型可以使用 TFX、Kubeflow 或 MLflow 等專門工具部署到事務型系統。
|
||||
|
||||
### 權威資料來源與派生資料 {#sec_introduction_derived}
|
||||
|
||||
|
@ -461,7 +461,7 @@ MapReduce 作業的輸出處理遵循同樣的原理。透過將輸入視為不
|
||||
|
||||
### Hadoop與分散式資料庫的對比
|
||||
|
||||
正如我們所看到的,Hadoop 有點像 Unix 的分散式版本,其中 HDFS 是檔案系統,而 MapReduce 是 Unix 程序的怪異實現(總是在 Map 階段和 Reduce 階段執行 `sort` 工具)。我們瞭解瞭如何在這些原語的基礎上實現各種連線和分組操作。
|
||||
正如我們所看到的,Hadoop 有點像 Unix 的分散式版本,其中 HDFS 是檔案系統,而 MapReduce 是 Unix 程序的怪異實現(總是在 Map 階段和 Reduce 階段執行 `sort` 工具)。我們瞭解了如何在這些原語的基礎上實現各種連線和分組操作。
|
||||
|
||||
當 MapReduce 論文發表時【1】,它從某種意義上來說 —— 並不新鮮。我們在前幾節中討論的所有處理和並行連線演算法已經在十多年前所謂的 **大規模並行處理(MPP,massively parallel processing)** 資料庫中實現了【3,40】。比如 Gamma database machine、Teradata 和 Tandem NonStop SQL 就是這方面的先驅【52】。
|
||||
|
||||
@ -477,7 +477,7 @@ MapReduce 作業的輸出處理遵循同樣的原理。透過將輸入視為不
|
||||
|
||||
這個想法與資料倉庫類似(請參閱 “[資料倉庫](/tw/ch3#資料倉庫)”):將大型組織的各個部分的資料集中在一起是很有價值的,因為它可以跨越以前相互分離的資料集進行連線。MPP 資料庫所要求的謹慎模式設計拖慢了集中式資料收集速度;以原始形式收集資料,稍後再操心模式的設計,能使資料收集速度加快(有時被稱為 “**資料湖(data lake)**” 或 “**企業資料中心(enterprise data hub)**”【55】)。
|
||||
|
||||
不加區分的資料轉儲轉移瞭解釋資料的負擔:資料集的生產者不再需要強制將其轉化為標準格式,資料的解釋成為消費者的問題(**讀時模式** 方法【56】;請參閱 “[文件模型中的模式靈活性](/tw/ch2#文件模型中的模式靈活性)”)。如果生產者和消費者是不同優先順序的不同團隊,這可能是一種優勢。甚至可能不存在一個理想的資料模型,對於不同目的有不同的合適視角。以原始形式簡單地轉儲資料,可以允許多種這樣的轉換。這種方法被稱為 **壽司原則(sushi principle)**:“原始資料更好”【57】。
|
||||
不加區分的資料轉儲轉移了解釋資料的負擔:資料集的生產者不再需要強制將其轉化為標準格式,資料的解釋成為消費者的問題(**讀時模式** 方法【56】;請參閱 “[文件模型中的模式靈活性](/tw/ch2#文件模型中的模式靈活性)”)。如果生產者和消費者是不同優先順序的不同團隊,這可能是一種優勢。甚至可能不存在一個理想的資料模型,對於不同目的有不同的合適視角。以原始形式簡單地轉儲資料,可以允許多種這樣的轉換。這種方法被稱為 **壽司原則(sushi principle)**:“原始資料更好”【57】。
|
||||
|
||||
因此,Hadoop 經常被用於實現 ETL 過程(請參閱 “[資料倉庫](/tw/ch3#資料倉庫)”):事務處理系統中的資料以某種原始形式轉儲到分散式檔案系統中,然後編寫 MapReduce 作業來清理資料,將其轉換為關係形式,並將其匯入 MPP 資料倉庫以進行分析。資料建模仍然在進行,但它在一個單獨的步驟中進行,與資料收集相解耦。這種解耦是可行的,因為分散式檔案系統支援以任何格式編碼的資料。
|
||||
|
||||
|
@ -48,7 +48,7 @@ breadcrumbs: false
|
||||
|
||||
例如,你可能會首先將資料寫入 **記錄系統** 資料庫,捕獲對該資料庫所做的變更(請參閱 “[變更資料捕獲](/tw/ch12#變更資料捕獲)”),然後將變更以相同的順序應用於搜尋索引。如果變更資料捕獲(CDC)是更新索引的唯一方式,則可以確定該索引完全派生自記錄系統,因此與其保持一致(除軟體錯誤外)。寫入資料庫是向該系統提供新輸入的唯一方式。
|
||||
|
||||
允許應用程式直接寫入搜尋索引和資料庫引入瞭如 [圖 11-4](/fig/ddia_1204.png) 所示的問題,其中兩個客戶端同時傳送衝突的寫入,且兩個儲存系統按不同順序處理它們。在這種情況下,既不是資料庫說了算,也不是搜尋索引說了算,所以它們做出了相反的決定,進入彼此間永續性的不一致狀態。
|
||||
允許應用程式直接寫入搜尋索引和資料庫引入了如 [圖 11-4](/fig/ddia_1204.png) 所示的問題,其中兩個客戶端同時傳送衝突的寫入,且兩個儲存系統按不同順序處理它們。在這種情況下,既不是資料庫說了算,也不是搜尋索引說了算,所以它們做出了相反的決定,進入彼此間永續性的不一致狀態。
|
||||
|
||||
如果你可以透過單個系統來提供所有使用者輸入,從而決定所有寫入的排序,則透過按相同順序處理寫入,可以更容易地派生出其他資料表示。這是狀態機複製方法的一個應用,我們在 “[全序廣播](/tw/ch9#全序廣播)” 中看到。無論你使用變更資料捕獲還是事件溯源日誌,都不如簡單的基於全序的決策原則更重要。
|
||||
|
||||
@ -181,7 +181,7 @@ Unix 和關係資料庫以非常不同的哲學來處理資訊管理問題。Uni
|
||||
* 複製日誌,保持其他節點上資料的副本最新(請參閱 “[複製日誌的實現](/tw/ch5#複製日誌的實現)”)
|
||||
* 全文檢索索引,允許在文字中進行關鍵字搜尋(請參閱 “[全文檢索與模糊索引](/tw/ch3#全文檢索與模糊索引)”),也內置於某些關係資料庫【1】
|
||||
|
||||
在 [第十一章](/tw/ch11) 和 [第十二章](/tw/ch12) 中,出現了類似的主題。我們討論了如何構建全文檢索索引(請參閱 “[批處理工作流的輸出](/tw/ch11#批處理工作流的輸出)”),瞭解瞭如何維護物化檢視(請參閱 “[維護物化檢視](/tw/ch12#維護物化檢視)”)以及如何將變更從資料庫複製到派生資料系統(請參閱 “[變更資料捕獲](/tw/ch12#變更資料捕獲)”)。
|
||||
在 [第十一章](/tw/ch11) 和 [第十二章](/tw/ch12) 中,出現了類似的主題。我們討論了如何構建全文檢索索引(請參閱 “[批處理工作流的輸出](/tw/ch11#批處理工作流的輸出)”),瞭解了如何維護物化檢視(請參閱 “[維護物化檢視](/tw/ch12#維護物化檢視)”)以及如何將變更從資料庫複製到派生資料系統(請參閱 “[變更資料捕獲](/tw/ch12#變更資料捕獲)”)。
|
||||
|
||||
資料庫中內建的功能與人們用批處理和流處理器構建的派生資料系統似乎有相似之處。
|
||||
|
||||
|
@ -17,7 +17,7 @@ breadcrumbs: false
|
||||
1. 作為應用程式開發者,你觀察現實世界(其中有人員、組織、貨物、行為、資金流動、感測器等),並用物件或資料結構,以及操作這些資料結構的 API 來建模。這些結構通常是特定於應用程式的。
|
||||
2. 當你想要儲存這些資料結構時,你用通用的資料模型來表達它們,例如 JSON 或 XML 文件、關係資料庫中的表,或者圖中的頂點和邊。這些資料模型是本章的主題。
|
||||
3. 構建你的資料庫軟體的工程師決定了如何用記憶體、磁碟或網路上的位元組來表示文件/關係/圖資料。這種表示可能允許以各種方式查詢、搜尋、操作和處理資料。我們將在 [第 4 章](/tw/ch4#ch_storage) 中討論這些儲存引擎的設計。
|
||||
4. 在更低的層次上,硬體工程師已經想出瞭如何用電流、光脈衝、磁場等來表示位元組的方法。
|
||||
4. 在更低的層次上,硬體工程師已經想出了如何用電流、光脈衝、磁場等來表示位元組的方法。
|
||||
|
||||
在複雜的應用程式中可能有更多的中間層,例如基於 API 之上的 API,但基本思想仍然相同:每一層透過提供一個簡潔的資料模型來隱藏下層的複雜性。這些抽象允許不同的人群 —— 例如,資料庫供應商的工程師和使用他們資料庫的應用程式開發者 —— 有效地合作。
|
||||
|
||||
@ -499,7 +499,7 @@ CREATE
|
||||
|
||||
當 [圖 3-6](/tw/ch3#fig_datamodels_graph) 的所有頂點和邊都新增到資料庫後,我們可以開始提出有趣的問題:例如,*查詢所有從美國移民到歐洲的人的姓名*。也就是說,找到所有具有指向美國境內位置的 `BORN_IN` 邊,以及指向歐洲境內位置的 `LIVING_IN` 邊的頂點,並返回每個頂點的 `name` 屬性。
|
||||
|
||||
[示例 3-5](/tw/ch3#fig_cypher_query) 顯示瞭如何在 Cypher 中表達該查詢。相同的箭頭符號用於 `MATCH` 子句中以在圖中查詢模式:`(person) -[:BORN_IN]-> ()` 匹配由標記為 `BORN_IN` 的邊相關的任意兩個頂點。該邊的尾頂點繫結到變數 `person`,頭頂點未命名。
|
||||
[示例 3-5](/tw/ch3#fig_cypher_query) 顯示了如何在 Cypher 中表達該查詢。相同的箭頭符號用於 `MATCH` 子句中以在圖中查詢模式:`(person) -[:BORN_IN]-> ()` 匹配由標記為 `BORN_IN` 的邊相關的任意兩個頂點。該邊的尾頂點繫結到變數 `person`,頭頂點未命名。
|
||||
|
||||
{{< figure id="fig_cypher_query" title="示例 3-5. Cypher 查詢查詢從美國移民到歐洲的人" class="w-full my-4" >}}
|
||||
|
||||
@ -741,7 +741,7 @@ Datalog 實際上基於關係資料模型,而不是圖,但它出現在本書
|
||||
|
||||
Datalog 資料庫的內容由 *事實* 組成,每個事實對應於關係表中的一行。例如,假設我們有一個包含位置的表 *location*,它有三列:*ID*、*name* 和 *type*。美國是一個國家的事實可以寫成 `location(2, "United States", "country")`,其中 `2` 是美國的 ID。一般來說,語句 `table(val1, val2, …)` 意味著 `table` 包含一行,其中第一列包含 `val1`,第二列包含 `val2`,依此類推。
|
||||
|
||||
[示例 3-11](/tw/ch3#fig_datalog_triples) 顯示瞭如何在 Datalog 中編寫 [圖 3-6](/tw/ch3#fig_datamodels_graph) 左側的資料。圖的邊(`within`、`born_in` 和 `lives_in`)表示為兩列連線表。例如,Lucy 的 ID 是 100,愛達荷州的 ID 是 3,所以關係"Lucy 出生在愛達荷州"表示為 `born_in(100, 3)`。
|
||||
[示例 3-11](/tw/ch3#fig_datalog_triples) 顯示了如何在 Datalog 中編寫 [圖 3-6](/tw/ch3#fig_datamodels_graph) 左側的資料。圖的邊(`within`、`born_in` 和 `lives_in`)表示為兩列連線表。例如,Lucy 的 ID 是 100,愛達荷州的 ID 是 3,所以關係"Lucy 出生在愛達荷州"表示為 `born_in(100, 3)`。
|
||||
|
||||
{{< figure id="fig_datalog_triples" title="示例 3-11. [圖 3-6](/tw/ch3#fig_datamodels_graph) 中資料的子集,表示為 Datalog 事實" class="w-full my-4" >}}
|
||||
|
||||
@ -805,7 +805,7 @@ GraphQL 是一種查詢語言,從設計上講,它比我們在本章中看到
|
||||
|
||||
GraphQL 的靈活性是有代價的。採用 GraphQL 的組織通常需要工具將 GraphQL 查詢轉換為對內部服務的請求,這些服務通常使用 REST 或 gRPC(參見 [第 5 章](/tw/ch5#ch_encoding))。授權、速率限制和效能挑戰是額外的關注點 [^61]。GraphQL 的查詢語言也受到限制,因為 GraphQL 來自不受信任的來源。該語言不允許任何可能執行成本高昂的操作,否則使用者可能透過執行大量昂貴的查詢對伺服器執行拒絕服務攻擊。特別是,GraphQL 不允許遞迴查詢(與 Cypher、SPARQL、SQL 或 Datalog 不同),並且不允許任意搜尋條件,如"查詢在美國出生並現在居住在歐洲的人"(除非服務所有者特別選擇提供此類搜尋功能)。
|
||||
|
||||
儘管如此,GraphQL 還是很有用的。[示例 3-13](/tw/ch3#fig_graphql_query) 顯示瞭如何使用 GraphQL 實現 Discord 或 Slack 等群聊應用程式。查詢請求使用者有權訪問的所有頻道,包括頻道名稱和每個頻道中的 50 條最新訊息。對於每條訊息,它請求時間戳、訊息內容以及訊息傳送者的姓名和個人資料圖片 URL。此外,如果訊息是對另一條訊息的回覆,查詢還會請求傳送者姓名和它所回覆的訊息內容(可能以較小的字型呈現在回覆上方,以提供一些上下文)。
|
||||
儘管如此,GraphQL 還是很有用的。[示例 3-13](/tw/ch3#fig_graphql_query) 顯示了如何使用 GraphQL 實現 Discord 或 Slack 等群聊應用程式。查詢請求使用者有權訪問的所有頻道,包括頻道名稱和每個頻道中的 50 條最新訊息。對於每條訊息,它請求時間戳、訊息內容以及訊息傳送者的姓名和個人資料圖片 URL。此外,如果訊息是對另一條訊息的回覆,查詢還會請求傳送者姓名和它所回覆的訊息內容(可能以較小的字型呈現在回覆上方,以提供一些上下文)。
|
||||
|
||||
{{< figure id="fig_graphql_query" title="示例 3-13. 群聊應用程式的示例 GraphQL 查詢" class="w-full my-4" >}}
|
||||
|
||||
|
@ -131,7 +131,7 @@ JSON 比 XML 更簡潔,但與二進位制格式相比,兩者仍然使用大
|
||||
}
|
||||
```
|
||||
|
||||
讓我們看一個 MessagePack 的例子,它是 JSON 的二進位制編碼。[圖 5-2](/tw/ch5#fig_encoding_messagepack) 顯示瞭如果你使用 MessagePack 編碼 [示例 5-2](/tw/ch5#fig_encoding_json) 中的 JSON 文件所得到的位元組序列。前幾個位元組如下:
|
||||
讓我們看一個 MessagePack 的例子,它是 JSON 的二進位制編碼。[圖 5-2](/tw/ch5#fig_encoding_messagepack) 顯示了如果你使用 MessagePack 編碼 [示例 5-2](/tw/ch5#fig_encoding_json) 中的 JSON 文件所得到的位元組序列。前幾個位元組如下:
|
||||
|
||||
1. 第一個位元組 `0x83` 表示接下來是一個物件(前四位 = `0x80`),有三個欄位(後四位 = `0x03`)。(如果你想知道如果物件有超過 15 個欄位會發生什麼,以至於欄位數無法裝入四位,那麼它會獲得不同的型別指示符,欄位數會以兩個或四個位元組編碼。)
|
||||
2. 第二個位元組 `0xa8` 表示接下來是一個字串(前四位 = `0xa0`),長度為八個位元組(後四位 = `0x08`)。
|
||||
|
@ -128,7 +128,7 @@ ACID 意義上的*隔離性*意味著同時執行的事務彼此隔離:它們
|
||||
|
||||
### 單物件與多物件操作 {#sec_transactions_multi_object}
|
||||
|
||||
回顧一下,在 ACID 中,原子性和隔離性描述瞭如果客戶端在同一事務中進行多次寫入,資料庫應該做什麼:
|
||||
回顧一下,在 ACID 中,原子性和隔離性描述了如果客戶端在同一事務中進行多次寫入,資料庫應該做什麼:
|
||||
|
||||
原子性
|
||||
: 如果在寫入序列的中途發生錯誤,事務應該被中止,並且到該點為止所做的寫入應該被丟棄。換句話說,資料庫讓你免於擔心部分失敗,透過提供全有或全無的保證。
|
||||
@ -163,7 +163,7 @@ SELECT COUNT(*) FROM emails WHERE recipient_id = 2 AND unread_flag = true
|
||||
當單個物件被更改時,原子性和隔離性也適用。例如,假設你正在向資料庫寫入 20 KB 的 JSON 文件:
|
||||
|
||||
* 如果在傳送了前 10 KB 後網路連線中斷,資料庫是否儲存了無法解析的 10 KB JSON 片段?
|
||||
* 如果資料庫正在覆蓋磁碟上的先前值的過程中電源失敗,你是否最終會將新舊值拼接在一起?
|
||||
* 如果資料庫正在覆蓋磁碟上的先前值的過程中電源失效,你是否最終會將新舊值拼接在一起?
|
||||
* 如果另一個客戶端在寫入過程中讀取該文件,它會看到部分更新的值嗎?
|
||||
|
||||
這些問題會令人非常困惑,因此儲存引擎幾乎普遍的目標是在一個節點上的單個物件(如鍵值對)上提供原子性和隔離性。原子性可以使用日誌實現崩潰恢復(參見["使 B 樹可靠"](/tw/ch4#sec_storage_btree_wal)),隔離性可以使用每個物件上的鎖來實現(一次只允許一個執行緒訪問物件)。
|
||||
@ -689,7 +689,7 @@ VoltDB 還使用儲存過程進行復制:它不是將事務的寫入從一個
|
||||
* 如果事務首先讀取然後寫入物件,它可以將其共享鎖升級為獨佔鎖。升級的工作方式與直接獲取獨佔鎖相同。
|
||||
* 獲取鎖後,事務必須繼續持有鎖直到事務結束(提交或中止)。這就是"兩階段"名稱的來源:第一階段(事務執行時)是獲取鎖,第二階段(事務結束時)是釋放所有鎖。
|
||||
|
||||
由於使用瞭如此多的鎖,很容易發生事務 A 等待事務 B 釋放其鎖,反之亦然的情況。這種情況稱為*死鎖*。資料庫自動檢測事務之間的死鎖並中止其中一個,以便其他事務可以取得進展。中止的事務需要由應用程式重試。
|
||||
由於使用了如此多的鎖,很容易發生事務 A 等待事務 B 釋放其鎖,反之亦然的情況。這種情況稱為*死鎖*。資料庫自動檢測事務之間的死鎖並中止其中一個,以便其他事務可以取得進展。中止的事務需要由應用程式重試。
|
||||
|
||||
#### 兩階段鎖定的效能 {#performance-of-two-phase-locking}
|
||||
|
||||
|
@ -592,7 +592,7 @@ CREATE INDEX edges_heads ON edges (head_vertex);
|
||||
|
||||
Cypher 是属性图的声明式查询语言,为 Neo4j 图形数据库而发明【37】(它是以电影 “黑客帝国” 中的一个角色来命名的,而与密码学中的加密算法无关【38】)。
|
||||
|
||||
[例 2-3]() 显示了将 [图 2-5](/v1/ddia_0205.png) 的左边部分插入图形数据库的 Cypher 查询。你可以以类似的方式把图的剩余部分添加进去,但这里为了文章可閱读性而省略这部分的示例。每个顶点都有一个像 `USA` 或 `Idaho` 这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) - [:WITHIN] ->(USA)` 创建一条标记为 `WITHIN` 的边,`Idaho` 为尾节点,`USA` 为头节点。
|
||||
[例 2-3]() 显示了将 [图 2-5](/v1/ddia_0205.png) 的左边部分插入图形数据库的 Cypher 查询。你可以以类似的方式把图的剩余部分添加进去,但这里为了文章可閱读性而省略这部分的示例。每个顶点都有一个像 `USA` 或 `Idaho` 这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) -[:WITHIN]-> (USA)` 创建一条标记为 `WITHIN` 的边,`Idaho` 为尾节点,`USA` 为头节点。
|
||||
|
||||
**例 2-3 将图 2-5 中的数据子集表示为 Cypher 查询**
|
||||
|
||||
@ -701,7 +701,7 @@ WITH RECURSIVE
|
||||
|
||||
三元组的主语相当于图中的一个顶点。而宾语是下面两者之一:
|
||||
|
||||
1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓语和宾语相当于主语顶点上的属性的键和值。例如,`(lucy, age, 33)` 就像属性 `{“age”:33}` 的顶点 lucy。
|
||||
1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓语和宾语相当于主语顶点上的属性的键和值。例如,`(lucy, age, 33)` 就像属性 `{"age": 33}` 的顶点 lucy。
|
||||
2. 图中的另一个顶点。在这种情况下,谓语是图中的一条边,主语是其尾部顶点,而宾语是其头部顶点。例如,在 `(lucy, marriedTo, alain)` 中主语和宾语 `lucy` 和 `alain` 都是顶点,并且谓语 `marriedTo` 是连接他们的边的标签。
|
||||
|
||||
[例 2-6]() 展示了与 [例 2-3]() 相同的数据,以称为 Turtle 的格式(Notation3(N3)【39】的一个子集)写成三元组。
|
||||
@ -726,7 +726,7 @@ _:namerica :name "North America"
|
||||
_:namerica :type :"continent"
|
||||
```
|
||||
|
||||
在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确哪些三元组引用了同一顶点。当谓语表示边时,该宾语是一个顶点,如 `_:idaho :within _:usa.`。当谓语是一个属性时,该宾语是一个字符串,如 `_:usa :name"United States"`
|
||||
在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确哪些三元组引用了同一顶点。当谓语表示边时,该宾语是一个顶点,如 `_:idaho :within _:usa.`。当谓语是一个属性时,该宾语是一个字符串,如 `_:usa :name"United States"`
|
||||
|
||||
一遍又一遍地重复相同的主语看起来相当重复,但幸运的是,可以使用分号来说明关于同一主语的多个事情。这使得 Turtle 格式相当不错,可读性强:请参阅 [例 2-7]()。
|
||||
|
||||
@ -786,7 +786,7 @@ _: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,如 `urn:example:within`。幸运的是,你只需在文件顶部对这个前缀做一次声明,后续就不用再管了。
|
||||
从 RDF 的角度来看,URL `<http://my-company.com/namespace>` 不一定需要能解析成什么东西,它只是一个命名空间。为避免与 `http://URL` 混淆,本节中的示例使用不可解析的 URI,如 `urn:example:within`。幸运的是,你只需在文件顶部对这个前缀做一次声明,后续就不用再管了。
|
||||
|
||||
### SPARQL 查询语言
|
||||
|
||||
@ -920,7 +920,7 @@ Cypher 和 SPARQL 使用 SELECT 立即跳转,但是 Datalog 一次只进行一
|
||||
|
||||
文档数据库和图数据库有一个共同点,那就是它们通常不会将存储的数据强制约束为特定模式,这可以使应用程序更容易适应不断变化的需求。但是应用程序很可能仍会假定数据具有一定的结构;区别仅在于模式是**明确的**(写入时强制)还是**隐含的**(读取时处理)。
|
||||
|
||||
每个数据模型都具有各自的查询语言或框架,我们讨论了几个例子:SQL,MapReduce,MongoDB 的聚合管道,Cypher,SPARQL 和 Datalog。我们也谈到了 CSS 和 XSL/XPath,它们不是数据库查询语言,而包含有趣的相似之处。
|
||||
每个数据模型都具有各自的查询语言或框架,我们讨论了几个例子:SQL、MapReduce、MongoDB 的聚合管道、Cypher、SPARQL 和 Datalog。我们也谈到了 CSS 和 XSL/XPath,它们不是数据库查询语言,而包含有趣的相似之处。
|
||||
|
||||
虽然我们已经覆盖了很多层面,但仍然有许多数据模型没有提到。举几个简单的例子:
|
||||
|
||||
|
@ -456,7 +456,7 @@ MapReduce 作業的輸出處理遵循同樣的原理。透過將輸入視為不
|
||||
|
||||
### Hadoop與分散式資料庫的對比
|
||||
|
||||
正如我們所看到的,Hadoop 有點像 Unix 的分散式版本,其中 HDFS 是檔案系統,而 MapReduce 是 Unix 程序的怪異實現(總是在 Map 階段和 Reduce 階段執行 `sort` 工具)。我們瞭解瞭如何在這些原語的基礎上實現各種連線和分組操作。
|
||||
正如我們所看到的,Hadoop 有點像 Unix 的分散式版本,其中 HDFS 是檔案系統,而 MapReduce 是 Unix 程序的怪異實現(總是在 Map 階段和 Reduce 階段執行 `sort` 工具)。我們瞭解了如何在這些原語的基礎上實現各種連線和分組操作。
|
||||
|
||||
當 MapReduce 論文發表時【1】,它從某種意義上來說 —— 並不新鮮。我們在前幾節中討論的所有處理和並行連線演算法已經在十多年前所謂的 **大規模並行處理(MPP,massively parallel processing)** 資料庫中實現了【3,40】。比如 Gamma database machine、Teradata 和 Tandem NonStop SQL 就是這方面的先驅【52】。
|
||||
|
||||
@ -472,7 +472,7 @@ MapReduce 作業的輸出處理遵循同樣的原理。透過將輸入視為不
|
||||
|
||||
這個想法與資料倉庫類似(請參閱 “[資料倉庫](/v1_tw/ch3#資料倉庫)”):將大型組織的各個部分的資料集中在一起是很有價值的,因為它可以跨越以前相互分離的資料集進行連線。MPP 資料庫所要求的謹慎模式設計拖慢了集中式資料收集速度;以原始形式收集資料,稍後再操心模式的設計,能使資料收集速度加快(有時被稱為 “**資料湖(data lake)**” 或 “**企業資料中心(enterprise data hub)**”【55】)。
|
||||
|
||||
不加區分的資料轉儲轉移瞭解釋資料的負擔:資料集的生產者不再需要強制將其轉化為標準格式,資料的解釋成為消費者的問題(**讀時模式** 方法【56】;請參閱 “[文件模型中的模式靈活性](/v1_tw/ch2#文件模型中的模式靈活性)”)。如果生產者和消費者是不同優先順序的不同團隊,這可能是一種優勢。甚至可能不存在一個理想的資料模型,對於不同目的有不同的合適視角。以原始形式簡單地轉儲資料,可以允許多種這樣的轉換。這種方法被稱為 **壽司原則(sushi principle)**:“原始資料更好”【57】。
|
||||
不加區分的資料轉儲轉移了解釋資料的負擔:資料集的生產者不再需要強制將其轉化為標準格式,資料的解釋成為消費者的問題(**讀時模式** 方法【56】;請參閱 “[文件模型中的模式靈活性](/v1_tw/ch2#文件模型中的模式靈活性)”)。如果生產者和消費者是不同優先順序的不同團隊,這可能是一種優勢。甚至可能不存在一個理想的資料模型,對於不同目的有不同的合適視角。以原始形式簡單地轉儲資料,可以允許多種這樣的轉換。這種方法被稱為 **壽司原則(sushi principle)**:“原始資料更好”【57】。
|
||||
|
||||
因此,Hadoop 經常被用於實現 ETL 過程(請參閱 “[資料倉庫](/v1_tw/ch3#資料倉庫)”):事務處理系統中的資料以某種原始形式轉儲到分散式檔案系統中,然後編寫 MapReduce 作業來清理資料,將其轉換為關係形式,並將其匯入 MPP 資料倉庫以進行分析。資料建模仍然在進行,但它在一個單獨的步驟中進行,與資料收集相解耦。這種解耦是可行的,因為分散式檔案系統支援以任何格式編碼的資料。
|
||||
|
||||
|
@ -44,7 +44,7 @@ breadcrumbs: false
|
||||
|
||||
例如,你可能會首先將資料寫入 **記錄系統** 資料庫,捕獲對該資料庫所做的變更(請參閱 “[變更資料捕獲](/v1_tw/ch11#變更資料捕獲)”),然後將變更以相同的順序應用於搜尋索引。如果變更資料捕獲(CDC)是更新索引的唯一方式,則可以確定該索引完全派生自記錄系統,因此與其保持一致(除軟體錯誤外)。寫入資料庫是向該系統提供新輸入的唯一方式。
|
||||
|
||||
允許應用程式直接寫入搜尋索引和資料庫引入瞭如 [圖 11-4](/v1/ddia_1104.png) 所示的問題,其中兩個客戶端同時傳送衝突的寫入,且兩個儲存系統按不同順序處理它們。在這種情況下,既不是資料庫說了算,也不是搜尋索引說了算,所以它們做出了相反的決定,進入彼此間永續性的不一致狀態。
|
||||
允許應用程式直接寫入搜尋索引和資料庫引入了如 [圖 11-4](/v1/ddia_1104.png) 所示的問題,其中兩個客戶端同時傳送衝突的寫入,且兩個儲存系統按不同順序處理它們。在這種情況下,既不是資料庫說了算,也不是搜尋索引說了算,所以它們做出了相反的決定,進入彼此間永續性的不一致狀態。
|
||||
|
||||
如果你可以透過單個系統來提供所有使用者輸入,從而決定所有寫入的排序,則透過按相同順序處理寫入,可以更容易地衍生出其他資料表示。這是狀態機複製方法的一個應用,我們在 “[全序廣播](/v1_tw/ch9#全序廣播)” 中看到。無論你使用變更資料捕獲還是事件溯源日誌,都不如簡單的基於全序的決策原則更重要。
|
||||
|
||||
@ -177,7 +177,7 @@ Unix 和關係資料庫以非常不同的哲學來處理資訊管理問題。Uni
|
||||
* 複製日誌,保持其他節點上資料的副本最新(請參閱 “[複製日誌的實現](/v1_tw/ch5#複製日誌的實現)”)
|
||||
* 全文搜尋索引,允許在文字中進行關鍵字搜尋(請參閱 “[全文搜尋和模糊索引](/v1_tw/ch3#全文搜尋和模糊索引)”),也內置於某些關係資料庫【1】
|
||||
|
||||
在 [第十章](/v1_tw/ch10) 和 [第十一章](/v1_tw/ch11) 中,出現了類似的主題。我們討論了如何構建全文搜尋索引(請參閱 “[批處理工作流的輸出](/v1_tw/ch10#批處理工作流的輸出)”),瞭解瞭如何維護物化檢視(請參閱 “[維護物化檢視](/v1_tw/ch11#維護物化檢視)”)以及如何將變更從資料庫複製到衍生資料系統(請參閱 “[變更資料捕獲](/v1_tw/ch11#變更資料捕獲)”)。
|
||||
在 [第十章](/v1_tw/ch10) 和 [第十一章](/v1_tw/ch11) 中,出現了類似的主題。我們討論了如何構建全文搜尋索引(請參閱 “[批處理工作流的輸出](/v1_tw/ch10#批處理工作流的輸出)”),瞭解了如何維護物化檢視(請參閱 “[維護物化檢視](/v1_tw/ch11#維護物化檢視)”)以及如何將變更從資料庫複製到衍生資料系統(請參閱 “[變更資料捕獲](/v1_tw/ch11#變更資料捕獲)”)。
|
||||
|
||||
資料庫中內建的功能與人們用批處理和流處理器構建的衍生資料系統似乎有相似之處。
|
||||
|
||||
|
@ -71,7 +71,7 @@ breadcrumbs: false
|
||||
|
||||
**圖 2-1 使用關係型模式來表示領英簡介**
|
||||
|
||||
例如,[圖 2-1](/v1/ddia_0201.png) 展示瞭如何在關係模式中表示簡歷(一個 LinkedIn 簡介)。整個簡介可以透過一個唯一的識別符號 `user_id` 來標識。像 `first_name` 和 `last_name` 這樣的欄位每個使用者只出現一次,所以可以在 User 表上將其建模為列。但是,大多數人在職業生涯中擁有多於一份的工作,人們可能有不同樣的教育階段和任意數量的聯絡資訊。從使用者到這些專案之間存在一對多的關係,可以用多種方式來表示:
|
||||
例如,[圖 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 標準增加了對結構化資料型別和 XML 資料的支援;這允許將多值資料儲存在單行內,並支援在這些文件內查詢和索引。這些功能在 Oracle,IBM DB2,MS SQL Server 和 PostgreSQL 中都有不同程度的支援【6,7】。JSON 資料型別也得到多個數據庫的支援,包括 IBM DB2,MySQL 和 PostgreSQL 【8】。
|
||||
@ -167,7 +167,7 @@ JSON 表示比 [圖 2-1](/v1/ddia_0201.png) 中的多表模式具有更好的 **
|
||||
|
||||
**圖 2-3 公司名不僅是字串,還是一個指向公司實體的連結(LinkedIn 截圖)**
|
||||
|
||||
[圖 2-4](/v1/ddia_0204.png) 闡明瞭這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。
|
||||
[圖 2-4](/v1/ddia_0204.png) 闡明了這些新功能需要如何使用多對多關係。每個虛線矩形內的資料可以分組成一個文件,但是對單位,學校和其他使用者的引用需要表示成引用,並且在查詢時需要連線。
|
||||
|
||||

|
||||
|
||||
@ -376,7 +376,7 @@ li.selected > p {
|
||||
}
|
||||
```
|
||||
|
||||
這裡的 CSS 選擇器 `li.selected > p` 聲明瞭我們想要應用藍色樣式的元素的模式:即其直接父元素是具有 CSS 類 `selected` 的 `<li>` 元素的所有 `<p>` 元素。示例中的元素 `<p>Sharks</p>` 匹配此模式,但 `<p>Whales</p>` 不匹配,因為其 `<li>` 父元素缺少 `class="selected"`。
|
||||
這裡的 CSS 選擇器 `li.selected > p` 聲明了我們想要應用藍色樣式的元素的模式:即其直接父元素是具有 CSS 類 `selected` 的 `<li>` 元素的所有 `<p>` 元素。示例中的元素 `<p>Sharks</p>` 匹配此模式,但 `<p>Whales</p>` 不匹配,因為其 `<li>` 父元素缺少 `class="selected"`。
|
||||
|
||||
如果使用 XSL 而不是 CSS,你可以做類似的事情:
|
||||
|
||||
@ -592,7 +592,7 @@ CREATE INDEX edges_heads ON edges (head_vertex);
|
||||
|
||||
Cypher 是屬性圖的宣告式查詢語言,為 Neo4j 圖形資料庫而發明【37】(它是以電影 “駭客帝國” 中的一個角色來命名的,而與密碼學中的加密演算法無關【38】)。
|
||||
|
||||
[例 2-3]() 顯示了將 [圖 2-5](/v1/ddia_0205.png) 的左邊部分插入圖形資料庫的 Cypher 查詢。你可以以類似的方式把圖的剩餘部分新增進去,但這裡為了文章可閱讀性而省略這部分的示例。每個頂點都有一個像 `USA` 或 `Idaho` 這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`(Idaho) - [:WITHIN] ->(USA)` 建立一條標記為 `WITHIN` 的邊,`Idaho` 為尾節點,`USA` 為頭節點。
|
||||
[例 2-3]() 顯示了將 [圖 2-5](/v1/ddia_0205.png) 的左邊部分插入圖形資料庫的 Cypher 查詢。你可以以類似的方式把圖的剩餘部分新增進去,但這裡為了文章可閱讀性而省略這部分的示例。每個頂點都有一個像 `USA` 或 `Idaho` 這樣的符號名稱,查詢的其他部分可以使用這些名稱在頂點之間建立邊,使用箭頭符號:`(Idaho) -[:WITHIN]-> (USA)` 建立一條標記為 `WITHIN` 的邊,`Idaho` 為尾節點,`USA` 為頭節點。
|
||||
|
||||
**例 2-3 將圖 2-5 中的資料子集表示為 Cypher 查詢**
|
||||
|
||||
@ -608,7 +608,7 @@ CREATE
|
||||
|
||||
當 [圖 2-5](/v1/ddia_0205.png) 的所有頂點和邊被新增到資料庫後,讓我們提些有趣的問題:例如,找到所有從美國移民到歐洲的人的名字。更確切地說,這裡我們想要找到符合下面條件的所有頂點,並且返回這些頂點的 `name` 屬性:該頂點擁有一條連到美國任一位置的 `BORN_IN` 邊,和一條連到歐洲的任一位置的 `LIVING_IN` 邊。
|
||||
|
||||
[例 2-4]() 展示瞭如何在 Cypher 中表達這個查詢。在 MATCH 子句中使用相同的箭頭符號來查詢圖中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配 `BORN_IN` 邊的任意兩個頂點。該邊的尾節點被綁定了變數 `person`,頭節點則未被繫結。
|
||||
[例 2-4]() 展示了如何在 Cypher 中表達這個查詢。在 MATCH 子句中使用相同的箭頭符號來查詢圖中的模式:`(person) -[:BORN_IN]-> ()` 可以匹配 `BORN_IN` 邊的任意兩個頂點。該邊的尾節點被綁定了變數 `person`,頭節點則未被繫結。
|
||||
|
||||
**例 2-4 查詢所有從美國移民到歐洲的人的 Cypher 查詢:**
|
||||
|
||||
@ -701,7 +701,7 @@ WITH RECURSIVE
|
||||
|
||||
三元組的主語相當於圖中的一個頂點。而賓語是下面兩者之一:
|
||||
|
||||
1. 原始資料型別中的值,例如字串或數字。在這種情況下,三元組的謂語和賓語相當於主語頂點上的屬性的鍵和值。例如,`(lucy, age, 33)` 就像屬性 `{“age”:33}` 的頂點 lucy。
|
||||
1. 原始資料型別中的值,例如字串或數字。在這種情況下,三元組的謂語和賓語相當於主語頂點上的屬性的鍵和值。例如,`(lucy, age, 33)` 就像屬性 `{"age": 33}` 的頂點 lucy。
|
||||
2. 圖中的另一個頂點。在這種情況下,謂語是圖中的一條邊,主語是其尾部頂點,而賓語是其頭部頂點。例如,在 `(lucy, marriedTo, alain)` 中主語和賓語 `lucy` 和 `alain` 都是頂點,並且謂語 `marriedTo` 是連線他們的邊的標籤。
|
||||
|
||||
[例 2-6]() 展示了與 [例 2-3]() 相同的資料,以稱為 Turtle 的格式(Notation3(N3)【39】的一個子集)寫成三元組。
|
||||
@ -726,7 +726,7 @@ _:namerica :name "North America"
|
||||
_:namerica :type :"continent"
|
||||
```
|
||||
|
||||
在這個例子中,圖的頂點被寫為:`_:someName`。這個名字並不意味著這個檔案以外的任何東西。它的存在只是幫助我們明確哪些三元組引用了同一頂點。當謂語表示邊時,該賓語是一個頂點,如 `_:idaho :within _:usa.`。當謂語是一個屬性時,該賓語是一個字串,如 `_:usa :name"United States"`
|
||||
在這個例子中,圖的頂點被寫為:`_:someName`。這個名字並不意味著這個檔案以外的任何東西。它的存在只是幫助我們明確哪些三元組引用了同一頂點。當謂語表示邊時,該賓語是一個頂點,如 `_:idaho :within _:usa.`。當謂語是一個屬性時,該賓語是一個字串,如 `_:usa :name"United States"`
|
||||
|
||||
一遍又一遍地重複相同的主語看起來相當重複,但幸運的是,可以使用分號來說明關於同一主語的多個事情。這使得 Turtle 格式相當不錯,可讀性強:請參閱 [例 2-7]()。
|
||||
|
||||
@ -786,7 +786,7 @@ _: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,如 `urn:example:within`。幸運的是,你只需在檔案頂部對這個字首做一次宣告,後續就不用再管了。
|
||||
從 RDF 的角度來看,URL `<http://my-company.com/namespace>` 不一定需要能解析成什麼東西,它只是一個名稱空間。為避免與 `http://URL` 混淆,本節中的示例使用不可解析的 URI,如 `urn:example:within`。幸運的是,你只需在檔案頂部對這個字首做一次宣告,後續就不用再管了。
|
||||
|
||||
### SPARQL 查詢語言
|
||||
|
||||
@ -842,7 +842,7 @@ SPARQL 是一種很好的查詢語言 —— 儘管它構想的語義網從未
|
||||
|
||||
[^viii]: Datomic 和 Cascalog 使用 Datalog 的 Clojure S 表示式語法。在下面的例子中使用了一個更容易閱讀的 Prolog 語法,但兩者沒有任何功能差異。
|
||||
|
||||
Datalog 的資料模型類似於三元組模式,但進行了一點泛化。把三元組寫成 **謂語**(**主語,賓語**),而不是寫三元語(**主語,謂語,賓語**)。[例 2-10]() 顯示瞭如何用 Datalog 寫入我們的例子中的資料。
|
||||
Datalog 的資料模型類似於三元組模式,但進行了一點泛化。把三元組寫成 **謂語**(**主語,賓語**),而不是寫三元語(**主語,謂語,賓語**)。[例 2-10]() 顯示了如何用 Datalog 寫入我們的例子中的資料。
|
||||
|
||||
**例 2-10 用 Datalog 來表示圖 2-5 中的資料子集**
|
||||
|
||||
@ -920,7 +920,7 @@ Cypher 和 SPARQL 使用 SELECT 立即跳轉,但是 Datalog 一次只進行一
|
||||
|
||||
文件資料庫和圖資料庫有一個共同點,那就是它們通常不會將儲存的資料強制約束為特定模式,這可以使應用程式更容易適應不斷變化的需求。但是應用程式很可能仍會假定資料具有一定的結構;區別僅在於模式是**明確的**(寫入時強制)還是**隱含的**(讀取時處理)。
|
||||
|
||||
每個資料模型都具有各自的查詢語言或框架,我們討論了幾個例子:SQL,MapReduce,MongoDB 的聚合管道,Cypher,SPARQL 和 Datalog。我們也談到了 CSS 和 XSL/XPath,它們不是資料庫查詢語言,而包含有趣的相似之處。
|
||||
每個資料模型都具有各自的查詢語言或框架,我們討論了幾個例子:SQL、MapReduce、MongoDB 的聚合管道、Cypher、SPARQL 和 Datalog。我們也談到了 CSS 和 XSL/XPath,它們不是資料庫查詢語言,而包含有趣的相似之處。
|
||||
|
||||
雖然我們已經覆蓋了很多層面,但仍然有許多資料模型沒有提到。舉幾個簡單的例子:
|
||||
|
||||
|
@ -83,7 +83,7 @@ $ cat database
|
||||
|
||||
鍵值儲存與在大多數程式語言中可以找到的 **字典(dictionary)** 型別非常相似,通常字典都是用 **雜湊對映(hash map)** 或 **散列表(hash table)** 實現的。雜湊對映在許多演算法教科書中都有描述【1,2】,所以這裡我們不會討論它的工作細節。既然我們已經可以用雜湊對映來表示 **記憶體中** 的資料結構,為什麼不使用它來索引 **硬碟上** 的資料呢?
|
||||
|
||||
假設我們的資料儲存只是一個追加寫入的檔案,就像前面的例子一樣,那麼最簡單的索引策略就是:保留一個記憶體中的雜湊對映,其中每個鍵都對映到資料檔案中的一個位元組偏移量,指明瞭可以找到對應值的位置,如 [圖 3-1](/v1/ddia_0301.png) 所示。當你將新的鍵值對追加寫入檔案中時,還要更新雜湊對映,以反映剛剛寫入的資料的偏移量(這同時適用於插入新鍵與更新現有鍵)。當你想查詢一個值時,使用雜湊對映來查詢資料檔案中的偏移量,**尋找(seek)** 該位置並讀取該值即可。
|
||||
假設我們的資料儲存只是一個追加寫入的檔案,就像前面的例子一樣,那麼最簡單的索引策略就是:保留一個記憶體中的雜湊對映,其中每個鍵都對映到資料檔案中的一個位元組偏移量,指明了可以找到對應值的位置,如 [圖 3-1](/v1/ddia_0301.png) 所示。當你將新的鍵值對追加寫入檔案中時,還要更新雜湊對映,以反映剛剛寫入的資料的偏移量(這同時適用於插入新鍵與更新現有鍵)。當你想查詢一個值時,使用雜湊對映來查詢資料檔案中的偏移量,**尋找(seek)** 該位置並讀取該值即可。
|
||||
|
||||

|
||||
|
||||
|
@ -100,7 +100,7 @@ JSON 比 XML 簡潔,但與二進位制格式相比還是太佔空間。這一
|
||||
}
|
||||
```
|
||||
|
||||
我們來看一個 MessagePack 的例子,它是一個 JSON 的二進位制編碼。圖 4-1 顯示瞭如果使用 MessagePack 【14】對 [例 4-1]() 中的 JSON 文件進行編碼,則得到的位元組序列。前幾個位元組如下:
|
||||
我們來看一個 MessagePack 的例子,它是一個 JSON 的二進位制編碼。圖 4-1 顯示了如果使用 MessagePack 【14】對 [例 4-1]() 中的 JSON 文件進行編碼,則得到的位元組序列。前幾個位元組如下:
|
||||
|
||||
1. 第一個位元組 `0x83` 表示接下來是 **3** 個欄位(低四位 = `0x03`)的 **物件 object**(高四位 = `0x80`)。(如果想知道如果一個物件有 15 個以上的欄位會發生什麼情況,欄位的數量塞不進 4 個 bit 裡,那麼它會用另一個不同的型別識別符號,欄位的數量被編碼兩個或四個位元組)。
|
||||
2. 第二個位元組 `0xa8` 表示接下來是 **8** 位元組長(低四位 = `0x08`)的字串(高四位 = `0x0a`)。
|
||||
|
@ -129,7 +129,7 @@ Cassandra 採取了折衷的策略【11, 12, 13】。Cassandra 中的表可以
|
||||
|
||||
假設你正在經營一個銷售二手車的網站(如 [圖 6-4](/v1/ddia_0604.png) 所示)。每個列表都有一個唯一的 ID—— 稱之為文件 ID—— 並且用文件 ID 對資料庫進行分割槽(例如,分割槽 0 中的 ID 0 到 499,分割槽 1 中的 ID 500 到 999 等)。
|
||||
|
||||
你想讓使用者搜尋汽車,允許他們透過顏色和廠商過濾,所以需要一個在顏色和廠商上的次級索引(文件資料庫中這些是 **欄位(field)**,關係資料庫中這些是 **列(column)** )。如果你聲明瞭索引,則資料庫可以自動執行索引 [^ii]。例如,無論何時將紅色汽車新增到資料庫,資料庫分割槽都會自動將其新增到索引條目 `color:red` 的文件 ID 列表中。
|
||||
你想讓使用者搜尋汽車,允許他們透過顏色和廠商過濾,所以需要一個在顏色和廠商上的次級索引(文件資料庫中這些是 **欄位(field)**,關係資料庫中這些是 **列(column)** )。如果你聲明了索引,則資料庫可以自動執行索引 [^ii]。例如,無論何時將紅色汽車新增到資料庫,資料庫分割槽都會自動將其新增到索引條目 `color:red` 的文件 ID 列表中。
|
||||
|
||||
[^ii]: 如果資料庫僅支援鍵值模型,則你可能會嘗試在應用程式程式碼中建立從值到文件 ID 的對映來實現次級索引。如果沿著這條路線走下去,請萬分小心,確保你的索引與底層資料保持一致。競爭條件和間歇性寫入失敗(其中一些更改已儲存,但其他更改未儲存)很容易導致資料不同步 - 請參閱 “[多物件事務的需求](/v1_tw/ch7#多物件事務的需求)”。
|
||||
|
||||
@ -278,7 +278,7 @@ Cassandra 和 Ketama 使用的第三種方法是使分割槽數與節點數成
|
||||
|
||||
**圖 6-8 使用 ZooKeeper 跟蹤分割槽分配給節點。**
|
||||
|
||||
例如,LinkedIn的Espresso使用Helix 【31】進行叢集管理(依靠ZooKeeper),實現瞭如[圖6-8](/v1/ddia_0608.png)所示的路由層。HBase、SolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。MongoDB具有類似的體系結構,但它依賴於自己的**配置伺服器(config server)** 實現和mongos守護程序作為路由層。
|
||||
例如,LinkedIn的Espresso使用Helix 【31】進行叢集管理(依靠ZooKeeper),實現了如[圖6-8](/v1/ddia_0608.png)所示的路由層。HBase、SolrCloud和Kafka也使用ZooKeeper來跟蹤分割槽分配。MongoDB具有類似的體系結構,但它依賴於自己的**配置伺服器(config server)** 實現和mongos守護程序作為路由層。
|
||||
|
||||
Cassandra 和 Riak 採取不同的方法:他們在節點之間使用 **流言協議(gossip protocol)** 來傳播叢集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖 6-7](/v1/ddia_0607.png) 中的方法 1)。這個模型在資料庫節點中增加了更多的複雜性,但是避免了對像 ZooKeeper 這樣的外部協調服務的依賴。
|
||||
|
||||
|
@ -347,7 +347,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了
|
||||
|
||||
因此,根據這個定義,在線性一致的資料儲存中是不存在併發操作的:必須有且僅有一條時間線,所有的操作都在這條時間線上,構成一個全序關係。可能有幾個請求在等待處理,但是資料儲存確保了每個請求都是在唯一時間線上的某個時間點自動處理的,不存在任何併發。
|
||||
|
||||
併發意味著時間線會分岔然後合併 —— 在這種情況下,不同分支上的操作是無法比較的(即併發操作)。在 [第五章](/v1_tw/ch5) 中我們看到了這種現象:例如,[圖 5-14](/v1/ddia_0514.png) 並不是一條直線的全序關係,而是一堆不同的操作併發進行。圖中的箭頭指明瞭因果依賴 —— 操作的偏序。
|
||||
併發意味著時間線會分岔然後合併 —— 在這種情況下,不同分支上的操作是無法比較的(即併發操作)。在 [第五章](/v1_tw/ch5) 中我們看到了這種現象:例如,[圖 5-14](/v1/ddia_0514.png) 並不是一條直線的全序關係,而是一堆不同的操作併發進行。圖中的箭頭指明了因果依賴 —— 操作的偏序。
|
||||
|
||||
如果你熟悉像 Git 這樣的分散式版本控制系統,那麼其版本歷史與因果關係圖極其相似。通常,一個 **提交(Commit)** 發生在另一個提交之後,在一條直線上。但是有時你會遇到分支(當多個人同時在一個專案上工作時),**合併(Merge)** 會在這些併發建立的提交相融合時建立。
|
||||
|
||||
@ -518,7 +518,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了
|
||||
|
||||
#### 使用線性一致性儲存實現全序廣播
|
||||
|
||||
上一節介紹瞭如何從全序廣播構建一個線性一致的 CAS 操作。我們也可以把它反過來,假設我們有線性一致的儲存,接下來會展示如何在此基礎上構建全序廣播。
|
||||
上一節介紹了如何從全序廣播構建一個線性一致的 CAS 操作。我們也可以把它反過來,假設我們有線性一致的儲存,接下來會展示如何在此基礎上構建全序廣播。
|
||||
|
||||
最簡單的方法是假設你有一個線性一致的暫存器來儲存一個整數,並且有一個原子 **自增並返回** 操作【28】。或者原子 CAS 操作也可以完成這項工作。
|
||||
|
||||
@ -628,7 +628,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】,它只考慮了
|
||||
|
||||
因此,該協議包含兩個關鍵的 “不歸路” 點:當參與者投票 “是” 時,它承諾它稍後肯定能夠提交(儘管協調者可能仍然選擇放棄);以及一旦協調者做出決定,這一決定是不可撤銷的。這些承諾保證了 2PC 的原子性(單節點原子提交將這兩個事件合為了一體:將提交記錄寫入事務日誌)。
|
||||
|
||||
回到婚姻的比喻,在說 “我願意” 之前,你和你的新娘 / 新郎有中止這個事務的自由,只要回覆 “沒門!” 就行(或者有類似效果的話)。然而在說了 “我願意” 之後,你就不能撤回那個聲明瞭。如果你說 “我願意” 後暈倒了,沒有聽到司儀說 “你們現在是夫妻了”,那也並不會改變事務已經提交的現實。當你稍後恢復意識時,可以透過查詢司儀的全域性事務 ID 狀態來確定你是否已經成婚,或者你可以等待司儀重試下一次提交請求(因為重試將在你無意識期間一直持續)。
|
||||
回到婚姻的比喻,在說 “我願意” 之前,你和你的新娘 / 新郎有中止這個事務的自由,只要回覆 “沒門!” 就行(或者有類似效果的話)。然而在說了 “我願意” 之後,你就不能撤回那個聲明了。如果你說 “我願意” 後暈倒了,沒有聽到司儀說 “你們現在是夫妻了”,那也並不會改變事務已經提交的現實。當你稍後恢復意識時,可以透過查詢司儀的全域性事務 ID 狀態來確定你是否已經成婚,或者你可以等待司儀重試下一次提交請求(因為重試將在你無意識期間一直持續)。
|
||||
|
||||
#### 協調者失效
|
||||
|
||||
@ -731,7 +731,7 @@ XA 事務解決了保持多個參與者(資料系統)相互一致的現實
|
||||
|
||||
非正式地,共識意味著讓幾個節點就某事達成一致。例如,如果有幾個人 **同時(concurrently)** 嘗試預訂飛機上的最後一個座位,或劇院中的同一個座位,或者嘗試使用相同的使用者名稱註冊一個帳戶。共識演算法可以用來確定這些 **互不相容(mutually incompatible)** 的操作中,哪一個才是贏家。
|
||||
|
||||
共識問題通常形式化如下:一個或多個節點可以 **提議(propose)** 某些值,而共識演算法 **決定(decides)** 採用其中的某個值。在座位預訂的例子中,當幾個顧客同時試圖訂購最後一個座位時,處理顧客請求的每個節點可以 **提議** 將要服務的顧客的 ID,而 **決定** 指明瞭哪個顧客獲得了座位。
|
||||
共識問題通常形式化如下:一個或多個節點可以 **提議(propose)** 某些值,而共識演算法 **決定(decides)** 採用其中的某個值。在座位預訂的例子中,當幾個顧客同時試圖訂購最後一個座位時,處理顧客請求的每個節點可以 **提議** 將要服務的顧客的 ID,而 **決定** 指明了哪個顧客獲得了座位。
|
||||
|
||||
在這種形式下,共識演算法必須滿足以下性質【25】:[^xiii]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user