1. 程式人生 > 其它 >《MySQL實戰45講》個人筆記-基礎篇

《MySQL實戰45講》個人筆記-基礎篇

拜讀了林曉斌大佬的《MySQL實戰45講》,特意做個知識點總結,以便後期回憶。

01、基礎架構:一條SQL查詢語句是如何執行的?

Server 層包括聯結器、查詢快取、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的內建函式(如日期、時間、數學和加密函式等),所有跨儲存引擎的功能都在這一層實現,比如儲存過程、觸發器、檢視等。而儲存引擎層負責資料的儲存和提取。其架構模式是外掛式的,支援 InnoDB、MyISAM、Memory 等多個儲存引擎。現在最常用的儲存引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了預設儲存引擎。

 

02、日誌系統:一條SQL更新語句是如何執行的?

主角:redo log(重做日誌)和 binlog(歸檔日誌)。使用WAL技術(Write-Ahead Logging),它的關鍵點就是先寫日誌,再寫磁碟。

當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log裡面,並更新記憶體,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤裡面,而這個更新往往是在系統比較空閒的時候做。

redo log和binlog區別:redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。redo log 是物理日誌,記錄的是“在某個資料頁上做了什麼修改”;binlog 是邏輯日誌,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 欄位加 1 ”。redo log 是迴圈寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 檔案寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌。

redo log和binlog是如何組合進行update更新的:圖中淺色框表示是在 InnoDB 內部執行的,深色框表示是在執行器中執行的。

 

03、事務隔離:為什麼你改了我還看不見?

事務隔離級別:
* 讀未提交是指,一個事務還沒提交時,它做的變更就能被別的事務看到。
* 讀提交是指,一個事務提交之後,它做的變更才會被其他事務看到。
* 可重複讀是指,一個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的。
* 序列化,顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。


若隔離級別是“讀未提交”, 則 V1 的值就是 2。這時候事務 B 雖然還沒有提交,但是結果已經被 A 看到了。因此,V2、V3 也都是 2。
若隔離級別是“讀提交”,則 V1 是 1,V2 的值是 2。事務 B 的更新在提交後才能被 A 看到。所以, V3 的值也是 2。
若隔離級別是“可重複讀”,則 V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就是這個要求:事務在執行期間看到的資料前後必須是一致的。
若隔離級別是“序列化”,則在事務 B 執行“將 1 改成 2”的時候,會被鎖住。直到事務 A 提交後,事務 B 才可以繼續執行。所以從 A 的角度看, V1、V2 值是 1,V3 的值是 2。

 

04、05、深入淺出索引

* 主鍵索引:B+樹葉子節點是一行全部資料。
* 普通索引:B+樹葉子節點是索引的欄位(聯合索引會是多個欄位)和主鍵ID。
* 回表:從普通索引查到主鍵ID,再去主鍵索引查。
* 覆蓋索引:如果查詢條件使用的是普通索引(或是聯合索引的最左原則欄位),查詢結果是聯合索引的欄位或是主鍵,不用回表操作,直接返回結果,減少IO磁碟讀寫讀取正行資料。
* 最左字首:聯合索引的最左 N 個欄位,也可以是字串索引的最左 M 個字元。
* 聯合索引:根據建立聯合索引的順序,以最左原則進行where檢索,比如(age,name)以age=1 或 age= 1 and name=‘張三’可以使用索引,單以name=‘張三’ 不會使用索引,考慮到儲存空間的問題,還請根據業務需求,將查詢頻繁的資料進行靠左建立索引。
* 索引下推:like 'hello%’and age >10 檢索,MySQL5.6版本之前,會對匹配的資料進行回表查詢。5.6版本後,會先過濾掉age<10的資料,再進行回表查詢,減少回表率,提升檢索速度。

 

06、 全域性鎖和表鎖 :給表加個欄位怎麼有這麼多阻礙?

全域性鎖:全域性鎖就是對整個資料庫例項加鎖。MySQL 提供了一個加全域性讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當你需要讓整個庫處於只讀狀態的時候,可以使用這個命令,之後其他執行緒的以下語句會被阻塞:資料更新語句(資料的增刪改)、資料定義語句(包括建表、修改表結構等)和更新類事務的提交語句。

