不要說你真的懂高性能MySQL

前言

最近做的項目對集羣數據庫性能要求較高,藉此機會打算重新學習、整理Mysql的原理及調優策略,參考梳理《高性能MySQL》第三版、MySQL官方文檔、網絡上前人寫的資料、以及個人實戰經驗總結。在接下來的一段時間內持續更新,希望在提升自己的同時能幫到同在數據庫調優的苦海中掙扎的程序猿們。(取這個標題是因爲筆者屢次面試都鼓吹自己懂高性能數據庫,不想打臉)

1 MySQL的邏輯架構

先上一幅MySQL服務器邏輯架構圖

    如上圖所示,MySQL的邏輯架構大致分三層:

第一層 (通信層):並非MySQL所獨有,諸如:連接處理、授權認證、安全等功能均在這一層處理。

第二層(服務層):MySQL大多數核心服務均在這一層,包括查詢解析、分析、優化、緩存、內置函數(比如:時間、數學、加密等函數)。所有的跨存儲引擎的功能也在這一層實現:存儲過程、觸發器、視圖等。

第三層(引擎層):最下層爲存儲引擎,其負責MySQL中的數據存儲和提取。每種存儲引擎都有其優勢和劣勢。中間的服務層通過API與存儲引擎通信,這些API接口屏蔽了不同存儲引擎間的差異。注意:存儲引擎不會去解析SQL,不同存儲引擎自檢不會相互通信,只會單純的響應上層的請求。

(更加詳盡的流程說明請移步此博客:https://blog.csdn.net/z_ryan/article/details/82260663

1.1 連接管理與安全

每個連接會在服務器進程中擁有一個連接,這個連接的查詢只會在這個單獨的線程中執行。服務器會負責緩存連接(MySQL5.5及之後的版本提供了一個API,支持線程池)。一旦客戶端連接成功,服務及會繼續驗證該客戶端是否具有執行某個特定查詢的權限。

1.2 優化與執行

MySQL會解析sql語句,並創建內部數據結構(解析樹),染回對其進行優化(包括重寫查詢、決定表的讀取順序、選擇合適的引擎等)。用戶可以用關鍵字hint(提示)和explain(解釋)分別提示優化器的決策和解釋優化過程中各個因素。

對於select語句,在解析查詢之前。服務器會先檢查查詢緩存(Query Cache),如果能夠在其中找到對應的查詢,直接返回查詢緩存中的結果集(後繼更新的博客會詳細介紹)。

2 併發控制

多個數據在需要再統一時刻修改數據,都會產生併發控制的問題。

可以通過一個有兩種類型的鎖組成的鎖系統來解決問題。這兩種類型的鎖通常被稱爲共享鎖和排他鎖,也叫讀鎖和寫鎖。

鎖粒度越小就能支持越多的併發。但是加鎖需要消耗資源,所謂鎖策略就是在鎖的開銷和數據的併發性能之間尋求平衡。而MySQL提供了多種原則,每種存儲引擎都可以實現自己的鎖策略和鎖粒度。並且MySQL支持多個存儲引擎的架構,所以不需要單一的通用解決方案。

兩種最重要的鎖策略:

表鎖(table lock):開銷最小,性能最差。

行級鎖(row lock):開銷最大,最大程度的支持併發處理。

3 事務

事務就是一組原子性的SQL查詢,或者說是一個獨立的工作單元。

3.1 ACID

原子性(Atomicity):一個事物必須被視爲一個不可分割的最小單元,要麼全部執行成功提交,要麼全部執行失敗回滾。(銀行轉賬中途失敗,不會扣錢)

一致性(Consistency):數據庫總是從一個一致的狀態轉換到另一個一致的狀態。(銀行轉賬不管成功失敗,雙方總金額一致)

隔離性(Isolation):一個事物所做的修改在最終提交之前,對其他事物不可見。(銀行轉賬成功之前,其他程序查詢雙方的餘額並沒有改變)

持久性(Durability):一旦事物提交,所做的修改永久保存到數據庫中。(不用解釋了吧)

注意:像鎖粒度的升級會增加系統開銷一樣,ACID安全性的實現程度越高,數據庫系統做的額外工作也越多。

3.2 隔離級別

SQL標準中定義了四種隔離級別:

READ UNCOMMITTED(未提交讀):事務中的修改,即時沒有提交,對其他事物也是可見的。會出現髒讀可能性。

READ COMMITTED(提交讀):一個事物從開始知道提交之前,所做的修改對其他事物是不可見的。會出現不可重複讀可能性。

REPEATABLE READ(可重複讀):保證同一事物中多次讀取同樣記錄的結果是一致的(通過保存快照實現)。會出現幻讀可能性。

SERIALIZABLE(可串行讀):最高隔離級別,會在讀取的每行數據上都加鎖。

大多數數據庫的默認隔離級別都是READ COMMITTED(提交讀),但是MySQL的默認隔離級別是REPEATABLE READ(可重複讀)。

3.3 事務日誌

使用事務日誌是爲了提高事務的效率。存儲引擎在修改表的數據時只需要修改其內存拷貝,再把該修改行爲記錄到持久在硬盤上的事務日誌中,而不是將修改的數據本身持久到磁盤。事務日誌採用的是追加的方式,因此寫日誌的操作是磁盤上一小塊區域中順序I/O,而不像隨機I/O需要再磁盤的多個地方移動磁頭,所以採用事務日誌的方式相對要快很多。

事務日誌持久以後,內存中被修改的數據在後臺可以慢慢的刷回到磁盤。目前大多數存儲引擎都是這樣實現的,我們通常稱之爲預寫式日誌,修改日誌需要寫兩次磁盤。如果數據的修改已經激勵到事務日誌持久化,但數據本身還沒有寫回磁盤,此時系統崩潰,存儲引擎在啓動時能夠自動恢復這部分修改的數據。

3.4 MySQL中的事務

MySQL提供了兩種事務型的存儲引擎:InnoDB和NDB Cluster。另外還有一些第三方存儲引擎也支持事務,如XtraDB和PBXT。

3.4.1 自動提交

MySQL默認採用自動提交(AUTOCOMMIT)模式。可以通過設置 AUTOCOMMIT 變量來啓用或者禁用自動提交模式:

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)

