《高效能MySQL》讀書筆記---第一章:MySQL架構與歷史
本章描述了MySQL的伺服器架構、各種儲存引擎之間的主要區別,以及這些區別的重要性
1.1 MySQL邏輯架構
MySQL的邏輯架構如下圖所示:
第一層:該層的服務並不是MySQL獨有的,大多數基於網路的客戶端/伺服器的工具或者伺服器都有類似的架構。如連線處理、授權認證、安全等
第二層:MySQL的核心服務功能層,包括查詢解析、分析、優化、快取以及所有的內建函式(如日期、時間、數學和加密函式)。所有跨儲存引擎的功能都在這一層實現:儲存過程、觸發器、檢視等。
第三層:包含了儲存引擎。儲存引擎負責MySQL中資料的儲存和提取。
伺服器通過API與儲存引擎進行通訊。這些介面遮蔽了不同儲存引擎之間的差異,使得這些差異對上層的查詢過程透明。
儲存引擎API包含幾十個底層函式,用於執行注入“開始一個事務”或者“根據主鍵提取一行記錄”等操作。
儲存引擎不會去解析SQL,不同儲存引擎之間也不會相互通訊,而知簡單相應上層伺服器的請求。
1.1.1 連線管理與安全性
每個客戶端都會在伺服器中擁有一個執行緒,這個連線的查詢只會在這單獨的執行緒中執行,該執行緒只能輪流在某個CPU核心或者CPU中執行
伺服器負責快取執行緒,因此不需要為每一個新建的連線直接建立或者銷燬執行緒
注:MySQL 5.5或更高的版本提供了一個API,支援執行緒池(Thread-Pooling)外掛,可以使用池中少量的執行緒來服務大量的連線
當客戶端(應用)連線到MySQL伺服器時,伺服器會基於使用者名稱、原始主機資訊和密碼來進行認證,連線成功後,伺服器對該客戶端的許可權進行判斷
1.1.2 優化與執行
MySQL會解析查詢,並建立內部資料結構(解析樹),然後對其進行各種優化,包括重寫查詢、決定表的讀取順序,以及選擇合適的索引等。
優化器並不關心表使用的是什麼儲存引擎,但儲存引擎對於優化查詢是有影響的
優化器會請求儲存引擎提供容量或某個具體操作的開銷資訊,以及表資料的統計資訊等。
對於select語句,在解析查詢前,伺服器會先檢查查詢快取(Query Cache),如果能夠在其中找到對應的查詢,伺服器就不必再執行查詢解析、優化和執行的整個過程,而是直接返回查詢快取中的結果集
1.2 併發控制
無論何時,只要有多個查詢需要在同一時刻修改資料,都會產生併發控制的問題
MySQL的併發控制存在於兩個層面:
1. 伺服器層
2. 儲存引擎層
1.2.1 讀寫鎖
在處理同一時刻的讀和寫操作時,可採取併發控制的方法
在處理併發讀或者寫時,可以通過實現一個有兩種型別的鎖 組成的鎖系統 來解決問題
兩種型別為:
1、共享鎖(shared lock),也可叫讀鎖(read lock)
2、排他鎖(exclusive lock),也可叫寫鎖(write lock)
鎖的概念:
讀鎖:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一個資源,而互不干擾
寫鎖:寫鎖時排他的,一個寫鎖會阻塞其他的寫鎖和讀鎖,這樣可以確保在給定的時間裡,只有一個使用者執行寫入,並防止其他使用者讀取正在寫入的同一資源。
1.2.2 鎖粒度
一種提高共享資源併發性的方式就是讓鎖定物件更有選擇性
儘量之鎖定需要修改的部分資料,而不是所有的資源。
更理想的方法是,只對會修改的資料片進行精確的鎖定。任何時候,在給定的資源上,鎖定的資料越少,則系統的併發程度越高,只要相互發生衝突即可。
由此會增加系統開銷,導致伺服器效能下降
而所謂的鎖策略,就是在鎖的開銷和資料的安全性之間尋求平衡,而平衡也會影響到效能。
在儲存引擎的設計中,鎖管理是非常重要的。將鎖粒度固定在某個級別,可以為某些特定的場景提供更好的效能,但會影響到其他應用場景。
MySQL支援多個儲存引擎的架構,所以不需要單一的通用解決方案。
兩種重要的所策略:
1、表鎖(table lock)
2、行級鎖(row lock)
表鎖
表鎖會鎖定整張表,一個使用者在對錶進行讀寫操作(插入、刪除、更新等)前,需先獲得寫鎖,從而阻塞其他使用者對該表的所有讀寫操作。
只有沒有寫鎖時,其他讀取的使用者才能獲得讀鎖,讀鎖之間不會相互阻塞
寫鎖也比讀鎖有更高的優先順序,因此一個寫鎖請求可能會被插入到讀鎖佇列的前面(寫鎖可以插入到鎖佇列中讀鎖的前面,反之讀鎖則不能插入到寫鎖的前面)
行級鎖
行級鎖可以最大程度地支援併發處理(同時也帶來了最大的鎖開銷)。
行級鎖只在儲存引擎層實現,而MySQL伺服器曾沒有實現。伺服器層完全不瞭解儲存引擎中的鎖實現。
1.3 事務
事務:一組原子性的SQL查詢,或者說一個獨立的工作單元。
事務內的語句,要麼全部執行成功,要麼全部執行失敗
事務具有一個ACID的概念:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、永續性(Durability)
一個執行良好的事務處理系統,必須具備ACID特性
原子性(Atomicity)
事務的原子性:一個事務必須被視為一個不可分割的最小工作單元,事務中的操作要麼全成功,要麼全失敗回滾,不可能只執行其中的一部分操作。
一致性(Consistency)
事務的一致性:資料庫總是從一個一致性的狀態,轉換到另一個一致性的狀態。事務沒有提交時,事務中所中的修改也不會儲存到資料庫。
隔離性(Isolation)
通常來說,一個事務所做的修改在最終提交之前,對其他事務是不可見的。
永續性(Durability)
一旦事務提交,則其所做的修改就會永久儲存到資料庫中。此時及時系統崩潰,修改的資料也不會丟失。
一個解讀ACID的例子:
假設一個銀行的資料庫有兩張表,支票和儲蓄,使用者進行轉賬200元的操作時,有以下三個步驟:
1、檢查支票賬戶中的餘額高於200元
2、從支票賬戶餘額中減去200元
3、在儲蓄賬戶餘額中增加兩百元
過程包含以下5個sql語句:
1 START TRANSACTION;
2 SELECT balance FROM checking WHERE customer_id = 1234567;
3 UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 1234567;
4 UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 1234567;
5 COMMIT;
對於原子性,以上5個語句可組成一個事務
對於一致性,如確保了一致性,即使在執行了第三、第四條語句時出錯,支票賬戶中也不會損失200元
對於隔離性,當執行完第三條語句、第四條語句還未開始時,此時有另一個賬戶彙總程式開始執行,則支票餘額不會減少200元
對於永續性,一旦commit,則支票賬戶減少200,儲蓄增加200,並永久儲存在資料庫中
與鎖粒度的升級會增加系統開銷一樣,一個實現了ACID的資料庫,需要更強的CPU處理能力、更大的記憶體和更多的磁碟空間。
使用者可以根據業務是否需要事務處理,來選擇合適的儲存引擎,對於不需要事務的查詢類應用,選擇一個非事務型的儲存引擎,可以獲得更高的效能。
1.3.1 隔離級別
SQL標準中定義了四種隔離級別,每種級別都規定了一個事務中所做的修改,哪些在事務內和事務間可見,哪些不可見。
較低級別的隔離通常可以執行更高的併發,系統的開銷也更低。
四種隔離級別如下:
READ UNCOMMITTED(未提交讀)
READ COMMITTED(提交讀)
REPEATABLE READ(可重複讀)
SERIALIZABLE(可序列化)
READ UNCOMMITTED(未提交讀)
在該級別中,事務中的修改,及時沒有提交,對其他事務也都是可見的
事務可以讀取未提交的資料,也被成為髒讀(Dirty Read)
該級別在效能上來說,不會比其他級別好太多,但卻缺乏其他級別的很多好處,在實際應用中一般很少使用
READ COMMITTED(提交讀)
大多數資料庫系統的預設隔離級別都是READ COMMITTED,但MySQL不是
READ COMMITTED滿足前面提到的隔離性定義:一個事務開始時,只能“看見”已經提交的事務所做的修改
一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的
REPEATABLE READ(可重複讀)
該隔離級別解決了髒讀的問題,保證了在同一個事務中多次讀取同樣記錄的結果是一致的。
但是理論上,該級別還是無法解決另外一個幻讀(Phantom Read)的問題。
幻讀:指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行(Phantom Row)。
InnoDB和XtraDB儲存引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)解決了幻讀的問題
可重複讀是MySQL的預設事務隔離級別
SERIALIZABLE(可序列化)
該級別是最高的隔離級別。它通過強制事務穿行執行,避免了前面說的幻讀問題
該級別會在讀取的每一行資料都加鎖,所以可能導致大量的超時和鎖爭用的問題
只有在非常需要確保資料的一致性而且可以接受沒有併發的情況下,才考慮採用該級別
1.3.2 死鎖
死鎖是指兩個或者多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性迴圈的現象。
死鎖的例子:
死鎖的兩個解決方式:
1.儲存引擎檢測到死鎖的迴圈依賴,並立即返回錯誤
2.當查詢的時間達到鎖等待超時的設定後,放棄鎖請求,這種方式不太友好
InnoDB的處理方法:將持有最少行級排他鎖的事務進行回滾
死鎖產生的雙重原因:
1.真正的資料衝突
2.儲存引擎的實現方式
死鎖發生後,只有部分或者完全回滾其中一個事務,才能打破死鎖(事務型的系統無法避免死鎖)
1.3.3 事務日誌
事務日誌可以幫助提高事務的效率
使用事務日誌,儲存引擎在修改表的資料時,只需要將修改行為記錄到硬碟上的事務日誌中,而不需要每次都將修改的資料本身持久化到磁碟。
事務日誌持久化後,記憶體中被修改的資料在後臺可以慢慢同步到磁碟,成為預寫式日誌(Write-Ahead Logging),修改資料需要些兩次磁碟(修改行為和資料本身)
若在資料同步的過程,發生了系統崩潰,儲存引擎在重啟時能夠自動回覆這部分修改的資料。具體恢復方式視儲存引擎而定
1.3.4 MySQL中的事務
MySQL提供了兩種事務型的儲存引擎:InnoDB和NDB Cluster
第三方引擎:XtraDB和PBXT,支援事務
自動提交(AUTOCOMMIT)
MySQL預設採用自動提交模式。如果不是顯式開始一個事務,則每個查詢都被當做一個事務執行提交操作。
啟用或者禁用的操作如下圖:
1:ON,表示啟動 0:OFF,表示禁用
當值為0時,所有的查詢都在一個事務中,直到顯式執行commit提交或者rollback回滾。
修改autocommit的值,對非事務型的表(如myisam,記憶體表),沒有任何影響,因其沒有commit或者rollback的概念
當執行導致大量資料改變的操作時,如ALTER TABLE,LOCK TABLE,在執行之前會強制執行commit提交當前的活動事務
MySQL可以通過一下命令來設定隔離級別:
mysql> SET SESSION TRANSACTIONLEVEL READ COMMITTED;
MySQL能夠識別所有的4個ANSI隔離級別,InooDB也支援所有隔離級別
在事務中混合使用儲存引擎
MySQL伺服器層不管理事務,事務是由下層的儲存引擎實現的。所以在同一事務中,使用多種儲存引擎是不可靠的。
如果在事務中混用了事務型和非事務型的表(如InnoDB和MyISAM表),可正常提交,但在回滾時,由於非事務型的表上的變更就無法撤銷,就會導致資料庫處於不一致的狀態,這將會導致難以修復的後果
大多數情況下,對非事務型的操作都不會有提示
隱式和顯式鎖定
InnoDB採用的是兩階段鎖定協議(two-phase locking protocol):在事務執行過程中,隨時都可以加鎖;當執行commit或者rollback的時候才會釋放,並且所有的鎖是在同一時刻被釋放。
在1.2.1節描述的鎖都是隱式鎖定,InnoDB會根據隔離級別在需要的時候自動加鎖。
InnoDB也支援通過特定的語句進行顯式鎖定,MySQL可以顯式使用LOCK TABLES和UNLOCK TABLES語句。
除了事務中禁用了AUTOCOMMIT,可以使用LOCK TABLES外,其他時候都不要顯式執行LOCK TABLES。
1.4 多版本併發控制
基於提升併發效能的考慮,MySQL的大多數事務型儲存引擎都事先了多版本控制(MVCC)
MVCC的實現,是通過儲存資料在某個時間點的快照來實現的。
不管執行多長時間,每個事務看到的資料都是一致的
根據事務開始的時間不同,每個事務在同一張表,同一時刻看到的資料可能是不一樣的
不同儲存引擎的MVCC實現是不通的,典型的有樂觀(optimistic)併發控制和悲觀(pessimistic)併發控制
InnoDB的MVCC,是通過在每行記錄後面儲存有兩個隱藏的列來實現的。一個儲存了行的建立時間,一個儲存了行的過期時間
儲存的是系統版本號(system version number),而不是實際的時間值
每開始一個新的事務,系統版本號都會遞增
事務開始時刻的系統版本號會作為事務的版本號,用來查詢到的每行記錄的版本號進行比較
在REPEATABLE READ隔離級別下,MVCC的具體操作:
SELECT
InnoDB會根據一下兩個條件查詢每行記錄:
a. InnoDB只查詢版本早於當前事務版本的資料行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣可以確保事務讀取到的行,要麼是在事務開始之前已經存在,要麼是事務自身插入或者修改過的
b. 行的刪除版本要麼未定義,要麼大於當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。
只有符合上述兩個條件的記錄,才能返回查詢結果
INSERT
InnoDB為新插入的每一行儲存當前系統版本號作為行版本號
DELETE
InnoDB為刪除的每一行儲存當前系統版本號作為行刪除標識
UPDATE
InnoDB為插入的每一行新紀錄,儲存當前系統版本號作為行版本號,同事儲存當前系統版本號到原來的行作為行刪除標識
儲存這兩個系統版本號,使大多數操作都可以不用加鎖
這樣的設計使得讀資料操作很簡單,效能很好,並且也能保證只會讀取到符合標準的行
不足之處:需要額外儲存空間,需要做更多的行檢查工作
MVCC只在REPEATABLE READ 和 READ COMMITTED兩個隔離級別下工作,而不相容其他兩個隔離級別,因為READ UNCOMMITTED總是讀取最新的資料行,而不是符合當前事務版本的資料行,而SERIALZABLE則會對所有讀取的行都加鎖