37 第一章 MySQL架構與歷史 高性能MySql閱讀總結

????????最上層的服務井不是MySQL所獨有的, 大多數基于網絡的客戶端/服務器的工具或者 服務都有類似的架構。 比如連接處理、 授權認證、 安全等等。

????????第二層架構是MySQL比較有意思的部分。 大多數MySQL的核心服務功能都在這一層,包括查詢解析、 分析、 優化、 緩存以及所有的內置函數(例如, 日期、 時間、 數學和加密函數), 所有跨存儲引擎的功能都在這一層實現:存儲過程、 觸發器、 視圖等。

????????第三層包含了存儲引擎。 存儲引擎負責MySQL中數據的存儲和提取。 和GNU/Linux下的各種文件系統一樣, 每個存儲引擎都有它的優勢和劣勢。 服務器通過API與存儲引擎 進行通信。 這些接口屏蔽了不同存儲引擎之間的差異,使得這些差異對上層的查詢過程透明。 存儲引擎API包含兒十個底層函數, 用千執行諸如 “開始一個事務” 或者 ”根據主鍵提取一行記錄” 等操作。 但存儲引擎不會去解析SQL注 1, 不同存儲引擎之間也不會相互通信, 而只是簡單地響應上層服務器的請求。

1.1.1連接管理與安全性

????????每個客戶端連接都會在服務器進程中擁有一個線程, 這個連接的查詢只會在這個單獨的 線程中執行, 該線程只能輪流在某個CPU核心或者CPU中運行。 服務器會負責緩存線程, 因此不需要為每一個新建的連接創建或者銷毀線程注

????????當客戶端(應用) 連接到MySQL服務器時,服務器需要對其進行認證。 認證基于用戶名、原始主機信息和密碼。 如果使用了安全套接字(SSL)的方式連接, 還可以使用X.509 證書認證。 一且客戶端連接成功, 服務器會繼續驗證該客戶端是否具有執行某個特定查詢的權限(例如, 是否允許客戶端對world數據庫的Country表執行SELECT語句)。

1.2.1讀寫鎖

????????這里先不討論鎖的具體實現,描述一下鎖的概念如下:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一個資源,而互不干擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖,這是出于安全策略的考慮,只有這樣,才能確保在給定的時間里,只有一個用戶能執行寫入,并防止其他用戶讀取正在寫入的同一資源。

????????在實際的數據庫系統中,每時每刻都在發生鎖定,當某個用戶在修改某一部分數據時,MySQL會通過鎖定防止其他用戶讀取同一數據。大多數時候,MySQL鎖的內部管理都是透明的。

1.2.2鎖粒度

????????在給定的資源上,鎖定的數據量越少,則系統的并發程度越高,只要相互之間不發生沖突即可。

? ??????每種MySQL存儲引擎都可以實現自己的鎖策略和鎖粒度。

表鎖(table lock)

? ??????表鎖是MySQL中最基本的鎖策略, 并且是開銷最小的策略。 表鎖非常類似于前文描述 的郵箱加鎖機制:它會鎖定整張表。 一個用戶在對表進行寫操作(插入、 刪除、 更新等)前, 需要先獲得寫鎖, 這會阻塞其他用戶對該表的所有讀寫操作。 只有沒有寫鎖時, 其他讀取的用戶才能獲得讀鎖, 讀鎖之間是不相互阻塞的。

????????在特定的場景中, 表鎖也可能有良好的性能。 例如,READ LOCAL表鎖支持某些類型的并發寫操作。 另外, 寫鎖也比讀鎖有更高的優先級, 因此一個寫鎖請求可能會被插入到讀鎖隊列的前面(寫鎖可以插入到鎖隊列中讀鎖的前面, 反之讀鎖則不能插入到寫鎖的前 面)。

行級鎖(rowlock)

