MYSQL鎖學習筆記

前言

MYSQL是在大小公司中使用率極高的開源的關係型數據庫,以其良好的易用性和在分佈式場景下的高性能而著稱,也是全部新手在數據庫入門時的產品首選。最近由於聽了公司的一位師兄關於MYSQL InnoDB鎖的講座,收穫不少,因此將MYSQL鎖相關的必備知識在此進行梳理。這些知識不只能夠幫助面試,也能夠在平常開發進行性能優化或死鎖問題排查時派上用場。固然,最重要的是,在對數據進行上鎖時,就可以梳理出相應的上鎖流程,從而避免真正走到故障時再去排查。html

本文主要包括mysql

  • MYSQL基礎架構
  • 語句執行順序
  • ACID原則
  • 事務分類
  • 事務隔離級別
  • 行鎖/表鎖/意向鎖

MYSQL基礎架構

mysql_logical_arch

MYSQL主要分爲客戶端和服務端,其中客戶端負責對服務端進行鏈接,服務端主要包含兩個部分,其中存儲引擎層(Storage Engines)決定數據在磁盤上具體的存儲形式,典型的存儲引擎包括InnoDb和MyISAM,而目前MYSQL甚至支持混合存儲引擎,便可能一張表一半存儲在InnoDb上,一半存儲在MyISAM。面試

除此之外的其它服務端組建則不關心數據用什麼形式存儲,主要負責執行具體的SQL語句,sql

  1. 連接池(Connections/Thread handling)組件負責管理客戶端和服務端創建的全部鏈接,
  2. 解析器(Parser)負責解析並校驗SQL語句
  3. 查詢緩存(Query Cache)負責對執行過的SQL語句結果進行緩存,當發現有相似的查詢請求命中緩存時,則會直接返回緩存中的查詢結果。可是,由於緩存的維護存在必定的開銷,好比數據更新時須要同時去更新緩存,所以有些線上環境的DB會將這個功能關閉
  4. 優化器(Optimizer)負責對解析後的SQL語句進行優化,如緩存數據優化,執行計劃優化。這個階段還會對用戶的權限進行校驗
  5. 元數據緩存(Table Metadata Cache)表單/DB等的元數據信息的緩存

這裏簡單比較一下InnoDBMyISAM這兩個存儲引擎。
InnoDB的特性以下:數據庫

  1. 支持事務及ACID
  2. 提供行鎖/表鎖
  3. MVCC能力

MyISAM的特性以下:segmentfault

  1. 非事務型引擎
  2. 支持全文檢索(目前最新的InnoDB也支持)
  3. 只提供表鎖

本文主要基於InnoDB對鎖的特性進行介紹。緩存

SQL語句執行順序

一個查詢請求在整個MYSQL服務端的鏈路以下:性能優化

  1. 在連接池處建立連接
  2. 前往查詢緩存(若開啓)判斷是否有類似的SQL的查詢結果能夠直接命中
  3. 經過解析器對SQL語句進行解析和校驗,併爲SQL生成sql_id
  4. 優化器對SQL語句進行優化,生成執行計劃
  5. 前往存儲引擎執行並獲取數據

那麼SQL語句在通過解析器和優化器時是什麼樣的一個鏈路呢?
一個標準的Select SQL語句包含如下幾個部分:服務器

select t1.column1 as column1, t2,column2 as column2... 
from TABLE t1, TABLE t2 ... 
WHERE condition1 
GROUP BY condition2 
HAVING condition3 
ORDER BY column1
LIMIT N

而這條語句的標準邏輯執行順序以下:架構

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. HAVING
  7. SELECT
  8. DISTINCT
  9. ORDER BY

這裏有一點須要注意,select語句是在group by和having以後執行,所以select中as出來的列名在group by和having中是不能夠引用的,可是order by中是能夠引用的。

可是真正的的執行順序和標準邏輯執行順序並不必定相同,由於優化器會對SQL的執行順序進行變動,從而儘量提升SQL的執行效率。好比:

select * from table1 t1 join table2 t2 on t1.id = t2.id where t1.count > 10 and t2.count > 100

標準的執行順序會先將表格t1和t2進行join操做,再對join後的結果針對where語句進行篩選。而優化器可能會變化一下執行順序,先根據where t1.count > 10 and t2.count > 100篩選出t1表和t2表中符合條件的數據,再執行join。

那麼有沒有辦法看到SQL在真實執行的時候的執行計劃呢?這就須要Explain語法。

Explain

Explain關鍵字的使用方法很簡單,只要將其加在具體的SELECT語句以前就能夠,Explain也只能解析SELECT語句。經過Explain關鍵字能夠觀察表的索引是否合理,語句的真實執行順序是否符合預期。Explain執行後生成的數據以下:

列名 含義
id SELECT語句的SQL_ID,它是指這個語句在查詢中的第n條語句,若是兩個id相同,則表明按照順序執行從上到下執行,id值越大,優先級越高,越先被執行
select_type SELECT語句類型, 如SIMPLE是指不使用UNION或子查詢
table 輸出行所屬的表格,derivex是指從第x步生成的衍生表
type 訪問類型,說明表是如何關聯的
possible keys 可選擇的索引
key 真正選擇的索引。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
key_len 選中索引的長度,顯示的是索引字段的最大可能長度,是根據表定義得來,而非表內檢索
ref 哪些列或常量被用來查找索引列上的值
rows 預估須要掃描的行數
filtered 預計多少比例的行數會被過濾出來

