MySQL 核心三劍客 —— 索引、鎖、事務

1、常見存儲引擎

1.1 InnoDB

InnoDB 是 MySQL 5.5 以後默認的存儲引擎,它具備高可靠、高性能的特色,主要具有如下優點:html

  • DML 操做徹底遵循 ACID 模型,支持事務,支持崩潰恢復,可以極大地保護用戶的數據安全;
  • 支持多版本併發控制,它會保存數據的舊版本信息,從而能夠支持併發和事務的回滾;
  • 支持行級鎖,支持相似 Oracle 的一致性讀的特性,從而能夠承受高併發地訪問;
  • InnoDB 組織數據時默認按照主鍵進行聚簇,從而能夠提升主鍵查找的效率。對於頻繁訪問的數據,InnoDB 還會爲其創建哈希索引,從而提升等值查詢的效率,這也稱爲自適應哈希索引;
  • InnoDB 基於磁盤進行存儲,全部存儲記錄按 的方式進行管理。爲彌補 CPU 速度與磁盤速度之間的鴻溝,InnoDB 引用緩存池 (Buffer Pool) 來提升數據的總體性能。查詢時,會將目標頁讀入緩存中;修改時,會先修改緩衝池中的頁,而後再遵循 CheckPoint 機制將頁刷回磁盤。全部緩存頁經過最近最少使用原則 ( LRU ) 來進行按期清理。
  • InnoDB 支持兩次寫 (DoubleWrite) ,從而能夠保證數據的安全,提升系統的可靠性。

一個 InnoDB 引擎完整的內存結構和磁盤結構以下圖所示:mysql

1.2 MyISAM

MyISAM 是 MySQL 5.5 以前默認的存儲引擎。建立 MyISAM 表時會建立兩個同名的文件:git

  • 擴展名爲 .MYDMYData):用於存儲表數據;
  • 擴展名爲 .MYIMYIndex): 用於存儲表的索引信息。

在 MySQL 8.0 以後,只會建立上述兩個同名文件,由於 8.0 後表結構的定義存儲在 MySQL 數據字典中,但在 MySQL 8.0 以前,還會存在一個擴展名爲 .frm 的文件,用於存儲表結構信息。MyISAM 與 InnoDB 主要的區別其只支持表級鎖,不支持行級鎖,不支持事務,不支持自動崩潰恢復,但可使用內置的 mysqlcheck 和 myisamchk 工具來進行檢查和修復。github

1.3 MEMORY

MEMORY 存儲引擎(又稱爲 HEAP 存儲引擎)一般用於將表中的數據存儲在內存中,它具備如下特徵:算法

  • MEMORY 表的表定義信息存儲在 MySQL 數據字典中,而實際的數據則存儲在內存空間中,並以塊爲單位進行劃分;所以當服務器重啓後,表自己並不會被刪除,只是表中的全部數據都會丟失。
  • MEMORY 存儲引擎支持 HASH 索引和 BTREE 索引,默認採用 HASH 索引。
  • MEMORY 表使用固定長度的行存儲格式,即使是 VARCHAR 類型也會使用固定長度進行存儲。
  • MEMORY 支持 AUTO_INCREMENT 列,但不支持 BLOB 列或 TEXT 列。
  • MEMORY 表和 MySQL 內部臨時表的區別在於:二者默認都採用內存進行存儲,但 MEMORY 表不受存儲轉換的影響,而內部臨時表則會在達到閾值時自動轉換爲磁盤存儲。

基於以上特性,MEMORY 表主要適合於存儲臨時數據 ,如會話狀態、實時位置等信息。sql

1.4 CSV

CSV 存儲引擎使用逗號分隔值的格式將數據存儲在文本文件中。建立 CSV 表時會同時建立兩個同名的文件:數據庫

  • 一個擴展名爲 csv ,負責存儲表的數據,其文件格式爲純文本,能夠經過電子表格應用程序 (如 Microsoft Excel ) 進行修改,對應的修改操做也會直接反應在數據庫表中。
  • 另外一個擴展名爲 CSM,負責存儲表的狀態和表中存在的行數。

1.5 ARCHIVE