????????行級鎖可以最大程度地支持井發處理(同時也帶來了最大的鎖開銷)。眾所周知,在 InnoDB和XtraDB,以及其他一些存儲引擎中實現了行級鎖。行級鎖只在存儲引擎層實現,而MySQL服務器層(如有必要,請回顧前文的邏輯架構圖)沒有實現。服務器層完全不了解存儲引擎中的鎖實現。

1.3 事務

????????在理解事務的概念之前, 接觸數據庫系統的其他高級特性還言之過早。事務就是一組原子性的SQL查詢, 或者說一個獨立的工作單元。如果數據庫引擎能夠成功地對數據庫應用該組查詢的全部語句, 那么就執行該組查詢。如果其中有任何一條語旬因為崩潰或其他原因無法執行, 那么所有的語句都不會執行。也就是說, 事務內的語句, 要么全部執行成功, 要么全部執行失敗。

????????可以用START TRANSACTION語句開始一個事務, 然后要么使用COMMIT提交事務將修改的數據持久保留, 要么使用ROLLBACK撤銷所有的修改。事務SQL的樣本如下:

????????除非系統通過嚴格的ACID測試, 否則空談事務的概念是不夠的。ACID表示原子性(atomicity) 、一致性(consistency) 、隔離性(isolation) 和持久性(durability) 。一個運行良好的事務處理系統, 必須具備這些標準特征。

原子性(atomicity)

????????一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要么全部提交成功, 要么全部失敗回滾,對千一個事務來說, 不可能只執行其中的一部分操作,這就是事務的原子性。

一致性(consistency)?

????????數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。在前面的例子中,一致性確保了, 即使在執行第三、四條語句之間時系統崩潰, 支票賬戶中也不會損失200美元,因為事務最終沒有提交,所以事務中所做的修改也不會保存到數據庫中。

隔離性(isolation)

????????通常來說, 一個事務所做的修改在最終提交以前,對其他事務是不可見的。在前面的例子中, 當執行完第三條語句、第四條語句還未開始時, 此時有另外一個賬戶匯總程序開始運行,則其看到的支票賬戶的余額井沒有被減去200美元。后面我們討論隔離級別(Isolation level)的時候,會發現為什么我們要說“通常來說” 是不可見的。

持久性(durability)

????????一且事務提交,則其所做的修改就會永久保存到數據庫中。此時即使系統崩潰,修改的數據也不會丟失。持久性是個有點模糊的概念, 因為實際上持久性也分很多不同的級別。有些持久性策略能夠提供非常強的安全保障, 而有些則未必。而且不可能有能做到100%的持久性保證的策略

1.3.1隔離級別

????????隔離性其實比想象的要復雜。在 SQL 標準中定義了四種隔離級別,每一種級別都規定了一個事務中所做的修改,哪些在事務內和事務間是可見的,哪些是不可見的。較低級別 的隔離通常可以執行更高的并發,系統的開銷也更低。

READ UNCOMMITTED (未提交讀)

????????在READ UNCOMMITTED 級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的數據,這也被稱為臟讀(Dirty Read)。這個級別會導致很多問題,從性能上來說,READ UNCOMMITTED 不會比其他的級別好太多,但卻缺乏其他級別的很多好處,除非真的有非常必要的理由,在實際應用中一般很少使用。

READ COMMITTED (提交讀)

????????大多數數據庫系統的默認隔離級別都是READ COMMITTED (但MySQL 不是)。READCOMMITTED 滿足前面提到的隔離性的簡單定義: 一個事務開始時,只能“ 看見” 已經提交的事務所做的修改。換句話說,一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。這個級別有時候也叫做不可重復讀(nonrepe atableread), 因為兩次執行同樣的查詢,可能會得到不一樣的結果。

REPEATABLE READ (可重復讀)