其中訪問類型(type)按照從好到壞包括

  • system:只有一行
  • const:表格中最多隻有一行匹配的數據,如使用主鍵進行查詢 如select * from user_info where id = 2
  • eq_ref: 使用惟一索引,對於每一個索引鍵值只有一條記錄匹配,如使用primary key或者unique key做爲多表連接的關聯條件,即前表的每個結果,在後表都只能找到一條匹配的記錄,只支持等號查詢。 SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id
  • ref: 針對非惟一或非主鍵索引,或是使用了最左前綴規則索引的查詢,支持非等號查詢。如 SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id AND order_info.user_id = 5
  • fulltext: 全文檢索
  • ref_or_null: 除了利用索引以外,MYSQL執行了額外的查詢來處理NULL值
  • index_merge: 對索引進行多段索引掃描,而且將結果進行合併
  • unique_subquery: 適用於IN語句,且IN中查詢出得數據惟一 如value IN (SELECT primary_key FROM single_table WHERE some_expr)
  • index_subquery: 同上,只是IN中查詢數據不惟一
  • range:獲取特定範圍內的數據,使用索引來決定哪些是這個範圍內的數據。全部的等值,非等值處理,判空等均可以使用range類型
  • index:相似於ALL,只不過全掃描的是索引樹。若是隻須要掃描索引樹,無需訪問具體表,則會在Extra列展現Using index。若是查詢中使用的索引是某個大索引的其中一部分時,也會使用這種檢索類型
  • ALL:全表掃描

在知道這些以後,使用Explain分析語句時能夠按照以下思路進行分析:

  1. 查看possible_keys和keys列,判斷是否充分利用了主鍵/惟一鍵/索引
  2. 查看key_len,判斷關鍵字長度是否過長
  3. 接着查看ref列,判斷是否可以往const優化
  4. 去除type=ALL的全表掃描鏈接

這裏建議看一下參考文章中的Explain實戰例子文章來加深經過Explain進行優化的思路

事務特性ACID

ATOMICY原子性:事務要麼所有執行,要麼所有不執行
CONSISTENCY一致性:事務執行前和執行後數據狀態應當一致
ISOLATION隔離性:事務之間不會相互影響
DURABILITY持久性:事務執行完成後結果不會丟失,所以須要可以對數據進行恢復

事務分類

隱式事務:在autocommit爲true的狀況下,默認每一條語句都會開啓一個事務執行,執行完畢後提交事務。所以不在事務上下文中執行select * from user where id = 1 for update語句在語句執行完後就會釋放排他鎖,這在大多數狀況下都是不合理的。
顯式事務:每一個事務以start transaction開啓,以commit或rollback結束。Spring中使用@Transactional或是transactionTemplate包圍的代碼段

事務隔離級別

事務總共有4個隔離級別:

  1. 讀未提交,會出現髒讀,不可重複度,幻讀,
  2. 讀已提交,會出現不可重複讀,幻讀
  3. 可重複度,InnoDB經過MVCC解決了幻讀問題,MVCC全稱Multiple Version Concurrency Control,其核心爲一個在t0時刻開啓的事務只能讀到t0時刻以及以前的提交的數據狀態
  4. 序列化

髒讀:一個事務中未提交的語句會被另外一個事務察覺
不可重複讀:一個事務中提交的update語句會被另外一個事務察覺
幻讀:一個事務中提交的insert語句會被另外一個事務察覺

鎖主要分爲表鎖和行鎖。顧名思義,表鎖就是指對整張表進行上鎖,而行鎖則是指針對一行數據進行上鎖。表鎖一般在服務器層面實現,而行鎖每每在存儲引擎層實現。行鎖並非只對數據行上鎖,還能夠對索引/索引區間進行上鎖,即強調的是粒度更小的鎖。

鎖的類型

鎖能夠分爲如下四類:

  1. 共享鎖(S)可重複獲取共享鎖,可是不能獲取排他鎖(select ... lock in share mode)
  2. 排他鎖(X)不能獲取數據行的任何鎖 (select ... for update, update, delete)
  3. 意向鎖(IS/IX)表級別鎖,當得到該表/行的共享/排他鎖時,會對該表加上意向共享/排他鎖。這樣別的表級別鎖來試圖鎖表時,能夠直接經過意向鎖來判斷該表中是否存在共享/排他鎖,而無需對錶中的每一行判斷是否有行級鎖,下降封鎖成本,提升併發性能

意向鎖和意向鎖之間是兼容的,而意向鎖和行鎖之間也是兼容。意向鎖主要是對錶鎖的優化。假如如今有一個事務須要對錶a加排他鎖,若是沒有意向鎖,就須要對全表進行掃描,直到找到第一個共享/排他鎖。而經過判斷是否有意向鎖,能夠極大的提升鎖互斥判斷的性能。加意向鎖是在全部鎖(行鎖/表鎖)以前進行判斷和執行的。

行鎖

行鎖具體有三種實現:

  1. record lock 記錄鎖:鎖定索引記錄自己
  2. gap lock:在索引記錄的間隙加鎖,鎖定範圍,不包括記錄自己
  3. next key:record lock + gap lock

只在可重複度REPEATABLE READ或以上的隔離級別下的特定操做纔會取得gap lock或nextkey lock。
讀已提交REPEATABLE COMMIT級別下只有record lock
MYSQL默認爲RR

所以當判斷語句如何加行鎖時,須要根據事務隔離級別+是否使用主鍵/惟一鍵/索引進行判斷。

加鎖順序本質上和索引的查詢順序是一致的
這裏有一種最糟糕的狀況,即若是where條件中的字段不是主鍵/索引/惟一索引,則會先對所有索引上排他鎖,在找到符合條件的記錄後,解鎖不知足條件的鎖。

參考文獻

MYSQL架構
Explain關鍵字
MYSQL性能優化神器Explain
Explain實戰例子
詳解 MySql InnoDB 中意向鎖的做用
幻讀

相關文章
相關標籤/搜索