mysql> set autocommit = 1;
Query OK, 0 rows affected (0.00 sec)

1或者ON表示啓用,0或者OFF表示禁用。當自動提交禁用時,所有查詢都會在一個事務中,指導顯式的執行commit提交或者rollback回滾,該事務結束,同時又開始了另一個新事物。修改autocommit對非事務型的表(比如MyISAM或者內存表)不會有任何影響。

還有一些命令,會在執行之前強制執行commit提交。經典的例子是ALTER TABLE(DDL)和LOCK TABLES。

MySQL可以通過 SET TRANSACTION ISOLATION LEVEL 命令來設置隔離級別。也可以自改變當前會話的隔離級別:

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

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

MySQL服務器層不管理事務,事務是由下層的存儲引擎實現的。如果在事務中混合使用了事務型和非事務型的表,如果正常提交就沒有什麼問題,但是如果需要回滾,非事務型的表上的變更將無法撤銷。

3.4.3 顯式鎖定和隱式鎖定

InnoDB採用的是兩階段鎖定協議(two-phase locking protocol)。

隱式鎖定:事務執行過程中,隨時都可以執行鎖定,鎖只有在commit或者rollback的時候纔會釋放。InnoDB會根據隔離級別在需要的時候自動加鎖

顯式鎖定:特定的語句進行顯式鎖定:

SELECT ... LOCK IN SHARE MODE --PS:不屬於SQL規範
SELECT ... FOR UPDATE --PS:不屬於SQL規範
LOCK TABLES ...
UNLOCK TABLES ...

4 多版本併發控制(MVCC)

MySQL的大多數事務型存儲引擎實現的都不是簡單的行級鎖。基於提升併發性能的考量,他們一般都同時實現了多版本併發控制(避免了很多加鎖操作,因此開銷很低,同時也實現了非阻塞的操作,寫操作也只鎖定必要的行)。

