《offer來了》第六章學習筆記

1.數據庫的概念

1.1.存儲引擎

常用的存儲引擎主要有 MyISAM、InnoDB、Memory、Archive 和 Federated。

1. MyIASM

MyIASM 是 MySQL 默認的存儲引擎,不支持數據庫事務、行級鎖和外鍵,因此在 INSERT(插入)或 UPDATE(更新)數據即寫操作時需要鎖定整個表,效率較低。

MyIASM 的缺點是更新數據慢且不支持事務處理,優點是查詢速度快。

2. InnoDB

InnoDB 爲 MySQL 提供了事務支持、回滾、崩潰修復能力、多版本併發控制、事務安全的操作。InnoDB 的底層存儲結構爲 B+ 樹,B+ 樹的每個節點都對應 InnoDB 的一個 Page,Page 大小是固定的,一般被設爲 16KB。其中,非葉子節點只有鍵值,葉子節點包含完整的數據

img

InnoDB 適用於有以下需求的場景。

◎ 經常有數據更新的表,適合處理多重併發更新請求。

◎ 支持事務。

◎ 支持災難恢復。

◎ 支持外鍵約束

◎ 支持自動增加列屬性 auto_increment。

3. TokuDB

TokuDB 的底層存儲結構爲 Fractal Tree。Fractal Tree 的結構與 B+ 樹有些類似,只是在 Fractal Tree 中除了每一個指針(key),都需要指向一個 child(孩子)節點,child 節點帶一個 Message Buffer,這個 Message Buffer 是一個先進先出隊列,用來緩存更新操作

img

TokuDB 在線添加索引,不影響讀寫操作,有非常高的寫入性能,主要適用於要求寫入速度快、訪問頻率不高的數據或歷史數據歸檔。

4. Memory

用內存空間創建。每個 Memory 表實際上都對應一個磁盤文件用於持久化。Memory 表因爲數據是存放在內存中的,因此訪問速度非常快,通常使用 Hash 索引來實現數據索引。Memory 表的缺點是一旦服務關閉,表中的數據就會丟失。

支持散列索引和 B 樹索引。

1.2.創建索引原則

◎ 選擇唯一性索引:唯一性索引一般基於 Hash 算法實現,可以快速、唯一地定位某條數據。

◎ 爲經常需要排序、分組和聯合操作的字段建立索引。

◎ 爲常作爲查詢條件的字段建立索引。

◎ 限制索引的數量:索引越多,數據更新表越慢,因爲在數據更新時會不斷計算和添加索引。

◎ 儘量使用數據量少的索引:如果索引的值很長,則佔用的磁盤變大,查詢速度會受到影響。

◎ 儘量使用前綴來索引:如果索引字段的值過長,則不但影響索引的大小,而且會降低索引的執行效率,這時需要使用字段的部分前綴來作爲索引。

◎ 刪除不再使用或者很少使用的索引。

◎ 儘量選擇區分度高的列作爲索引:區分度表示字段值不重複的比例。

◎ 索引列不能參與計算:帶函數的查詢不建議參與索引。

◎ 儘量擴展現有索引:聯合索引的查詢效率比多個獨立索引高。

1.3.數據庫三範式

範式是具有最小冗餘的表結構,三範式的概念如下所述。

1.第一範式

如果每列都是不可再分的最小數據單元(也叫作最小的原子單元),則滿足第一範式,第一範式的目標是確保每列的原子性。

如下圖,其中的 Address 列違背了第一範式列不可再分的原則,要滿足第一範式,就需要將 Address 列拆分爲 Country 列和 City 列。

img

2.第二範式

第二範式在第一範式的基礎上,規定表中的非主鍵列不存在對主鍵的部分依賴,即第二範式要求每個表只描述一件事情。

如下圖,Orders 表既包含訂單信息,也包含產品信息,需要將其拆分爲兩個單獨的表。

img

3.第三範式

第三範式的定義爲:滿足第一範式和第二範式,並且表中的列不存在對非主鍵列的傳遞依賴。

如下圖,除了主鍵的訂單編號,顧客姓名依賴於非主鍵的顧客編號,因此需要將該列去除。

img

1.4 數據庫事務

數據庫事務執行一系列基本操作,要麼都執行,要麼都不執行。

事務是一個不可分割的工作邏輯單元。必須具備以下 4 個屬性 ACID。

◎ 原子性(Atomicity):事務是一個完整操作,參與事務的邏輯單元要麼都執行,要麼都不執行。

◎ 一致性(Consistency):在事務執行完畢時(無論是正常執行完畢還是異常退出),數據都必須處於一致狀態。

◎ 隔離性(Isolation):對數據進行修改的所有併發事務都是彼此隔離的,它不應以任何方式依賴或影響其他事務。

◎ 永久性(Durability):在事務操作完成後,對數據的修改將被持久化到永久性存儲中。