既然要全庫只讀,為什麼不使用 set global readonly=true 的方式呢?
一是,在有些系統中,readonly 的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。因此,修改 global 變數的方式影響面更大,我不建議你使用。
二是,在異常處理機制上有差異。如果執行 FTWRL 命令之後由於客戶端發生異常斷開,那麼 MySQL 會自動釋放這個全域性鎖,整個庫回到可以正常更新的狀態。而將整個庫設定為 readonly 之後,如果客戶端發生異常,則資料庫就會一直保持 readonly 狀態,這樣會導致整個庫長時間處於不可寫狀態,風險較高。

表級鎖:MySQL 裡面表級別的鎖有兩種:一種是表鎖,一種是元資料鎖(meta data lock,MDL)。

表鎖的語法是 lock tables … read/write。與 FTWRL 類似,可以用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放。需要注意,lock tables 語法除了會限制別的執行緒的讀寫外,也限定了本執行緒接下來的操作物件。舉個例子, 如果在某個執行緒 A 中執行 lock tables t1 read, t2 write; 這個語句,則其他執行緒寫 t1、讀寫 t2 的語句都會被阻塞。同時,執行緒 A 在執行 unlock tables 之前,也只能執行讀 t1、讀寫 t2 的操作。連寫 t1 都不允許,自然也不能訪問其他表。

MDL鎖:當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對錶做結構變更操作的時候,加 MDL 寫鎖。讀鎖之間不互斥,因此你可以有多個執行緒同時對一張表增刪改查。讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。因此,如果有兩個執行緒要同時給一個表加欄位,其中一個要等另一個執行完才能開始執行。

 

07、行鎖功過:怎麼減少行鎖對效能的影響?

行鎖就是針對資料表中行記錄的鎖,僅InnoDB支援行鎖。在 InnoDB 事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議。

死鎖和死鎖檢測:當併發系統中不同執行緒出現迴圈資源依賴,涉及的執行緒都在等待別的執行緒釋放資源時,就會導致這幾個執行緒都進入無限等待的狀態,稱為死鎖。這裡我用資料庫中的行鎖舉個例子。

這時候,事務 A 在等待事務 B 釋放 id=2 的行鎖,而事務 B 在等待事務 A 釋放 id=1 的行鎖。 事務 A 和事務 B 在互相等待對方的資源釋放,就是進入了死鎖狀態。

當出現死鎖以後,有兩種策略:一種策略是,直接進入等待,直到超時。這個超時時間可以通過引數 innodb_lock_wait_timeout 來設定。另一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將引數 innodb_deadlock_detect 設定為 on,表示開啟這個邏輯。

 

08、事務到底是隔離的還是不隔離的?

我在第 3 篇文章和你講事務隔離級別的時候提到過,如果是可重複讀隔離級別,事務 T 啟動的時候會建立一個檢視 read-view,之後事務 T 執行期間,即使有其他事務修改了資料,事務 T 看到的仍然跟在啟動時看到的一樣。也就是說,一個在可重複讀隔離級別下執行的事務,好像與世無爭,不受外界影響。但是,我在上一篇文章中,和你分享行鎖的時候又提到,一個事務要更新一行,如果剛好有另外一個事務擁有這一行的行鎖,它又不能這麼超然了,會被鎖住,進入等待狀態。問題是,既然進入了等待狀態,那麼等到這個事務自己獲取到行鎖要更新資料的時候,它讀到的值又是什麼呢?

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

這裡,我們需要注意的是事務的啟動時機。begin/start transaction 命令並不是一個事務的起點,在執行到它們之後的第一個操作 InnoDB 表的語句,事務才真正啟動。如果你想要馬上啟動一個事務,可以使用 start transaction with consistent snapshot 這個命令。第一種啟動方式,一致性檢視是在執行第一個快照讀語句時建立的;第二種啟動方式,一致性檢視是在執行 start transaction with consistent snapshot 時建立的。

結果是:A讀到的是1,因為讀的是當前事務的快照;B讀到的是3,B執行update時會進行“當前讀”,也就是id=1這條記錄的最新版本,因為C已經把k更新成了2,B讀取到2後再加1,此時讀到的是3。

注意:“當前讀”時會先獲取行鎖,只有當事務commit時才會釋放行鎖,因為C是一條sql,執行完事務就自動commit了。如果C是手動commit的話,C只要不提交,B的update語句就會一直等待,直到超時或死鎖檢測。另外,如果某個事物需要回滾,也會先獲取到行鎖,再進行後續操作。