ARCHIVE 存儲引擎默認採用 zlib 無損數據壓縮算法進行數據壓縮,可以利用極小的空間存儲大量的數據。建立ARCHIVE 表時,存儲引擎會建立與表同名的 ARZ 文件,用於存儲數據。它還具備如下特色:緩存

  • ARCHIVE 引擎支持 INSERT,REPLACE 和 SELECT,但不支持 DELETE 或 UPDATE。
  • ARCHIVE 引擎支持 AUTO_INCREMENT 屬性,並支持在其對應的列上創建索引,若是嘗試在不具備 AUTO_INCREMENT 屬性的列上創建索引,則會拋出異常。
  • ARCHIVE 引擎不支持分區操做。

1.6 MEGRE

MERGE 存儲引擎,也稱爲 MRG_MyISAM 引擎,是一組相同 MyISAM 表的集合。 」相同」 表示全部表必須具備相同的列數據類型和索引信息。能夠經過 UNION = (list-of-tables) 選項來建立 MERGE 表,以下:安全

mysql> CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;
mysql> CREATE TABLE t2 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;
mysql> INSERT INTO t1 (message) VALUES ('Testing'),('table'),('t1');
mysql> INSERT INTO t2 (message) VALUES ('Testing'),('table'),('t2');
mysql> CREATE TABLE total (a INT NOT NULL AUTO_INCREMENT,message CHAR(20), INDEX(a))
       ENGINE=MERGE UNION=(t1,t2) INSERT_METHOD=LAST;
複製代碼

建立表時能夠經過 INSERT_METHOD 選項來控制 MERGE 表的插入:使用 FIRSTLAST 分別表示在第一個或最後一個基礎表中進行插入;若是未指定 INSERT_METHOD 或者設置值爲 NO ,則表示不容許在 MERGE 表上執行插入操做。MERGE 表支持 SELECT,DELETE,UPDATE 和 DELETE 語句,示例以下:服務器

mysql>  SELECT * FROM total;
+---+---------+
| a | message |
+---+---------+
| 1 | Testing |
| 2 | table   |
| 3 | t1      |
| 1 | Testing |
| 2 | table   |
| 3 | t2      |
+---+---------+
複製代碼

2、索引

2.1 B+ tree 數據結構

若是沒有特殊說明,一般大多數數據庫採用的索引都是 B+ tree 索引,它是基於 B+ tree 這種數據結構構建的。爲何採用 B+ tree 而不是平衡二叉樹 (AVL) 或紅黑樹等數據結構?這裏假設索引爲 1-16 的自增數據,各種數據結構的表現以下:

平衡二叉樹數據結構

紅黑樹數據結構

Btree 數據結構

B+ Tree 數據結構

以上圖片均經過數據結構可視化網站 Data Structure Visualizations 自動生成,感興趣的小夥伴也可自行嘗試。

從上面的圖示中咱們能夠看出 B+ Tree 樹具備如下優勢:

  • B+ Tree 樹的全部非葉子節點 (如 003,007),都會在葉子節點冗餘一份,全部葉子節點都按照鏈表的方式進行組織,這樣帶來的好處是在範圍查詢中,只須要經過遍歷葉子節點就能夠獲取到全部的索引信息。
  • B+ Tree 的全部非葉子節點均可以存儲多個數據值,這取決於節點的大小,在 MySQL 中每一個節點的大小爲 16K ,所以其具有更大的出度,即在相同的數據量下,其樹的高度更低。
  • 全部非葉子節點都只存儲索引值,不存儲實際的數據,只有葉子節點纔會存儲指針信息或數據信息。按照每一個節點爲 16K 的大小計算,對於千萬級別的數據,其樹的高度一般都在 3~6 左右 (取決於索引值的字節數),所以其查詢性能很是優異。
  • 葉子節點存儲的數據取決於不一樣數據庫的實現,對於 MySQL 來講,取決於使用的存儲引擎和是不是主鍵索引。

2.2 B+ tree 索引

對於 InnoDB ,由於主鍵索引是彙集索引,因此其葉子節點存儲的就是實際的數據。而非主鍵索引存儲的則是主鍵的值 :

對於 MyISAM,由於主鍵索引是非彙集索引,因此其葉子節點存儲的只是指向數據位置的指針:

綜上所述,B+ tree 結構廣泛適用於範圍查找,優化排序和分組等操做。B+ tree 是基於字典序進行構建的,所以其適用於如下查詢:

  • 全值匹配:以索引爲條件進行精確查找。如 emp_no 字段爲索引,查詢條件爲 emp_no = 10008
  • 前綴匹配:以聯合索引的前綴爲查找條件。如 emp_nodept_no 爲聯合索引,查找條件爲 emp_no = 10008
  • 列前綴匹配:匹配索引列的值的開頭部分。如 dept_no 爲索引,查詢條件爲 dept_no like "d1%"。前綴匹配和列前綴匹配都是索引前綴性的體現,在某些時候也稱爲前綴索引。
  • 匹配範圍值:按照索引列匹配必定範圍內的值。如 emp_no 字段爲索引,查詢條件爲 emp_no > 10008
  • 只訪問索引的查詢:如 emp_no 字段爲索引,查詢語句爲 select emp_no from employees,此時 emp_no 索引被稱爲本次查詢的覆蓋索引,即只須要從索引上就能夠獲取所有的查詢信息,而沒必要訪問實際的表中的數據。
  • 精確匹配某一列並範圍匹配某一列:如 emp_nodept_no 爲聯合索引,查找條件爲 dept_no = "d004" and emp_no < 10020,這種狀況下索引順序必須爲 ( emp_no,dept_no ),這樣才能基於 emp_no 的字典序排序進行範圍查找。

2.3 哈希索引

使用哈希索引時,存儲引擎會對索引列的值進行哈希運算,並將計算出的哈希值和指向該行數據的指針存儲在索引中,所以它更適用於等值比較查詢,而不是範圍查詢,一樣也不能用於優化排序和分組等操做。在創建哈希索引時,須要選取選擇性比較高的列,即列上的數據不容易重複 (如身份證號),這樣能夠儘可能避免哈希衝突。由於哈希索引並不須要存儲索引列的數據,因此其結構比較緊湊,對應的查詢速度也比較快。

InnoDB 引擎有一個名爲 「自適應哈希索引 (adaptive hash index)」 的功能,當某些索引值被頻繁使用時,它會在內存中基於 B+ tree 索引再建立一個哈希索引,從而讓 B-Tree 索引具有哈希索引快速查找的優勢。

2.4 索引的優勢

  • 索引極大減小了服務器須要掃描的數據量;
  • 索引能夠幫助服務器避免排序和臨時表;
  • 索引能夠將隨機 IO 轉換爲順序 IO。

2.5 使用策略

  • 在查詢時,應該避免在索引列上使用函數或者表達式。
  • 對於多列索引,應該按照使用頻率由高到低的順序創建聯合索引。
  • 儘可能避免建立冗餘的索引。如存在索引 (A,B),接着又建立了索引 A,由於索引 A 是索引 (A,B) 的前綴索引,從而出現冗餘。
  • 創建索引時,應該考慮查詢時候的排序和分組的需求。只有當索引列的順序和 ORDER BY 字句的順序徹底一致,而且遵循一樣的升序或降序規則時候,MySQL 纔會使用索引來對結果作排序。

3、鎖

3.1 共享鎖與排它鎖

InnoDB 存儲引擎支持如下兩種標準的行級鎖:

  • 共享鎖 (S Lock,又稱讀鎖) :容許加鎖事務讀取數據;
  • 排它鎖 (X Lock,又稱寫鎖) :容許加鎖事務刪除或者修改數據。

排它鎖和共享鎖的兼容狀況以下:

X X
X 不兼容 不兼容
S 不兼容 兼容

3.2 意向共享鎖與意向排它鎖

爲了說明意向鎖的做用,這裏先引入一個案例:假設事務 A 利用 S 鎖鎖住了表中的某一行,讓其只能讀不能寫。以後事務 B 嘗試申請整個表的寫鎖,若是事務 B 申請成功,那麼理論上它就應該能修改表中的任意一行,這與事務 A 持有的行鎖是衝突的。想要解決這個問題,數據庫必須知道表中某一行已經被鎖定,從而在事務 B 嘗試申請整個表的寫鎖時阻塞它。想要知道表中某一行被鎖定,能夠對錶的每一行進行遍歷,這種方式可行可是性能比較差,因此 InnoDB 引入了意向鎖。

  • 意向共享鎖 (IS Lock) :當前表中某行或者某幾行數據存在共享鎖;
  • 意向排它鎖 (LX Lock) :當前表中某行或者某幾行數據存在排它鎖。