1.5.存儲過程

存儲過程指一組用於完成特定功能的 SQL 語句集,它被存儲在數據庫中,經過第一次編譯後再次調用時不需要被再次編譯,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。

編寫事務需要遵守原則

◎ 儘量利用一些 SQL 語句代替一些小循環,例如聚合函數、求平均函數等。

◎ 中間結果被存放於臨時表中,並加索引。

◎ 少使用遊標(Cursors):SQL 是種集合語言,對於集合運算有較高的性能,而遊標是過程運算。比如,對一個 50 萬行的數據進行查詢時,如果使用遊標,則需要對錶執行 50 萬次讀取請求,將佔用大量的數據庫資源,影響數據庫的性能。

◎ 事務越短越好:SQL Server 支持併發操作,如果事務過長或者隔離級別過高,則都會造成併發操作的阻塞、死鎖,導致查詢速度極慢、CPU 佔用率高等。

◎ 使用 try-catch 處理異常。

◎ 儘量不要將查找語句放在循環中,防止出現過度消耗系統資源的情況。

1.6.觸發器

觸發器是一段能自動執行的程序,和普通存儲過程的區別是「觸發器在對某一個表或者數據進行操作時觸發」,例如UPDATE、INSERT、DELETE 操作時,系統會自動調用和執行該表對應的觸發器。

觸發器一般用於數據變化後需要執行一系列操作的情況,比如對系統核心數據的修改需要通過觸發器來存儲操作日誌的信息等。

2.併發與鎖

2.1.數據庫併發策略

數據庫的併發控制一般採用三種方法實現:

  • 樂觀鎖
  • 悲觀鎖
  • 時間戳

1. 樂觀鎖

樂觀鎖在讀數據時,認爲別人不會去寫其所讀的數據;

悲觀鎖就剛好相反,覺得自己讀數據時,別人可能剛好在寫自己剛讀的數據,態度比較保守;

時間戳在操作數據時不加鎖,而是通過時間戳來控制併發出現的問題。

2. 悲觀鎖

悲觀鎖指在其修改某條數據時,不允許別人讀取該數據,直到自己的整個事務都提交併釋放鎖,其他用戶才能訪問該數據。

悲觀鎖又可分爲排它鎖(寫鎖)共享鎖(讀鎖)

3. 時間戳

時間戳指在數據庫表中額外加一個時間戳列 TimeStamp。每次讀數據時,都把時間戳也讀出來,在更新數據時把時間戳加 1,在提交之前跟數據庫的該字段比較一次,如果比數據庫的值大,就允許保存,否則不允許保存。

這種處理方法雖然不使用數據庫系統提供的鎖機制,但是可以大大提高數據庫處理的併發量。

2.2.數據庫鎖

1.行級鎖

行級鎖指對某行數據加鎖,防止其他事務修改此行。執行以下操作,自動應用行級鎖。

◎ INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n|NOWAIT]。

◎ SELECT … FOR UPDATE 語句允許用戶一次針對多條記錄執行更新。

◎ 使用 COMMIT 或 ROLLBACK 語句釋放鎖。

2.表級鎖

表級鎖指對當前操作的整張表加鎖。

常用的 MyISAM 與 InnoDB 都支持表級鎖定。

表級鎖定分爲表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)。

3.頁級鎖

頁級鎖的鎖定粒度介於行級鎖和表級鎖之間。

表級鎖的加鎖速度快,但衝突多。

行級衝突少,但加鎖速度慢。

頁級鎖在二者之間做了平衡,一次鎖定相鄰的一組記錄。

4.基於 Redis 的分佈式鎖

數據庫鎖是基於單個數據庫實現的,在我們的業務跨多個數據庫時,就要使用分佈式鎖來保證數據的一致性。

使用 Redis 實現一個分佈式鎖的流程:

Redis 實現的分佈式鎖以 Redis setnx 命令爲中心實現,setnx 是 Redis 的寫入操作命令,具體語法爲 setnx(key val)。

在且僅在 key 不存在時,則插入一個 key 爲 val 的字符串,返回 1;

若 key 存在,則什麼都不做,返回 0。

通過 setnx 實現分佈式鎖的思路如下。

◎ 獲取鎖:在獲取鎖時調用 setnx,如果返回 0,則該鎖正在被別人使用;如果返回 1,則成功獲取鎖。

◎ 釋放鎖:在釋放鎖時,判斷鎖是否存在,如果存在,則執行 Redis 的 delete 操作釋放鎖。

簡單的 Redis 實現分佈式鎖的代碼如下:

如果鎖併發比較大,則可以設置一個鎖的超時時間,在超時時間到後,Redis 會自動釋放鎖給其他線程使用:

img

img