????????REPEATABLE READ解決了臟讀的問題。該級別保證了在同一個事務中多次讀取同樣記錄的結果是一致的。但是理論上,可重復讀隔離級別還是無法解決另外一個幻讀(Phantom Read)的問題。所謂幻讀,指的是當某個事務在讀取某個范圍內的記錄時,另外一個事務又在該范圍內插入了新的記錄,當之前的事務再次讀取該范圍的記錄時,會產生幻行(Phantom Row)。InnoDB和XtraDB存儲引擎通過多版本并發控制(MVCC, Multiversion Concurrency Control)解決了幻讀的問題。本章稍后會做進一步的討論。可重復讀是MySQL 的默認事務隔離級別。

SERIALIZABLE (可串行化)

????????SERIALIZABLE 是最高的隔離級別。它通過強制事務串行執行,避免了前面說的幻讀的問題。簡單來說,SERIALIZABLE 會在讀取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用的問題。實際應用中也很少用到這個隔離級別,只有在非常需要確保數據的一致性而且可以接受沒有井發的情況下,才考慮采用該級別。



1.3.2死鎖

????????死鎖是指兩個或者多個事務在同一資源上相互占用, 井請求鎖定對方占用的資源, 從而導致惡性循環的現象。 當多個事務試圖以不同的順序鎖定資源時, 就可能會產生死鎖。多個事務同時鎖定同一個資源時, 也會產生死鎖。InnoDB 目前處理死鎖的方法是, 將持有最少行級排他鎖的事務進行回滾(這是相對比較簡單的死鎖回 滾算法)。

1.3.3事務日志

????????事務日志可以幫助提高事務的效率。 使用事務日志, 存儲引擎在修改表的數據時只需要修改其內存拷貝, 再把該修改行為記錄到持久在硬盤上的事務日志中, 而不用每次都將修改的數據本身持久到磁盤。 事務日志采用的是追加的方式, 因此寫日志的操作是磁盤 上一小塊區域內的順序1/0, 而不像隨機1/0需要在磁盤的多個地方移動磁頭, 所以采用 事務日志的方式相對來說要快得多。 事務日志持久以后, 內存中被修改的數據在后臺可以慢慢地刷回到磁盤。 目前大多數存儲引擎都是這樣實現的, 我們通常稱之為預寫式日 志 (Write-Ahead Logging), 修改數據需要寫兩次磁盤。

????????如果數據的修改已經記錄到事務日志井持久化, 但數據本身還沒有寫回磁盤, 此時系統崩潰, 存儲引擎在重啟時能夠自動恢復這部分修改的數據。 具體的恢復方式則視存儲引擎而定。

1.3.4 MySQL中的事務

????????MySQL 提供了兩種事務型的存儲引擎: InnoDB 和NOB Cluster。另外還有一些第三方存儲引擎也支持事務, 比較知名的包括XtraDB 和PBXT。后面將詳細討論它們各自的一些特點。

自動提交(AUTOCOMMIT)

????????MySQL 默認采用自動提交(AUTO C OMMIT) 模式。也就是說, 如果不是顯式地開始一個事務, 則每個查詢都被當作一個事務執行提交操作。在當前連接中, 可以通過設置AUTOCOMMIT 變扯來啟用或者禁用自動提交模式:

在事務中混合使用存儲引擎

????????MySQL服務器層不管理事務, 事務是由下層的存儲引擎實現的。 所以在同一個事務中, 使用多種存儲引擎是不可靠的。

????????如果在事務中混合使用了事務型和非事務型的表(例如InnoDB和MyISAM表),在正常提交的情況下不會有什么問題。

????????但如果該事務需要回滾, 非事務型的表上的變更就無法撤銷,這會導致數據庫處千不一致的狀態,這種情況很難修復, 事務的最終結果將無法確定。 所以, 為每張表選擇合適的存儲引擎非常重要。

隱式和顯式鎖定

