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

大體來說,MySQL 可以分為 Server 層和存儲引擎層兩部分。
Server 層包括連接器、查詢緩存、分析器、優化器、執行器等,涵蓋 MySQL 的大多數核心服務功能,以及所有的內置函數(如日期、時間、數學和加密函數等),所有跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等。
而存儲引擎層負責數據的存儲和提取。其架構模式是插件式的,支持 InnoDB、MyISAM、Memory 等多個存儲引擎。現在最常用的存儲引擎是 InnoDB,它從 MySQL 5.5.5 版本開始成為了默認存儲引擎。
2.日志系統:一條SQL更新語句是如何執行的?
與查詢流程不一樣的是,更新流程還涉及兩個重要的日志模塊,它們正是我們今天要討論的主角:redo log(重做日志)和 binlog(歸檔日志)
具體來說,當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log(粉板)里面,並更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統比較空閑的時候做,這就像打烊以后掌櫃做的事。
InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個文件,每個文件的大小是 1GB,那么這塊“粉板”總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。

重要的日志模塊:binlog
前面我們講過,MySQL 整體來看,其實就有兩塊:一塊是 Server 層,它主要做的是 MySQL 功能層面的事情;還有一塊是引擎層,負責存儲相關的具體事宜。上面我們聊到的粉板 redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)
這兩種日志有以下三點不同。
1.redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。
2.redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
3.redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,並不會覆蓋以前的日志。
3.事務隔離:為什么你改了我還看不見?
隔離性與隔離級別
提到事務,你肯定會想到 ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔離性、持久性),今天我們就來說說其中 I,也就是“隔離性”。
當數據庫上有多個事務同時執行的時候,就可能出現臟讀(dirty read)、不可重復讀(non-repeatable read)、幻讀(phantom read)的問題,為了解決這些問題,就有了“隔離級別”的概念。
在談隔離級別之前,你首先要知道,你隔離得越嚴實,效率就會越低。因此很多時候,我們都要在二者之間尋找一個平衡點。SQL 標准的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(serializable )。下面我逐一為你解釋:
1.讀未提交是指,一個事務還沒提交時,它做的變更就能被別的事務看到。
2讀提交是指,一個事務提交之后,它做的變更才會被其他事務看到。
3可重復讀是指,一個事務執行過程中看到的數據,總是跟這個事務在啟動時看到的數據是一致的。當然在可重復讀隔離級別下,未提交變更對其他事務也是不可見的。
4.串行化,顧名思義是對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖沖突的時候,后訪問的事務必須等前一個事務執行完成,才能繼續執行。