以上代碼定義了 RedisLock 類,在該類中定義了一個 Redis 數據庫連接 Jedis,同時定義了 lock 方法來獲取一個鎖,在獲取鎖時首先通過 setnx 設置鎖 id 獲取 Redis 內鎖的信息,如果返回信息爲 0,則表示鎖正在被人使用(鎖 id 存在於 Redis 中);如果不爲 0,則表示成功在內存中設置了該鎖。同時在 RedisLock 類中定義了 unlock 方法用於釋放一個鎖,具體做法是在 Redis 中查找該鎖並刪除。

2.3.數據庫分表

垂直切分和水平切分兩種。下面是區別。

垂直切分:將表按照功能模塊、關係密切程度劃分並部署到不同的庫中。例如,我們會創建定義數據庫 workDB、商品數據庫 payDB、用戶數據庫 userDB、日誌數據庫 logDB 等,分別用於存儲項目數據定義表、商品定義表、用戶數據表、日誌數據表等

img

水平切分:在一個表中的數據量過大時,我們可以把該表的數據按照某種規則如 userID 散列進行劃分,然後將其存儲到多個結構相同的表和不同的庫上

img

3.數據庫分佈式事務

3.1.CAP

CAP指的是在一個分佈式系統中,一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)三者不可兼得。

◎ **一致性:**在分佈式系統的所有數據備份中,在同一時刻是否有同樣的值(等同於所有節點都訪問同一份最新的數據副本)。

◎ **可用性:**在集羣中一部分節點發生故障後,集羣整體能否響應客戶端的讀寫請求(對數據更新具備高可用性)。

◎ **分區容錯性:**系統如果不能在時限內達成數據的一致性,就意味着發生了分區,必須就當前操作在 C 和 A 之間做出選擇。以實際效果而言,分區相當於對通信的時限要求。

3.2.兩階段提交協議

分佈式事務指涉及操作多個數據庫的事務,在分佈式系統中,各個節點之間在物理上相互獨立,通過網絡進行溝通和協調。

二階段提交(Two-Phase Commit)指在計算機網絡及數據庫領域內,爲了使分佈式數據庫的所有節點在進行事務提交時都保持一致性而設計的一種算法。在分佈式系統中,每個節點只知道自己的操作是否成功,不知道其他節點的操作是否成功。

在一個事務跨越多個節點時,爲了保持事務的 ACID 特性,需要引入一個協調者組件來統一掌控所有節點(稱作參與者)的操作結果,並最終指示這些節點是否真正提交操作結果(比如將更新後的數據寫入磁盤等)。

二階段提交思路概括爲:參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋決定各參與者是提交操作還是中止操作。

1.Prepare(準備階段)

事務協調者(事務管理器)給每個參與者(源管理器)都發送 Prepare 消息,每個參與者要麼直接返回失敗(如權限驗證失敗),要麼在本地執行事務,寫本地的 redo 和 undo 日誌但不提交,是一種「萬事俱備,只欠東風」的狀態。

2.Commit(提交階段)

如果協調者接收到了參與者的失敗消息或者超時,則直接給每個參與者都發送回滾消息,否則發送提交消息,參與者根據協調者的指令執行提交或者回滾操作,釋放在所有事務處理過程中使用的鎖資源,如圖 7-8 所示。

img

3.兩階段提交的缺點

◎ 同步阻塞問題:在執行過程中,所有參與者的任務都是阻塞執行的。

◎ 單點故障:所有請求都需要經過協調者,協調者發生故障時,所有參與者都會被阻塞。

◎ 數據不一致:在二階段提交的第 2 階段,在協調者向參與者發送 Commit(提交)請求後發生了局部網絡異常,或者在發送 Commit 請求過程中協調者發生了故障,導致只有一部分參與者接收到 Commit 請求,於是整個分佈式系統出現了數據不一致的現象

◎ 協調者宕機後事務狀態丟失:協調者在發出 Commit 消息之後宕機,唯一接收到這條消息的參與者也宕機,即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒有人知道事務是否已被提交。

3.3.三階段提交協議

三階段提交(Three-Phase Commit),是二階段提交(2PC)的改進版本。改進如下:

◎ 引入超時機制:在協調者和參與者中引入超時機制,如果協調者長時間接收不到參與者的反饋,則認爲參與者執行失敗。

◎ 在第 1 階段和第 2 階段都加入一個預準備階段,以保證在最後的任務提交之前各參與節點的狀態是一致的。也就是說,除了引入超時機制,三階段提交協議(3PC)把兩階段提交協議(2PC)的準備階段再次一分爲二,這樣三階段提交就有 CanCommit、PreCommit、DoCommit 三個階段。

1.CanCommit 階段

協調者向參與者發送 Commit 請求,參與者如果可以提交就返回 Yes 響應,否則返回 No 響應。

2.PreCommit 階段

協調者根據參與者的反應來決定是否繼續進行,有兩種可能:

◎ 協調者從所有參與者那裏獲得的反饋都是 Yes 響應,就預執行事務。

◎ 有任意參與者向協調者發送了 No 響應,或者在等待超時之後協調者都沒有接收到參與者的響應,則執行事務的中斷。

3.DoCommit 階段

該階段進行真正的事務提交,主要包括:協調者發送提交請求,參與者提交事務,參與者響應反饋(在事務提交完之後向協調者發送 Ack 響應),協調者確定完成事務,如圖 :

img

3.4.分佈式事務

1.傳統事務

傳統事務遵循 ACID 原則

◎ 原子性:事務是包含一系列操作的原子操作,事務的原子性確保這些操作全部完成或者全部失敗。

◎ 一致性:事務執行的結果必須使數據庫從不一致性狀態轉爲一致性狀態。保證數據庫的一致性指在事務完成時,必須使所有數據都有一致的狀態。

◎ 隔離性:因爲可能在相同的數據集上同時有許多事務要處理,所以每個事務都應該與其他事務隔離,避免數據被破壞。

◎ 持久性:一旦事務完成,其結果就應該能夠承受任何系統的錯誤,比如在事務提交過程中服務器的電源被切斷等。在通常情況下,事務的結果被寫入持續性存儲中。

2.柔性事務

在分佈式數據庫領域,基於 CAP 理論及 BASE 理論,阿里巴巴提出了柔性事務概念BASE 理論CAP 理論的延伸,包括基本可用(Basically Available)、柔性狀態(Soft State)、最終一致性(Eventual Consistency)三個原則,並基於這三個原則設計出了柔性事務。

通常所說的柔性事務分爲

  • 兩階段型
  • 補償型
  • 異步確保型
  • 最大努力通知型

兩階段型事務指分佈式事務的兩階段提交,對應技術上的 XA 和 JTA/JTS,是分佈式環境下事務處理的典型模式。

TCC 型事務(Try、Confirm、Cancel)爲補償型事務,是一種基於補償的事務處理模型。如下圖,服務器 A 發起事務,服務器 B 參與事務,如果服務器 A 的事務和服務器 B 的事務都順利執行完成並提交,則整個事務執行完成。如果事務 B 執行失敗,事務 B 本身就回滾,這時事務 A 已被提交,所以需要執行一個補償操作,將已經提交的事務 A 執行的操作進行反操作,恢復到未執行前事務 A 的狀態。需要注意的是,發起提交的一般是主業務服務,而狀態補償的一般是業務活動管理者,因爲活動日誌被存儲在業務活動管理中,補償需要依靠日誌進行恢復。TCC 事務模型犧牲了一定的隔離性和一致性,但是提高了事務的可用性。

img

異步確保型事務指將一系列同步的事務操作修改爲基於消息隊列異步執行的操作,來避免分佈式事務中同步阻塞帶來的數據操作性能下降。如下圖,在寫業務數據 A 觸發後將執行以下流程。

(1)業務 A 的模塊在數據庫 A 上執行數據更新操作。

(2)業務 A 調用寫消息數據模塊。

(3)寫消息日誌模塊將數據庫的寫操作狀態寫入數據庫 A 中。

(4)寫消息日誌模塊將寫操作日誌發送給消息服務器。

(5)讀消息日誌模塊接收操作日誌。

(6)讀消息數據調用寫業務 B 的模塊。

(7)寫業務 B 更新數據到數據庫 B。

(8)寫業務數據 B 的模塊發送異步消息更新數據庫 A 中的寫消息日誌狀態,說明自己已經完成了異步數據更新操作。

img

最大努力通知型事務也是通過消息中間件實現的,與前面異步確保型操作不同的是:在消息由 MQ 服務器發送到消費者之後,允許在達到最大重試次數之後正常結束事務,因此無法保障數據的最終一致性。如下圖,寫業務數據 A 在更新數據庫後調用寫消息日誌將數據操作以異步消息的形式發送給讀消息日誌模塊;讀消息日誌模塊在接收到數據操作後調用寫業務 B 寫數據庫。和異步確保型不同的是,數據庫 B 在寫完之後將不再通知寫狀態到數據庫 A,如果因爲網絡或其他原因,在如下圖所示的第 4 步沒有接收到消息,則消息服務器將不斷重試發送消息到讀消息日誌,如果經過 N次重試後讀消息日誌還是沒有接收到日誌,則消息不再發送,這時會出現數據庫 A 和數據庫 B 數據不一致的情況。最大努力型通知事務通過消息服務使分佈式事務異步解耦,並且模塊簡單、高效,但是犧牲了數據的一致性,在金融等對事務要求高的業務中不建議使用,但在日誌記錄類等對數據一致性要求不是很高的應用上執行效率很高。

img

4.總結

image-20200426015555778

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章