《MySQL實戰45講》學習小結(基礎篇)

最近學習丁奇老師的《MySQL實戰45講》,明白了以前一知半解的概念,對MySQL的運行機制有了比較系統的理解。

課程內容組織得非常好,留言裏面也有很多精彩的見解,學到了很多東西,過程很享受。

在此先感謝下丁奇老師。

 

這裏把學到的內容組織一下,把知識點串成線,織成網。

寫下來的只是個提綱,以及一些要點。

接下來還要學以致用,在實際工作中帶着這些知識去練習。

 

把學到的內容歸納爲三部分:

一、基礎概念

二、運維管理

三、合理使用MySQL

 

 

第一篇:基礎概念

 

1. 事務

第3講,8講,20講

 

事務是關係數據庫(RDB)區別於非關係數據庫的關鍵特性。

關鍵在於理解併發時可能出現的問題,以及避免這些問題出現的機制。

 

從問題入手:

- 髒讀:讀到其他事務未提交的修改

- 不可重複度:同一行,兩次讀到的數據不同

- 幻讀:兩次執行同一個sql,得到的結果集不同

理解“問題”的關鍵:兩次讀,是限定在一個事務的上下文內的。

很簡單,可我之前就是沒領會到,汗 ~ ~ ~

 

隔離級別(InnoDB):

- 讀提交(RC),解決髒讀問題

- 可重複讀(RR),解決不可重複讀問題,以及大部分幻讀問題

- 串行化,同時只能有一個事務運行,完全解決幻讀問題

隔離級別越高,解決的問題越多,並行能力越低。

InnoDB的缺省隔離級別爲:可重複度。

 

 

2. 鎖

第6講,7講,19講,20講,21講,30講,39講,40講

 

鎖是數據庫用以實現事務隔離的工具。

按照被鎖定的對象,有:全局鎖、表鎖、MDL、行鎖、gap lock、自增鎖等。

按並行能力,可分共享鎖(讀鎖)和互斥鎖(寫鎖)

 

兩階段鎖:鎖在需要時加上,在事務結束時釋放。

 

行鎖、gap lock:這兩個鎖要重點學習。它們都在索引上執行。被訪問到的記錄才需要加鎖。如果查詢的是非主鍵索引,除了在非主鍵索引上的 next-key lock,還要在主鍵索引上對符合條件的記錄加行鎖。

 

MDL(meta-data lock)

爲避免表結構被其他線程修改,自動加鎖。

5.6版本實現online DDL,通過 MDL降級使得DDL期間仍可讀寫該表,提升並行能力。

原理:DDL語句先申請DML寫鎖,拿到MDL寫鎖之後,創建臨時表,把源表數據拷過去,變更臨時表數據結構,然後再用臨時表替換源表,並把期間對源表的變動應用到新的表。在拷貝數據、變更臨時表數據結構期間,MDL寫鎖降級爲讀鎖,其他事務可以對原表進行讀寫。最後臨時表和源表切換的時候,再從MDL讀鎖升爲寫鎖。

 

死鎖檢測

死鎖檢測,時間複雜度爲O(n^2)

高併發的情況下,如果出現大量鎖等待,會因爲死鎖檢測,消耗大量的CPU,導致性能急劇下降。

這種情況下,要控制對數據庫的併發操作數量,以提升性能。

 

 

3. Multi-Version Concurrency Control(MVCC)

第3講,8講

 

多版本併發控制,是在不加鎖的情況下實現事務隔離的一種手段。

對數據的每個修改,都會記錄一個undo log。

每個事務會記錄其啓動瞬間,執行中的事務的trx_id列表。每行數據的每個版本(undo log),也會記錄導致該變化的trx_id。trx_id,在InnoDB引擎層,在事務啓動是獲取,嚴格遞增。但由於事務提交的次序和啓動的次序不一致,所以在數據版本上,後一個版本的trx_id可能比前一個版本的trx_id小。