????????InnoDB采用的是兩階段鎖定協議(two-phase locking protocol)。 在事務執行過程中,隨時都可以執行鎖定, 鎖只有在執行COMMIT 或者ROLLBACK的時候才會釋放,并且所有的 鎖是在同一時刻被釋放。 前面描述的鎖定都是隱式鎖定,InnoDB會根據隔離級別在需要的時候自動加鎖。

1.4多版本并發控制

????????MySQL的大多數事務型存儲引擎實現的都不是簡單的行級鎖。 基于提升井發性能的考慮,它們一般都同時實現了多版本并發控制(MVCC)。

????????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.1 lnnoDB存儲引擎

InnoDB的數據存儲在表空間( tablespace)中,表空間是由InnoDB管理的一個黑盒子,由一系列的數據文件組成。

????????InnoDB采用MVCC來支持高井發, 并且實現了四個標準的隔離級別。其默認級別是REPEATABLE READ( 可重復讀),并且通過間隙鎖( next-key locking)策略防止幻讀的出現。間隙鎖使得InnoDB不僅僅鎖定查詢涉及的行,還會對索引中的間隙進行鎖定,以防止幻影行的插入。

????????InnoDB表是基于聚簇索引建立的,我們會在后面的章節詳細討論聚簇索引。InnoDB的索引結構和MySQL的其他存儲引擎有很大的不同,聚簇索引對主鍵查詢有很高的性能。不過它的二級索引(secondar y ind ex , 非主鍵索引) 中必須包含主鍵列,所以如果主鍵列很大的話,其他的所有索引都會很大。因此,若表上的索引較多的話,主鍵應當盡可能的小。InnoDB的存儲格式是平臺獨立的,也就是說可以將數據和索引文件從Intel平臺復制到PowerPC或者Sun SPARC平臺。

1.5.5選擇合適的引擎

????????除非萬不得已, 否則建議不要混合使用多種存儲引擎, 否則可能帶來一系列復雜的問題,以及一些潛在的bug和邊界問題。存儲引擎層和服務器層的交互已經比較復雜, 更不用說混合多個存儲引擎了。至少, 混合存儲對一致性備份和服務器參數配置都帶來了一些困難。

如果應用需要不同的存儲引擎, 請先考慮以下幾個因素。

事務

????????如果應用需要事務支持, 那么InnoDB(或者XtraDB)是目前最穩定并且經過驗證的選擇。如果不需要事務, 并且主要是SELECT和INSERT操作, 那么MyISAM 是不錯的選擇。一般日志型的應用比較符合這一特性。

備份

????????備份的需求也會影響存儲引擎的選擇。如果可以定期地關閉服務器來執行備份, 那么備份的因素可以忽略。反之,如果需要在線熱備份,那么選擇InnoDB就是基本的要求。

崩潰恢復

????????數據量比較大的時候,系統崩潰后如何快速地恢復是一個需要考慮的問題。相對而言,MylSAM崩潰后發生損壞的概率比InnoDB要高很多,而且恢復速度也要慢。因此,即使不需要事務支持,很多人也選擇InnoDB引擎,這是一個非常重要的因素。

特有的特性

????????最后,有些應用可能依賴一些存儲引擎所獨有的特性或者優化,比如很多應用依賴聚簇索引的優化。另外,MySQL中也只有MyISAM支持地理空間搜索。如果一個存儲引擎擁有一些關鍵的特性,同時卻又缺乏一些必要的特性,那么有時候不得不做折中的考慮,或者在架構設計上做一些取舍。某些存儲引擎無法直接支持的特性,有時候通過變通也可以滿足需求。

日志型應用

????????利用MySQL內置的復制方案將數據復制一份到備庫,然后在備庫上執行比較消耗時間和CPU的查詢。這樣主庫只用千高效的插入工作,而備庫上執行的查詢也無須擔心影響到日志的插入性能。當然也可以在系統負載較低的時候執行報表查詢操作,但應用在不斷變化,如果依賴這個策略可能以后會導致問題。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容