按照意向鎖的規則,當上面的事務 A 給表中的某一行加 S 鎖時,會同時給表加上 IS 鎖,以後事務 B 嘗試獲取表的 X 鎖時,因爲 X 鎖與 IS 鎖並不兼容,因此事務 B 會被阻塞。

X IX S IS
X 不兼容 不兼容 不兼容 不兼容
IX 不兼容 兼容 不兼容 兼容
S 不兼容 不兼容 兼容 兼容
IS 不兼容 兼容 兼容 兼容

3.3 一致性讀

1. 一致性非鎖定讀

一致非鎖定讀 (consistent nonlocking read) 是指在 InnoDB 存儲引擎下,若是將要讀取的行正在執行 DELETE 或 UPDATE 操做,此時沒必要去等待行上鎖的釋放,而是去讀取 undo 日誌上該行的快照數據,具體以下:

  • 在 READ COMMITTED 事務隔離級別下,讀取被鎖定行的最新一份快照數據;
  • 在 REPEATABLE READ 事務隔離級別下,讀取事務開始時所處版本的數據。

基於多版本併發控制和一致性非鎖定讀,能夠避免獲取鎖的等待,從而提升併發訪問下的性能。

2. 一致性鎖定度

一致性鎖定讀則容許用戶按照本身的需求在進行 SELECT 操做時手動加鎖,一般有如下兩種方式:

  • SELECT ... FOR SHARE:在讀取行上加 S 鎖;
  • SELECT ... FOR UPDATE:在讀取行上加 X 鎖。

3.4 鎖的算法

InnoDB 存儲引擎支持如下三種鎖的算法:

Record Lock:行鎖,用於鎖定單個行記錄。示例以下:

-- 利用行鎖能夠防止其餘事務更新或刪除該行
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
複製代碼

Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄自己,主要用於解決幻讀問題,示例以下:

-- 利用間隙鎖能夠阻止其餘事務將值15插入列 t.c1
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
複製代碼

Next-Key Lock:等價於 行鎖+間隙鎖,既鎖定範圍,也鎖定記錄自己。能夠用於解決幻讀中的 」當前讀「 的問題。

4、事務

4.1 ACID 定義

InnoDB 存儲引擎徹底支持 ACID 模型:

1. 原子性(Atomicity)

事務是不可分割的最小工做單元,事務的全部操做要麼所有提交成功,要麼所有失敗回滾,不存在部分紅功的狀況。

2. 一致性(Consistency)

數據庫在事務執行先後都保持一致性狀態,數據庫的完整性沒有被破壞。

3. 隔離性(Isolation)

容許多個併發事務同時對數據進行操做,但一個事務所作的修改在最終提交之前,對其它事務是不可見的。

4. 持久性(Durability)

一旦事務提交,則其所作的修改將會永遠保存到數據庫中。即便宕機等故障,也不會丟失。

4.2 事務的實現

數據庫隔離性由上一部分介紹的鎖來實現,而原子性、一致性、持久性都由 undo log 和 redo log 來實現。

  • undo log:存儲在 undo 表空間或全局臨時表空間的 undo 日誌段 (segment) 上,用於記錄數據修改前的狀態,主要用於幫助事務回滾以及實現 MVCC 功能 (如一致性非鎖定讀)。
  • redo log:負責記錄數據修改後的值,主要用於保證事務的持久化。

4.3 併發問題

在併發環境下,數據的更改一般會產生下面四種問題:

1.丟失更新

一個事務的更新操做被另一個事務的更新操做鎖覆蓋,從而致使數據不一致:

2. 髒讀

在不一樣的事務下,一個事務讀取到其餘事務未提交的數據:

3. 不可重複讀

在同一個事務的兩次讀取之間,因爲其餘事務對數據進行了修改,致使對同一條數據兩次讀到的結果不一致:

4.幻讀

