1. 程式人生 > >《高效能MySQL》讀書筆記---第一章:MySQL架構與歷史

《高效能MySQL》讀書筆記---第一章:MySQL架構與歷史

本章描述了MySQL的伺服器架構、各種儲存引擎之間的主要區別,以及這些區別的重要性

 

1.1 MySQL邏輯架構

MySQL的邏輯架構如下圖所示:

image.png

第一層:該層的服務並不是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(可序列化)

        該級別是最高的隔離級別。它通過強制事務穿行執行,避免了前面說的幻讀問題

        該級別會在讀取的每一行資料都加鎖,所以可能導致大量的超時和鎖爭用的問題

        只有在非常需要確保資料的一致性而且可以接受沒有併發的情況下,才考慮採用該級別

image.png


1.3.2 死鎖


        死鎖是指兩個或者多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性迴圈的現象。

死鎖的例子:

image.png

        死鎖的兩個解決方式:

            1.儲存引擎檢測到死鎖的迴圈依賴,並立即返回錯誤

            2.當查詢的時間達到鎖等待超時的設定後,放棄鎖請求,這種方式不太友好

        InnoDB的處理方法:將持有最少行級排他鎖的事務進行回滾

        

         死鎖產生的雙重原因:

             1.真正的資料衝突

             2.儲存引擎的實現方式

        死鎖發生後,只有部分或者完全回滾其中一個事務,才能打破死鎖(事務型的系統無法避免死鎖)


1.3.3 事務日誌

        事務日誌可以幫助提高事務的效率

        使用事務日誌,儲存引擎在修改表的資料時,只需要將修改行為記錄到硬碟上的事務日誌中,而不需要每次都將修改的資料本身持久化到磁碟。

        事務日誌持久化後,記憶體中被修改的資料在後臺可以慢慢同步到磁碟,成為預寫式日誌(Write-Ahead Logging),修改資料需要些兩次磁碟(修改行為和資料本身)

        若在資料同步的過程,發生了系統崩潰,儲存引擎在重啟時能夠自動回覆這部分修改的資料。具體恢復方式視儲存引擎而定


1.3.4 MySQL中的事務

        MySQL提供了兩種事務型的儲存引擎:InnoDB和NDB Cluster

        第三方引擎:XtraDB和PBXT,支援事務

自動提交(AUTOCOMMIT)

        MySQL預設採用自動提交模式。如果不是顯式開始一個事務,則每個查詢都被當做一個事務執行提交操作。

        啟用或者禁用的操作如下圖:

image.png

        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則會對所有讀取的行都加鎖