InnoDB的MVCC,是通過在每行記錄後面保存兩個隱藏的列來實現的。這兩個列一個保存了行的創建時間,一個保存了行的過期時間(這裏的時間並不是實際的時間值,而是版本號)。沒開始一個新事物,系統版本號會自動遞增。事務開始時刻的系統版本號會作爲事務的版本號,用來和查詢到的版本號進行比較。

以REPEATABLE READ 隔離級別下的MVCC的具體操作爲例:

SELECT

  InnoDB檢查每行,要確定它符合兩個標準。

  InnoDB必須知道行的版本號,這個行的版本號至少要和事物版本號一樣的老。(也就是是說它的版本號可能少於或者和事物版         本號相同)。這個既能確定事物開始之前行是存在的,也能確定事物創建或修改了這行。

       行的刪除操作的版本一定是未定義的或者大於事物的版本號。確定了事物開始之前,行沒有被刪除。

  符合了以上兩點。會返回查詢結果。

INSERT

  InnoDB記錄了當前新增行的系統版本號。

DELETE

  InnoDB記錄的刪除行的系統版本號作爲行的刪除ID。

UPDATE

  InnoDB複製了一行。這個新行的版本號使用了系統版本號。它也把系統版本號作爲了刪除行的版本。

5 MySQL的存儲引擎

在文件系統中,MySQL將每個數據庫(schema)保存爲數據目錄下的一個子目錄,創建表時,MySQL會在數據庫子目錄下創建一個和表同名的.frm文件保存表的定義。

可以使用 SHOW TABLE STAUS 命令顯示錶的信息:

mysql> show table status like 'device'\G;    --小技巧:加上\G更方便看
*************************** 1. row ***************************
           Name: device                      --表名
         Engine: InnoDB                      --存儲引擎
        Version: 10                          --什麼版本?對不起,不認識
     Row_format: Dynamic                     --行的格式:Dynamic(行長度可變)、Fixed
           Rows: 36                          --目前有多少行
 Avg_row_length: 455                         --平均每行字節數
    Data_length: 16384                       --表數據大小(字節)
Max_data_length: 0                           --表數據的最大容量(0是無限吧)
   Index_length: 16384                       --索引大小(字節)
      Data_free: 0                           --已分配但是未使用的空間
 Auto_increment: 37                          --下一個自增序列的值(AUTO_INCREMENT)
    Create_time: 2018-08-16 17:17:02         --創建時間
    Update_time: NULL                        --最新修改時間
     Check_time: NULL                        --檢查時間(CHECK TABLE 命令)
      Collation: utf8_bin                    --默認字符集_字符列排序規則
       Checksum: NULL                        --如果啓用,保存的是整個表的實時校驗和
 Create_options:                             --其他指定選項
        Comment: 設備表              
1 row in set (0.00 sec)

5.1 InnoDB 存儲引擎

1)InnoDB是MySQL的默認事務型引擎,也是最重要的、使用最廣泛的引擎。InnoDB的性能和自動崩潰恢復特性,使得它在非事務型存儲的需求中也很有用。

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

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

4)InnoDB表是基於聚簇索引建立的。(後面的更新會詳細討論聚簇索引)

5)InnoDB內部做了很多優化,包括從磁盤讀取數據時採用的可預測性預讀。

5.2 MyISAM 存儲引擎

在MySQL5.1及之前的版本,MySQL是默認的存儲引擎。MyISAM不支持事務和行級鎖(MyISAM對整張表加鎖,而不針對行),缺陷是崩潰後無法安全恢復。MyISAM引擎設計簡單,數據以緊密格式存儲,所以在某些場景下的性能很好。MyISAM有一些服務器級別的性能擴展限制,比如對索引鍵緩衝區(key cache)的Mutex鎖,MariaDB基於段的索引鍵緩衝區機制來避免該問題。但MyISAM最經典的性能問題還是表鎖的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章