事務從一行數據的最新版本開始,逐個對比數據版本的trx_id和上述trx_id列表,決定當前版本是否可見,否則執行對應的undo log,直到找到第一個可見版本。

這個過程可稱爲一致性讀。事務對應的數據版本,可稱爲一致性視圖。

需要注意:

如果存在對同一個數據頻繁更新的長事務,併發執行的其他事務,如果要讀取同一個數據,爲獲取一致性視圖,需要執行大量的undo操作,會導致性能急劇下降。

 

 

4. 日誌系統

第2講,15講,23講,24講,25講,26講,27講,28講,31講

 

主要有3類日誌:binlog、redo log、undo log

 

binlog:

記錄一個事務的所有修改。數據異常恢復、主備數據同步,都依賴binlog。

binlog有statement和row兩種記錄方式。statement方式記錄事務中所執行的語句。row方式記錄對數據的最終影響,細化到每一行,也即一個語句在執行時影響了多少行記錄,就會在binlog中記錄多少條語句,並寫明把每個字段設置爲什麼值。

MySQL使用binlog來完成主備數據同步。考慮到主備服務器的差異、執行同一條sql命令的時間不同等等因素,在binlog中會額外加入一些語句,用以設置當前時間等環境相關的信息。

對應事務的原子性,binlog在事務執行期間,在獨立的空間內先緩存,待事務提交之時,一起追加到binlog文件尾,因此,一個事務的binlog在文件中必然是連續的。

 

redo log(InnoDB):

InnoDB引擎才支持redo log。redo log是物理日誌,記錄“在某個數據頁上做了什麼修改”,類似binlog的row格式。

redo log,隨着腳本的執行,隨時寫入。對並行的事務,其redo log可以穿插寫入,因此可以共用redo log cache。

redo log在事務已完成,且對應的內存頁已同步到存儲的情況下,就可以刪除。因此其需要的空間有限,所以被設計爲固定大小(可分多個文件),循環寫的方式,提高空間利用率。

但要注意:如果有長事務導致redo log的存儲空間被全部佔滿,整個數據庫服務都會被阻塞。

 

從日誌系統的角度,每個事務爲2階段提交:

1. 引擎層:數據修改更新到內存頁(或change buffer),記錄redo log,處於prepare狀態;

2. server層:寫binlog

3. server層提交事務,通知引擎層把redo log的狀態設爲commit。

在這個機制下,結合redo log和binlog,可恢復所有已提交的數據,保證主備數據一致。

這個機制稱爲WAL(Write-Ahead Logging),能恢復在內存中已執行完畢的事務,實現 crash safe。

過程如下圖所示:

redo log 和 binlog

圖中淺色爲InnoDB引擎層,深色爲server層。

redo log和binlog有一個共同的字段:XID,用於進行關聯。

由於數據的持久化操作,由各部分自行控制,因此redo log的狀態修改、binlog的持久化,雖然在原文稿中爲一前一後,但實際運行的時機,可視爲並行。

數據庫崩潰後的恢復:

如redo log處於commit狀態,有完整記錄binlog,則事務已提交完成,數據已入盤,無需額外操作。

如redo log處於commit狀態,但沒有完整的binlog,則執行redo log,然後commit,補全binlog。

如redo log處於prepare狀態,有完整的binlog,引擎層應執行redo log,恢復數據。

如redo log處於prepare狀態,但沒有完整的binlog,事務未提交,無需操作,效果等同於回滾。

 

undo log

上文MVCC中已提高,針對數據的每個操作,對應redo log,有一個undo log,用於實現不用鎖的事務隔離。

MySQL會定期檢查當前執行中的事務,找到最早開始執行的事務,以此來決定哪些undo log可以被刪除。

長事務,又是長事務。長事務的情況下,undo log的數量會顯著增加。

監控長事務,及早發現並予以消除(需要業務系統開發者配合),是系統運維中必做的動作。

 

 

5. 索引