我們來看看在不同的隔離級別下,事務 A 會有哪些不同的返回結果,也就是圖里面 V1、V2、V3 的返回值分別是什么。
- 若隔離級別是“讀未提交”, 則 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。
在實現上,數據庫里面會創建一個視圖,訪問的時候以視圖的邏輯結果為准。在“可重復讀”隔離級別下,這個視圖是在事務啟動時創建的,整個事務存在期間都用這個視圖。在“讀提交”隔離級別下,這個視圖是在每個 SQL 語句開始執行的時候創建的。這里需要注意的是,“讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念;而“串行化”隔離級別下直接用加鎖的方式來避免並行訪問。
事務的啟動方式
MySQL 的事務啟動方式有以下幾種:
- 顯式啟動事務語句, begin 或 start transaction。配套的提交語句是 commit,回滾語句是 rollback。
- set autocommit=0,這個命令會將這個線程的自動提交關掉。意味着如果你只執行一個 select 語句,這個事務就啟動了,而且並不會自動提交。這個事務持續存在直到你主動執行 commit 或 rollback 語句,或者斷開連接。
你可以在 information_schema 庫的 innodb_trx 這個表中查詢長事務,比如下面這個語句,用於查找持續時間超過 60s 的事務。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
04 | 深入淺出索引(上)
索引的常見模型
1.哈希表是一種以鍵 - 值(key-value)存儲數據的結構,我們只要輸入待查找的值即 key,就可以找到其對應的值即 Value。哈希的思路很簡單,把值放在數組里,用一個哈希函數把 key 換算成一個確定的位置,然后把 value 放在數組的這個位置。
哈希表這種結構適用於只有等值查詢的場景
2.有序數組索引只適用於靜態存儲引擎,比如你要保存的是 2017 年某個城市的所有人口信息,這類不會再修改的數據
3. N 叉樹
InnoDB 的索引模型 ---B+樹
根據葉子節點的內容,索引類型分為主鍵索引和非主鍵索引。
主鍵索引的葉子節點存的是整行數據。在 InnoDB 里,主鍵索引也被稱為聚簇索引(clustered index)。
非主鍵索引的葉子節點內容是主鍵的值。在 InnoDB 里,非主鍵索引也被稱為二級索引(secondary index)。
根據上面的索引結構說明,我們來討論一個問題:基於主鍵索引和普通索引的查詢有什么區別?
如果語句是 select * from T where ID=500,即主鍵查詢方式,則只需要搜索 ID 這棵 B+ 樹;
如果語句是 select * from T where k=5,即普通索引查詢方式,則需要先搜索 k 索引樹,得到 ID 的值為 500,再到 ID 索引樹搜索一次。這個過程稱為回表。
也就是說,基於非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該盡量使用主鍵查詢。
05 | 深入淺出索引(下)
覆蓋索引
如果執行的語句是 select ID from T where k between 3 and 5,這時只需要查 ID 的值,而 ID 的值已經在 k 索引樹上了,因此可以直接提供查詢結果,不需要回表。也就是說,在這個查詢里面,索引 k 已經“覆蓋了”我們的查詢需求,我們稱為覆蓋索引。
由於覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優化手段。
最左前綴原則
這里我們的評估標准是,索引的復用能力。因為可以支持最左前綴,所以當已經有了 (a,b) 這個聯合索引后,一般就不需要單獨在 a 上建立索引了。因此,第一原則是,如果通過調整順序,可以少維護一個索引,那么這個順序往往就是需要優先考慮采用的。
06 | 全局鎖和表鎖 :給表加個字段怎么有這么多阻礙?
根據加鎖的范圍,MySQL 里面的鎖大致可以分成全局鎖、表級鎖和行鎖三類
全局鎖
全局鎖就是對整個數據庫實例加鎖。可以通過命令 Flush tables with read lock (FTWRL) 對全局進行加鎖,讓整個庫處於只讀狀態。
使用全局鎖后,其他線程的以下語句會被阻塞:
* 數據更新語句(增刪改)
* 數據定義語句(包括建表,修改表結構等)
* 更新類事務的提交語句。
全局鎖的典型使用場景是,做全庫邏輯備份
表級鎖
MySQL 里面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lock,MDL)。
* 1.表鎖的語法是 lock tables … read/write
* 2.另一類表級的鎖是 MDL(metadata lock)。MDL 不需要顯式使用,在訪問一個表的時候會被自動加上。MDL 的作用是,保證讀寫的正確性。你可以想象一下,如果一個查詢正在遍歷一個表中的數據,而執行期間另一個線程對這個表結構做變更,刪了一列,那么查詢線程拿到的結果跟表結構對不上,肯定是不行的。
07 | 行鎖功過:怎么減少行鎖對性能的影響?
在 InnoDB 事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議
死鎖和死鎖檢測
當出現死鎖以后,有兩種策略:
一種策略是,直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wait_timeout 來設置。
另一種策略是,發起死鎖檢測,發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將參數 innodb_deadlock_detect 設置為 on,表示開啟這個邏輯
在 InnoDB 中,innodb_lock_wait_timeout 的默認值是 50s,意味着如果采用第一個策略,當出現死鎖以后,第一個被鎖住的線程要過 50s 才會超時退出,然后其他線程才有可能繼續執行。對於在線服務來說,這個等待時間往往是無法接受的。
08 | 事務到底是隔離的還是不隔離的?
在 MySQL 里,有兩個“視圖”的概念:
1.一個是 view。它是一個用查詢語句定義的虛擬表,在調用的時候執行查詢語句並生成結果。創建視圖的語法是 create view … ,而它的查詢方法與表一樣。
2.另一個是 InnoDB 在實現 MVCC 時用到的一致性讀視圖,即 consistent read view,用於支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重復讀)隔離級別的實現。
