溫習《高性能MySQL》的第一章 MySQL架構與歷史 1.1 MySQL邏輯架構 參考http://www.cnblogs.com/baochuan/archive/2012/03/15/2397536.html 圖1-1:MySQL伺服器邏輯架構圖 最上層的服務並不是MySQL所獨有的,大多數基 ...
溫習《高性能MySQL》的第一章 MySQL架構與歷史
1.1 MySQL邏輯架構
參考http://www.cnblogs.com/baochuan/archive/2012/03/15/2397536.html
圖1-1:MySQL伺服器邏輯架構圖
最上層的服務並不是MySQL所獨有的,大多數基於網路的客戶端/伺服器的工具或者服務都有類似的架構。比如連接處理、授權認證、安全等等。
第二層架構是MySQL比較有意思的部分。大多數MySQL的核心服務功能都在這一層,包括查詢解析、分析、優化、緩存以及所有的內置函數(例如,日期、時間、數學和加密函數),所有跨存儲引擎的功能都在這一層實現:存儲過程、觸發器、視圖等。
第三層包含了存儲引擎。存儲引擎負責MySQL中數據的存儲和提取。和GNU/Linux下的各種文件系統一樣,每個存儲引擎都有它的優勢和劣勢。伺服器通過API與存儲引擎進行通信。這些介面屏蔽了不同存儲引擎之間的差異,使得這些差異對上層的查詢過程透明。存儲引擎API包含幾十個底層函數,用於執行諸如“開始一個事務”或者“根據主鍵提取一行記錄”等操作。但存儲引擎不會去解析SQL,不同存儲引擎之間也不會相互通信,而只是簡單地響應上層伺服器的請求。
1.2 併發控制
1.2.1 讀寫鎖
這兩種類型的鎖通常被稱為共用鎖(shared lock)和排他鎖(exclusive lock),也叫讀鎖(read lock)和寫鎖(write lock)。讀鎖是共用的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一個資源,而互不幹擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖。
1.2.2 鎖粒度
兩種最重要的鎖策略:表鎖和行級鎖
表鎖(table lock)
表鎖是MySQL中最基本的鎖策略,並且是開銷最小的策略。它會鎖定整張表。一個用戶在對錶進行寫操作(插入、刪除、更新等)前,需要先獲得寫鎖,這會阻塞其他用戶對該表的所有讀寫操作。只有沒有寫鎖時,其他讀取的用戶才能獲得讀鎖,讀鎖之間是不相互阻塞的。
在特定的場景中,表鎖也可能有良好的性能。例如,READ LOCAL表鎖支特某些類型的併發寫操作。另外,寫鎖也比讀鎖有更高的優先順序,因此一個寫鎖請求可能會被插入到讀鎖隊列的前面(寫鎖可以插入到鎖隊列中讀鎖的前面,反之讀鎖則不能插入到寫鎖的前面)。
行級鎖( row lock)
行級鎖可以最大程度地支持併發處理(同時也帶來了最大的鎖開銷)。眾所周知,在InnoDB和XtraDB,以及其他一些存儲引擎中實現了行級鎖。行級鎖只在存儲引擎層實現,而MySQL伺服器層沒有實現。伺服器層完全不瞭解存儲引擎中的鎖實現。
1.3 事務
事務支持ACID原則。
原子性(atomicity)
一個事務必須被視為一個不可分割的最小工作單元。
一致性(consistency)
資料庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。
隔離性(isolation)
通常來說,一個事務所做的修改在最終提交以前,對其他事務是不可見的。
持久性(durability)
一旦事務提交,則其所做的修改就會永久保存到資料庫中。
1.3.1 隔離級別
下麵簡單地介紹一下四種隔離級別。
READ UNCOMMITTED(未提交讀)
在READ UNCOMMITTED級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的數據,這也被稱為臟讀(Dirty Read)。這個級別會導致很多問題,從性能上來說,READ UNCOMMITTED不會比其他的級別好太多,但卻缺乏其他級別的很多好處,除非真的有非常必要的理由,在實際應用中一般很少使用。
READ COMMITTED(提交讀)
大多數資料庫系統的預設隔離級別都是READ COMMITTED(但MySQL不是)。一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。這個級別有時候也叫做不可重覆讀(nonrepeatable read),因為兩次執行同樣的查詢,可能會得到不一樣的結果。
REPEATABLE READ(可重覆讀)
REPEATABLE READ解決了臟讀的問題。該級別保證了在同一個事務中多次讀取同樣記錄的結果是一致的。但是理論上,可重覆讀隔離級別還是無法解決另外一個幻讀 (Phantom Read)的問題。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行(Phantom Row)。InnoDB和XtraDB存儲引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)解決了幻讀的問題。
可重覆讀是MySQL的預設事務隔離級別。
SERIALIZABLE(可串列化)
SERIALIZABLE是最高的隔離級別。它通過強制事務串列執行,避免了前面說的幻讀的問題。簡單來說,SERIALIZABLE會在謨取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用的問題。實際應用中也很少用到這個隔離級別,只有在非常需要確保數據的一致性而且可以接受沒有併發的情況下,才考慮採用該級別。
1.3.2 死鎖
死鎖是指兩個或者多個事務在同一資源上相互占用,並請求鎖定對方占用的資源,從而導致惡性迴圈的現象。當多個事務試圖以不同的順序鎖定資源時,就可能會產生死鎖。多個事務同時鎖定同一個資源時,也會產生死鎖。
為瞭解決這種問題,資料庫系統實現了各種死鎖檢測和死鎖超時機制。越複雜的系統,比如InnoDB存儲引擎,越能檢測到死鎖的迴圈依賴,並立即返回一個錯誤。這種解決方式很有效,否則死鎖會導致出現非常慢的查詢。還有一種解決方式,就是當查詢的時間達到鎖等待超時的設定後放棄鎖請求,這種方式通常來說不太好。InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾(這是相對比較簡單的死鎖回滾演算法)。
鎖的行為和順序是和存儲引擎相關的。以同樣的順序執行語句,有些存儲引擎會產生死鎖,有些則不會。死鎖的產生有雙重原因:有些是因為真正的數據衝突,這種情況通常很難避免,但有些則完全是由於存儲引擎的實現方式導致的。
1.3.3 事務日誌
使用事務日誌,存儲引擎在修改表的數據時只需要修改其記憶體拷貝,再把該修改行為記錄到持久在硬碟上的事務日誌中,而不用每次都將修改的數據本身持久到磁碟。事務日誌採用的是追加的方式。事務日誌持久以後,記憶體中被修改的數據在後臺可以慢慢地刷回到磁碟。目前大多數存儲引擎都是這樣實現的,我們通常稱之為預寫式日誌(Write-Ahead Logging),修改數據需要寫兩次磁碟。
如果數據的修改已經記錄到事務日誌並持久化,但數據本身還沒有寫回磁碟,此時系統崩潰,存儲引擎在重啟時能夠自動恢復這部分修改的數據。具體的恢復方式則視存儲引擎而定。
1.3.4 MySQL中的事務
1.4 多版本併發控制
MVCC的實現,是通過保存數據在某個時間點的快照來實現的。也就是說,不管需要執行多長時間,每個事務看到的數據都是一致的。根據事務開始的時間不同,每個事務對同一張表,同一時刻看到的數據可能是不一樣的。下麵我們通過InnoDB的簡化版行為來說明MVCC是如何工作的。
InnoDB的MVCC,是通過在每行記錄後面保存兩個隱藏的列來實現的。這兩個列,一個保存了行的創建時間,一個保存行的過期時間(或刪除時間)。當然存儲的並不是實際的時間值,而是系統版本號(system version number)。每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和查詢到的每行記錄的版本號進行比較。下麵看一下在REPEATABLE READ隔離級別下,MVCC具體是如何操作的。
SELECT
InnoDB會根據以下兩個條件檢查每行記錄:
a.InnoDB只查找版本早於當前事務版本的數據行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣可以確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的。
b.行的刪除版本要麼未定義,要麼大於當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。
只有符合上述兩個條件的記錄,才能返回作為查詢結果。
INSERT
InnoDB為新插入的每一行保存當前系統版本號作為行版本號。
DELETE
InnoDB為刪除的每一行保存當前系統版本號作為行刪除標識。
UPDATE
InnoDB為插入一行新記錄,保存當前系統版本號作為行版本號,同時保存當前系統版本號到原來的行作為行刪除標識。
保存這兩個額外系統版本號,使大多數讀操作都可以不用加鎖。這樣設計使得讀數據操作很簡單,性能很好,並且也能保證只會讀取到符合標準的行。不足之處是每行記錄都需要額外的存儲空間,需要做更多的行檢查工作,以及一些額外的維護工作。
MVCC只在REPEATABLE READ和READ COMMITTED兩個隔離級別下工作。其他兩個隔離級別都和MVCC不相容註4,因為READ UNCOMMITTED總是讀取最新的數據行,而不是符合當前事務版本的數據行。而SERIALIZABLE則會對所有讀取的行都加鎖。
1.5 MySQL的存儲引擎
在文件系統中,MySQL將每個資料庫(也可以稱之為schema)保存為數據目錄下的一個子目錄。創建表時,MySQL會在資料庫子目錄下創建一個和表同名的.frm文件保存表的定義。例如創建一個名為MyTable的表,MySQL會在MyTable.frm文件中保存該表的定義。因為MySQL使用文件系統的目錄和文件來保存資料庫和表的定義,大小寫敏感性和具體的平臺密切相關。在Windows中,大小寫是不敏感的;而在類Unix中則是敏感的。不同的存儲引擎保存數據和索引的方式是不同的,但表的定義則是在MySQL服務層統一處理的。
可以使用SHOW TABLE STATUS命令(在MySQL 5.0以後的版本中,也可以查詢INFORMATION SCHEMA中對應的表)顯示表的相關信息。例如,對於mysql資料庫中的user表:
mysql> SHOW TABLE STATUS LIKE 'user' \G
Name: user
Engine: MyISAM
Row_format: Dynamic
Rows : 6
Avg_row_length: 59
Data length: 356
Max data length: 4294967295
Index length: 2048
Data_free: 0
Auto_increment: NULL
Create_time: 2002-01-24 18:07:17
Update_time : 2002 -01-24 21: 56 : 29
Check_time: NULL
Collation : ut f8_bin
Checksum: NULL
Create_options :
Comment: Users and global privileges
1 row in set (o.oo sec)
榆出的結果表明,這是一個MyISAM表。輸出中還有很多其他信息以及統計信息。下麵簡單介紹一下每一行的含義。
Name |
表名。 |
Engine |
表的存儲引擎類型。在舊版本中,該列的名字叫Type,而不是Engine。 |
Row- format |
行的格式。對於MyISAM表,可選的值為Dynamic、Fixed或者Comp ressed。Dynamic的行長度是可變的,一般包含可變長度的欄位,如VARCHAR或BLOB。Fixed的行長度則是固定的,只包含固定長度的列,如CHAR和INTEGER。Compressed的行則只在壓縮表中存在。 |
Rows |
表中的行數。對於MyISAM和其他一些存儲引擎,該值是精確的,但對於InnoDB,該值是估計值。 |
Avg_ row_length |
平均每行包含的位元組數。 |
Data_length |
表數據的大小(以位元組為單位)。 |
Max- data_length |
表數據的最大容量,該值和存儲引擎有關。 |
Index_length |
索引的大小(以位元組為單位)。 |
Data_free |
對於MyISAM表,表示已分配但目前沒有使用的空間。這部分空間包括了之前刪除的行,以及後續可以被INSERT利用到的空間。 |
Auto_increment |
下一個AUTO INCREMENT的值。 |
Create_time |
表的創建時間。 |
Update_time |
表數據的最後修改時間。 |
Check_ time |
使用CKECK TABLE命令或者myisamchk工具最後一次檢查表的時間。 |
Collation |
表的預設字元集和字元列排序規則。 |
Checksum |
如果啟用,保存的是整個表的實時校驗和。 |
Create_options |
刨建表時指定的其他選項。 |
Comment |
該列包含了一些其他的額外信息。對於MyISAM表,保存的是表在創建時帶的註釋。對於InnoDB表,則保存的是InnoDB表空間的剩餘空間信息。如果是一個視圖,則該列包含“VIEW”的文本字樣。 |
1.6 MySQL時間線
1.7 MySQL的開發模式
參考:《高性能 MySQL》