第4講,5講,9講,10講,11講,15講,37講,38講

 

InnoDB引擎

B+樹來存儲索引和數據

 

主鍵索引爲聚簇索引,所有數據和索引存儲在一起,通過主鍵索引就能查到全部數據。

非主鍵索引,在定位到位置後,需要再查一次主鍵索引才能得到全部數據,該動作稱爲回表

非主鍵索引,可設計爲包含了某個查詢語句所需的所有字段的值,這種情況稱爲覆蓋索引

前綴索引:對於字符串字段,可設置索引只保存最靠左的多少個字符,以縮小索引所需空間,提高存儲和搜索效率。但這樣可能會導致查找次數變多。需要根據場景,合理選擇。

InnoDB還支持根據某個函數做索引。

 

Memory引擎(17講,36講,38講)

Memory引擎的數據用數組來存儲,按寫入順序存儲的。

主鍵索引和普通索引一樣,都只記錄數據的位置。缺省爲hash索引。

 

 

6. 數據存儲

第4講,9講,13講,36講,38講,41講

 

B+樹

平衡N叉樹,數據都在葉子節點上。葉子節點保證左小右大。

N的數量,由數據塊的大小和每行對應的索引數據大小共同決定(InnoDB數據頁默認爲16KB)。

符合磁盤按頁讀寫的特性。

InnoDB中,主鍵索引爲聚簇索引,葉子節點保存了全部數據。非主鍵索引,葉子階段保存主鍵的值。

這種方式稱爲 Index Organizied Table。

要理解數據庫,必須深入瞭解B+數。

 

Memory引擎

數據用數組來存儲,按寫入順序存儲的。每行佔用的空間大小固定。如果一行被刪除,後續新增的數據就可以使用這個空間。

主鍵索引和普通索引一樣,都只記錄數據的位置。缺省爲hash索引。

這種方式稱爲 Heap Organizied Table。

 

 

7. 緩存

 

內存數據頁 buffer pool(第33講,35講,12講,19講)

數據庫對數據的讀寫,需要把數據先讀取到內存中。根據磁盤的特性,一次讀入一頁。

InnoDB每個數據頁大小默認爲16KB。

SQL腳本要訪問的數據,如果已經在內存中,可以大大提升性能。Buffer pool對查詢的加速效果,用內存命中率來衡量。一般情況下,內存命中率在99%以上。

InnoDB的內存管理,使用LRU算法,用鏈表實現。

爲了避免因爲對很大的冷表的全表掃,導致熱數據被擠出內存,InnoDB對LRU進行了優化,將內存分爲young和old兩個區域,需要新插入的數據頁,先放到old區頭部,如果這個數據頁再次被訪問時,存在時間大於1秒,再移到young區頭部。

雖然LRU算法做了優化,但是大表的join,如果執行時間超過1秒,還是可能會影響buffer pool,導致正常訪問的數據無法進入young區,並有可能將熱數據擠出young區,這樣會在語句執行結束後的一段時間內仍影響後續查詢語句的性能。

 

change buffer(第9講)

要修改的數據不在內存頁中,在不影響數據一致性的前提下,InooDB 會將這些更新操作緩存在 change buffer 中。待數據頁被讀入內存後,再執行change buffer中與這個頁有關的操作。需要說明,change buffer本身也是可以被持久化的數據(通過redo log),以防數據丟失。

對於寫多讀少的業務來說,頁面在寫完以後馬上被訪問到的概率比較小,此時 change buffer 的使用效果最好。這種業務模型常見的就是賬單類、日誌類的系統。

反過來,假設一個業務的更新模式是寫入之後馬上會做查詢,那麼change buffer反而會降低性能。

 

binlog buffer(第23講)

事務結束之前,binlog先寫到buffer中,事務提交時再寫到文件中,每個事務單獨一塊buffer。

 

redo log buffer(第23講)

先寫buffer,再寫磁盤,多個事務共用buffer,多個事務的redo log可以穿插寫入。

