高效能MYSQL LEC1 MYSQL架構
MYSQL並不完美,卻足夠靈活。
舉個例子,MYSQL能適應高要求的環境,比如web類應用。同時,MYSQL既可以嵌入到應用程式中,也可以支援資料倉庫、內容索引和部署軟體、高可用的冗餘系統、OLTP等各種應用型別。
That's why we need to learn its design. Only for pressing its performance under different circumstances.
1 MYSQL邏輯架構
MYSQL創新特性與邏輯架構
儲存引擎的架構,這種架構設計將Querying Process以及Server Task和Data Storage and Data Retrieval分離。簡而言之,分離查詢任務層與資料存取層
-
第一層的服務並不是MYSQL獨有的,大多數基於網路的C/S工具都有類似結構。比如連線處理、授權認證、安全等。
-
第二層包含MYSQL大部分核心功能,包括查詢解析、分析、優化、快取以及所有的內建函式(如時間、數學、加密函式)。所有跨儲存引擎的功能都在這一層實現:儲存過程、觸發器、檢視等。這一層通過API介面來和儲存引擎通訊,並以介面來遮蔽不同引擎間差異。
-
第三層包含儲存引擎,它負責MYSQL中資料的儲存與提取。和Linux下各個檔案系統一樣,每個儲存引擎都有其優勢和劣勢。儲存引擎API包含十幾個底層函式,用於執行“開始事務”或“根據主鍵提取一行記錄”等操作。但儲存引擎不會解析sql[1]
MYSQL連線管理與安全性
-
每個客戶端連線都會在伺服器程序中擁有一個執行緒。這個連線的查詢只會在這個單獨的執行緒中執行。伺服器會負責快取執行緒,因此不需要為每一個新建的連線建立或者銷燬程序[2]。
-
客戶端連線到MYSQL伺服器時,伺服器需要對其認證。比如認證可以基於使用者名稱、原始主機資訊和密碼。還能選擇SSL方式連線,可以使用數字證書認證。
-
連線成功後,伺服器會繼續檢查客戶端是否具有執行某個特定查詢的許可權。
MYSQL優化和執行
-
MYSQL會解析查詢,並建立解析樹,基於此進行優化,包括重寫查詢、決定表的讀取順序以及選擇合適的索引等[3]
- 使用者可以通過特殊關鍵字提示(hint)優化器,影響決策過程。
- 也可以請求優化器解釋(explain)優化過程的各個因素,展示伺服器是如何進行優化決策的。並提供一個參考基準,便於使用者重構查詢和schema,修改相關配置。
-
優化器不關心使用哪個儲存引擎,但儲存引擎對優化查詢有影響。優化器會請求儲存引擎提供容量或某個具體操作的開銷,以及表資料的統計資訊。比如,某些儲存引擎的某種索引,可能對特定查詢有優化。[4]why?這個不是建表就決定好用哪個嗎 -
對於SELECT操作,在解析查詢前就會查Query Cache,如果能在其中找到對應查詢,伺服器就不會再執行解析、優化和執行的整個過程,而是直接返回查詢快取中的結果集。[5]
2 併發控制
多個查詢在同一時刻對資料的修改,即為併發問題的本質。
併發可以通過加鎖解決,但簡單的加鎖會降低程式效能,不利於高併發。
讀寫鎖
也就是共享鎖和排他鎖
-
讀鎖共享,讀鎖間相互不阻塞
-
寫鎖排它,寫鎖阻塞其他寫鎖
-
讀鎖與寫鎖,互相阻塞
-
mysql中,寫鎖優先順序高於讀鎖。也就是,一個寫鎖請求可能會被插入到讀鎖佇列的前面。why?好處呢,難道是讀多寫少,避免寫餓死嗎,但一些情況下,寫多讀少怎麼辦?
鎖粒度
-
一種提高共享資源併發度方式是,降低鎖粒度。理想方式,只鎖定需要修改的資料。鎖的資料量越少,併發程度越高。
-
問題是加鎖也會提高系統開銷。比如系統會花費大量時間來管理鎖,包括獲得鎖、檢查鎖是否接觸、釋放鎖等,而不是存取資料。
-
所謂的鎖策略,就算在鎖的開銷、併發效能和資料的安全性之間權衡。
- 大多數db都是提供行級鎖,並以各種複雜方式實現,來保證鎖較多情況下效能。
- MYSQL提供多種選擇來針對不同場景,而非單一解決方案。每個儲存引擎都可以實現自己的鎖策略和鎖粒度。
表鎖
-
依舊分讀寫鎖。
-
表鎖自身開銷最小,但併發效能會低。
-
儘管儲存引擎可以管理自己的鎖,但MYSQL本身還是會使用各種有效的表鎖來實現不同目的。比如,伺服器會為如ALTER TABLE這樣的操作使用表鎖,而忽略儲存引擎的鎖機制。
行級鎖
- 依舊分讀寫鎖
- 行級鎖自身開銷最大,但併發效能最高。行級鎖只在儲存引擎層實現,而MYSQL服務層沒有實現。眾所周知,InnoDB和其他一些儲存引擎實現了行級鎖。伺服器層完全不瞭解儲存引擎層的鎖實現。
3 事務
事務的本質是,原子性。
只要事務其中一條失敗,那麼所有的語句都不會執行。也就是說,要麼全部成功,要麼全部失敗。全部成功後才能提交。
ACID
- 原子性(atomicity):事務要麼全部成功,要麼全部失敗。
- 一致性(consistency):資料庫總是從一個一致性的狀態轉換到另一個一致性的狀態。比如銀行賬戶間轉賬,先從A減去200,再給B加200。如果執行第二條出錯,就會產生不一致。
- 隔離性(isolation):一個事務的修改在提交前,對其他事務不可見。比如A-200後事務還在執行,另一個事務查A的餘額,則看到的還是A原來的餘額。
- 永續性(durability):一旦事務提交,所做的修改就會永久儲存,即使系統崩潰。永續性比較模糊,不可能做到100%的永續性,如果資料庫本身就能做到永續性,備份又怎麼能增加永續性。備份的備份。
- ACID在應用程式中,比如銀行系統,實現非常困難。資料庫來實現,也要做很多複雜工作來保證ACID。
- 事務就像鎖粒度,會增加系統開銷。非事務型的儲存引擎,可以獲得更高的效能。
隔離級別
個人覺得,本質原因是事務執行需要一段時間,這段時間內,其他事務讀取他執行範圍的資料會出現問題。如果不需要時間,瞬間完成,就不需要隔離級別,不會產生髒讀、不可重複讀和幻讀。
級別調整:SET TRANSACTION ISOLATION LEVEL
-
read uncommitted(未提交讀):
- 事務開始時,能看見未提交的事務的修改。
- 讀取到其他事務未提交的內容,被稱為髒讀。
- 髒讀會帶來許多問題,而且相比其他隔離級別,效能不會好太多,所以很少使用。
-
read committed(已提交讀):
- 事務開始時,只能看見已提交的事務的修改。
- 事務內部兩次執行相同的查詢得到不同結果,因為內容被修改過,被稱為不可重複讀。
- 大多數db預設隔離級別都是這個,但mysql預設是可重複讀。
-
repeatable read(可重複讀)
- 同一個事務多次讀取相同記錄的結果是一致的。
- 事務內部兩次執行相同的查詢,由於另一個事務在查詢範圍內新增一條記錄,產生了幻行。兩次不一致。InnoDB使用多版本併發控制(MVCC)來實現。
- mysql預設是可重複讀
-
serializable(可序列化)
- 強制事務序列執行,簡單來說,就是事務會在讀取到的每一行資料上都加鎖,可能導致大量鎖爭用和超時問題。只要兩個事務有相同的資料範圍,就必須序列執行。
- 讀取到的都加鎖,它是唯一加鎖的隔離級別。另外,加鎖讀還有另一個含義,在同一個事務中,如果你先查詢資料,隨後對相關資料進行插入或修改,那麼在標準的SLELECT中不會給出足夠的保護。在你查詢期間另一個事務可以更新或者刪除相同的行。這被稱為加鎖讀的原因。
- 實際很少使用,只有需要資料強一致並接收無併發的情況下才有可能使用。
- 怎麼實現加鎖讀?InnoDB提供兩種型別加鎖讀。
- SELECT ... LOCK IN SHARE MODE
- 給讀到的每一行都加共享鎖。其它的會話也可以讀到這些行,但是它們不能修改這些行,直到你的事務提交。如果這些行被其它事務修改了但尚未提交,查詢必須等待直到那個事務結束。
- 加意向共享鎖
- SELECT ... FOR UPDATE。
- 對於檢索到的每一個索引記錄,鎖定這些行和與之關聯的索引記錄。
- 加意向排它鎖
- SELECT ... LOCK IN SHARE MODE
隔離級別 | 髒讀可能性 | 不可重複讀可能性 | 幻讀可能性 | 加鎖讀 |
---|---|---|---|---|
未提交讀 | Y | Y | Y | N |
已提交讀 | N | Y | Y | N |
可重複讀 | N | N | Y | N |
序列化 | N | N | N | Y |
死鎖
-
多個事務在同一資源上相互佔用,並請求鎖定對方佔用資源,造成惡性迴圈。多個事務以不同順序鎖定相同資源,互相等待,就會產生死鎖。如下面兩個事務,假如都執行了第一條update,並鎖定該行。
START TRANSACTION; UPDATE A; UPDATE B; COMMIT; START TRANSACTION; UPDATE B; UPDATE A; COMMIT;
-
為了解決死鎖,db實現了各種死鎖檢測和死鎖超時的機制。
- 比如InnoDB就能檢測出死鎖的迴圈依賴,並立即返回錯誤。InnoDB目前處理死鎖的方式是,將持有最少行級排他鎖的事務回滾。(這是比較簡單的死鎖回滾方法)
-
死鎖超時,就是查詢時間達到鎖等待時間就放棄鎖請求,通常這種方式不太好。是我理解的,不管鎖了,直接改的意思嗎?
事務日誌
- 幫助提高事務的效率,類似輸入井,緩衝區。修改資料需要寫兩次磁碟。
-
緩衝池可以幫助我們消除CPU和磁碟間的鴻溝。也被稱作預寫式日誌。
- 使用事務日誌,當有更新操作時,儲存引擎只將資料在記憶體拷貝修改成更新後的值,但並不將資料的更新重新整理磁碟,只是將更新操作的行為記錄到硬碟上的事務日誌中。因為事務日誌的記錄是採用檔案追加的方式,因此寫日誌的操作是磁碟上一小塊區域內的順序I/O,而不像隨機I/O需要在磁碟的多個地方移動磁頭。並且僅僅記錄資料更新行為的資料量也非常小,所以事務日誌的記錄是非常快的。
- 事務日誌持久以後,對使用者而言,資料的更新操作就完成了。記憶體中被修改的資料在後臺可以慢慢的重新整理到真正的硬碟資料庫檔案中。這樣資料最終就被完整的儲存好了。通過事務日誌的方式更新資料,需要寫兩次磁碟,第一次是將更新行為記錄到事務日誌,第二次是真正的將更新後的資料記錄到資料庫檔案中。
-
如果資料的修改行為已經記錄到事務日誌,但資料本身還沒有寫到資料庫檔案中,此時系統崩潰,儲存引擎在重啟之後會自動根據事務日誌恢復這部分修改的資料。
- 緩衝池中的髒頁會以一定的頻率被刷入磁碟(check point機制)。事物日誌會通過一個標記點來確定某個事物是否已將快取中的資料寫入資料檔案。當db重啟後,它會檢視日誌中最新的標記點,並將這個標記點後面的事務記錄redo。
自動提交
- MYSQL預設自動提交(autocommit = 1)模式。
- autocommit = 1 ,如果不是顯式的開啟一個事務,每個查詢都會被當作一個事務執行提交。
- autocommit = 0,所有查詢都在一個事務裡,直到顯式執行了COMMIT或ROLLBACK。該事務結束,又開啟一個新的事務。
- 修改autocommit,對非事務型的表,比如MYISAM或者記憶體表,無任何影響。相當於一直是autocommit = 1。
- 在DDL裡,比如ALTER TABLE、LOCK TABLE等,執行前會強制COMMIT提交當前的活動事務。
儲存引擎混用
- 服務層不管事務,事務由儲存引擎實現。如果混合使用了事務型和非事務型的表(比如MYISAM表和InnoDB表),在正常提交不會出現問題,如果需要回滾,非事務型的表的變更無法撤銷,這種情況很難修復。
顯式和隱式鎖定
- 隱式鎖定:
- InnoDB採用兩階段鎖定協議。兩段鎖協議是指每個事務的執行可以分為兩個階段:生長階段(加鎖階段)和衰退階段(解鎖階段)。
- 加鎖階段:在該階段可以進行加鎖操作。在對任何資料進行讀操作之前要申請並獲得S鎖,在進行寫操作之前要申請並獲得X鎖。加鎖不成功,則事務進入等待狀態,直到加鎖成功才繼續執行。
- 解鎖階段:當事務釋放了一個封鎖以後,事務進入解鎖階段,在該階段只能進行解鎖操作不能再進行加鎖操作。
- 兩段封鎖法可以這樣來實現:事務開始後就處於加鎖階段,一直到執行ROLLBACK和COMMIT之前都是加鎖階段。ROLLBACK和COMMIT使事務進入解鎖階段,即在ROLLBACK和COMMIT模組所有的鎖同一時刻被釋放。 在對任何資料進行讀、寫操作之前,要申請並獲得對該資料的封鎖。每個事務中,所有的封鎖請求先於所有的解鎖請求。可以證明若併發執行的所有事務均遵守兩段鎖協議,則對這些事務的任何併發排程策略都是可序列化的
- InnoDB採用兩階段鎖定協議。兩段鎖協議是指每個事務的執行可以分為兩個階段:生長階段(加鎖階段)和衰退階段(解鎖階段)。
- 顯式鎖定:
4 多版本併發控制
實現讀已提交與可重複讀。