在同一個事務的兩次讀取之間,因爲其餘事務對數據進行了修改,致使第二次讀取到第一次不存在數據,或第一次本來存在的數據,第二次卻讀取不到,就好像以前的讀取是 「幻覺」 同樣:

4.4 隔離級別

想要解決以上問題,能夠經過設置隔離級別來實現:InnoDB 支持如下四個等級的隔離級別,默認隔離級別爲可重複讀:

  • 讀未提交:在此級別下,一個事務中的修改,即使沒有提交,對其餘事務也是可見的。
  • 讀已提交:在此級別下,一個事務中的修改只有已經提交的狀況下,對其餘事務纔是可見的。
  • 可重複讀:保證在同一個事務中屢次讀取一樣數據的結果是同樣的。
  • 串行化:全部事務強制串行執行,因爲已經不存在並行,因此上述全部併發問題都不會出現。

在每一個級別下,併發問題是否可能出現的狀況以下:

隔離級別 髒讀 不可重複讀 幻讀
讀未提交(READ UNCOMMITTED) 可能出現 可能 可能
讀已提交(READ COMMITTED) 不可能出現 可能 可能
可重複讀(REPEATABLE READ) 不可能 不可能 可能
串行化(SERIALIZABLE) 不可能 不可能 不可能

就數據庫層面而言,當前任何隔離級別下都不會發生丟失更新的問題,以 InnoDB 存儲引擎爲例,若是你想要更改表中某行數據,該行數據上必然會加上 X 鎖,而對應的表上則會加上 IX 鎖,其餘任何事務必須等待獲取該鎖才能進行修改操做。

5、數據庫設計範式

數據庫設計當中經常使用的三範式以下:

第一範式:屬性不可分

要求表中的每一列都是不可再細分的原子項。這是最低的範式要求,一般都可以被知足。

第二範式:屬性徹底依賴於主鍵

要求非主鍵列必須徹底依賴於主鍵列,而不能存在部分依賴。示例以下:

mechanism_id (組織機構代碼) employee_id (僱員編號) ename (僱員名稱) mname (機構名稱)
28193182 10001 heibaiying XXXX公司

以上是一張全市在職人員統計表,主鍵爲:機構編碼 + 僱員編號。表中的僱員名稱徹底依賴於此聯合主鍵,但機構名稱卻只依賴於機構編碼,這就是部分依賴,所以違背了第二範式。此時經常使用的解決方式是創建一張組織機構與組織名稱的字典表。

第三範式:避免傳遞依賴

非主鍵列不能依賴於其餘非主鍵列,若是其餘非主鍵列又依賴於主鍵列,此時就出現了傳遞依賴。示例以下:

employee_id (僱員編號) ename (僱員名稱) dept_no (部門編號) dname(部門名稱)
10001 heibaiying 06 開發部

以上是一張僱員表,僱員名稱和所屬的部門編號都依賴於主鍵 employee_id ,但部門名稱卻依賴於部門編號,此時就出現了非主鍵列依賴於其餘非主鍵列,這就違背的第三範式。此時經常使用的解決方式是創建一張部門表用於維護部門相關的信息。

反範式設計

從上面的例子中咱們也能夠看出,想要徹底遵循三範式設計,可能須要額外增長不少表來進行維護。因此在平常開發中,基於其餘因素的綜合考量,可能並不會徹底遵循範式設計,甚至可能違反範式設計,這就是反範式設計。

參考資料

  1. 官方文檔:The InnoDB Storage EngineOptimization and IndexesInnoDB Locking and Transaction Model
  2. 姜承堯 . MySQL技術內幕:InnoDB存儲引擎(第2版) . MySQL技術內幕 . 2013-05
  3. 施瓦茨 (Baron Schwartz) / 扎伊採夫 (Peter Zaitsev) / 特卡琴科 (Vadim Tkachenko) . 高性能mysql(第3版) . 電子工業出版社 . 2013-05-01
  4. InnoDB 數據頁解析
  5. MySQL索引背後的數據結構及算法原理
  6. MYSQL-B+TREE索引原理
  7. Innodb中的事務隔離級別和鎖的關係

更多文章,歡迎訪問個人 GitHub 倉庫:Full-Stack-Notes

相關文章
相關標籤/搜索