一個事務提交時,可將buffer中這個事務的最後一條log及之前的log,全部寫入磁盤(組提交)

在這兩個binlog和redo log的buffer之外,IO端本身也有buffer,由IO控制器決定何時持久化到磁盤上。

日誌是實現數據可靠性的主要工具,因此,在大多數情況下,應讓日誌儘可能快地寫入磁盤。

 

sort_buffer(第16講,17講)

根據sql腳本需要,在內存中對數據進行排序,如果數據量超出buffer size,需要用到臨時文件做歸併排序。

優先選擇全字段排序。如果需要返回的數據字段多、長度長,考慮rowid排序,只把參與排序的字段和id載入sort_buffer,排序後逐行回表,返回結果。

 

join_buffer(第34講,35講)

適用BNL算法時,被驅動表沒有索引,把驅動表的數據讀入join_buffer,然後掃描被驅動表,滿足join條件的,作爲結果集的一部分返回。

驅動表讀入join_buffer的數據,先按這個表的條件進行篩選,只存入join和結果集需要的字段。

join_buffer默認256K,如果放不下驅動表,那就把驅動表分段放入join_buffer,這樣對被驅動表要做多次全表掃。爲了提高性能,可以增大 join_buffer。

 

內部臨時表(第37講)

執行union語句、group by語句時,MySQL會自動創建內部臨時表,存儲中間數據,並進行計數、sum、去重等操作。

內存臨時表默認大小爲16M,如果超出這個大小,就會轉成磁盤臨時表,默認使用InnoDB引擎。

 

外部臨時表(第35講,36講)

典型用途:用臨時表優化join。

將被驅動表滿足條件的數據放入臨時表,並在臨時表上創建join所需的索引,避免全表掃。

對於其他複雜的查詢邏輯,也可以將中間查詢過程放入臨時表,利用臨時表的索引提升效率。

外部臨時表只對當前用戶會話可見。當前會話結束時,臨時表會自動關閉。這種臨時表的命名與非臨時表可以同名。當重名是,用表名訪問的是臨時表。

 

 

在基礎概念這部分,最困擾我的問題是事務隔離級別的選擇。

 

MySQL InnoDB的缺省隔離級別是可重複讀(RR),而且這個可重複度不僅解決不可重複度的問題,而且通過gap lock解決幻讀的問題。而Oracle的缺省隔離級別是讀提交(RC)。

InnoDB爲什麼有這個設計確定,我的理解是要從其數據的恢復、備份出發。MySQL基於binlog來實現數據的恢復和備份,而且一開始只支持statemen格式。爲了實現主庫和備庫的數據一致,必須解決幻讀問題。這個是設計的出發點。

爲了在滿足數據主備一致的前提上,儘量提升併發能力,提升可用性,採用了undo log、一致性視圖等方法。儘管如此,隔離級別從(RC)變爲(RR)還有存在性能差異的。

在binlog支持row格式之後,我認爲主庫上選擇讀提交(RC)這個隔離級別也能實現主備數據嚴格一致了。那麼,選擇隔離級別的主要依據就是讀提交(RC)這個隔離級別是否能夠滿足業務要求了。

而這一點,尚未找到判斷的準則,需要在實踐中積累。

 

附:丁奇老師在第20講中的靈魂之問:

“在備份期間,備份線程用的是可重複讀,而業務線程用的是讀提交。同時存在兩種事務隔離級別,會不會有問題?進一步地,這兩個不同的隔離級別現象有什麼不一樣的,關於我們的業務,“用讀提交就夠了”這個結論是怎麼得到的?”

 

本文內容爲丁奇老師《MySQL實戰45講》的學習筆記,只是一個提綱。

這門課程的內容和組織方式,每一講的思考題、大家的留言、老師的點評,都非常棒。

強烈推薦!

 

可識別下圖的二維碼購買學習,也算順便打個賞 :-)

極客時間《MySQL實戰45講》

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