大話MySQL鎖

1、鎖介紹

不一樣存儲引擎支持的鎖是不一樣的,好比MyISAM只有表鎖,而InnoDB既支持表鎖又支持行鎖。html

下圖展現了InnoDB不一樣鎖類型之間的關係:mysql

圖中的概念比較多很差理解,下面依次進行說明。算法

1.1樂觀鎖

​ 樂觀鎖是相對悲觀鎖而言的,樂觀鎖假設數據通常狀況下不會形成衝突,所在在數據進行提交更新時,纔會對數據的衝突與否進行檢測,若是發現衝突了,則返回給用戶錯誤信息,讓用戶決定如何處理,其核心是基於CAS算法。樂觀鎖適用於讀多寫少的場景,能夠提升程序吞吐量。sql

​ Mysql自帶的是沒有樂觀鎖的,可是能夠經過表上加個version字段來實現本身樂觀鎖。數據庫

假如要更新一個用戶的年齡,能夠這樣作:編程

  1. 查出用戶id等於3的用戶信息,select id,name,age,version from user where id = 3,獲得以下的數據。
id Name Age Version
3 張三 26 1
  1. 更新張三的年齡爲27,注意where條件帶上版本號。update user set age = 27,version = 2 where id = 3 and version = 1;併發

  2. 若是更新的結果是1則表示更新成功了,若是是0則表示更新失敗須要從新嘗試。高併發

1.2悲觀鎖

​ 悲傷鎖就是在每次操做數據時,都悲觀地認爲會出現數據衝突,因此必須先獲取到數據的鎖再對其修改。傳統的關係型數據庫用的就是悲觀鎖,還有JDK中的synchronized關鍵字等。悲觀鎖主要分爲共享鎖和排他鎖。性能

1.3共享鎖

共享鎖【shared locks】,又叫讀鎖,顧名思義,共享鎖就是多個事務對同一個數據能夠共享一把鎖,都能訪問到數據,可是隻能讀不能修改。rest

如何獲取共享鎖?

select * from user where id = 3 lock in share mode;

注意:在有事務獲取到了共享鎖以後,其餘事務是不能作insert/update/delete操做的,由於insert/update/delete語句會自動加上排他鎖。

1.4排他鎖

排他鎖【exclusive locks】,又叫寫鎖,顧名思義,排他鎖就是不能與其餘鎖並存,若是一個事務獲取了一個數據行的排他鎖,其餘事務就不能再獲取該行的其餘鎖,包括共享鎖和排他鎖,可是獲取排他鎖的事務是能夠對數據進行讀取和修改。

如何獲取排他鎖?

在sql語句後加上for update便可。

select * from user where id = 3 for update

1.5表鎖

表鎖,顧名思義就是對整張表加鎖,是Mysql各存儲引擎中最大粒度的鎖定機制。

優勢:實現邏輯簡單,獲取鎖和釋放鎖的速度很快,因爲每次都是將整張表鎖定因此能夠很好的避免死鎖問題。

缺點:鎖定顆粒度大致使出現鎖定資源爭用的機率高,併發度低。

1.6行鎖

行鎖,顧名思義就是對錶中的某行數據加鎖,鎖定顆粒度最小。

優勢:發生鎖衝突的機率低,併發處理能力強。

缺點:因爲鎖定資源的顆粒度很小,因此每次獲取鎖和釋放鎖須要作的事情也更多,帶來的消耗天然也就更大了。此外,行級鎖定也最容易發生死鎖。

如何判斷使用的是行鎖仍是表鎖?

InnoDB的行鎖是針對索引加的鎖,不是針對記錄加的鎖,因此只有在經過索引條件檢索數據時纔會用行鎖,不然使用表鎖。而且該索引不能失效,不然都會從行鎖升級爲表鎖。因此在使用select for update時,where 子句必定要帶上索引,不然極容易形成性能問題。

行鎖又細分三種實現算法:

  • record lock:專門對索引項加鎖;

  • gap lock:間隙鎖,是對索引之間的間隙加鎖;

  • Next-key lock:是前面兩種的組合,對索引及其之間的間隙加鎖;

1.7頁面鎖

頁面鎖出現比較少,它的特色是開銷和加鎖時間界於表鎖和行鎖之間,會出現死鎖,鎖定粒度界於表鎖和行鎖之間,併發度通常。

2、死鎖

2.1死鎖原理

​ 死鎖(Deadlock) 所謂死鎖:是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。因爲資源佔用是互斥的,當某個進程提出申請資源後,使得有關進程在無外力協助下,永遠分配不到必需的資源而沒法繼續運行,這就產生了一種特殊現象死鎖。

死鎖的四個必要條件:

  1. 互斥條件:一個資源每次只能被一個進程使用。

  2. 佔有且等待:一個進程因請求資源而阻塞時,對已得到的資源保持不放。

  3. 不可強行佔有:進程已得到的資源,在末使用完以前,不能強行剝奪。

  4. 循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。

2.2死鎖案例

案例一

首先建立一張訂單記錄表,用於作訂單的冪等性校驗防止重複生成訂單。

CREATE TABLE `order_record`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` int(11) DEFAULT NULL,
  `status` int(4) DEFAULT NULL,
  `create_date` datetime(0) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_order_status`(`order_no`,`status`) USING BTREE
) ENGINE = InnoDB
事務A 事務B
關閉自動提交事務,set autocommit = 0; set autocommit = 0;
select id from order_record where order_no = 4 for update;//檢查是否存在訂單號爲4的訂單
select id from order_record where order_no = 5 for update;//檢查是否存在訂單號爲5的訂單
//若是沒有則插入信息
insert into order_record(order_no,status,create_date) values(4,1,'2020-10-04 10:56:00');
此時鎖等待中...
//若是沒有則插入信息
insert into order_record(order_no,status,create_date) values(5,1,'2020-10-04 10:56:00');
返回結果代表發生死鎖,ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
COMMIT;(未完成) COMMIT;(未完成)

分析:

因爲order_no列爲非惟一索引,並且此時是RR事務隔離級別,因此SELECT 的加鎖類型是gap lock,並且gap範圍是(4,+∞)。

當咱們執行插入 SQL 時,會在插入間隙上再次獲取插入意向鎖。插入意向鎖其實也是一種 gap 鎖,它與 gap lock 是衝突的,事務 A 和事務 B 都持有間隙 (4,+∞)的 gap 鎖,而接下來的插入操做爲了獲取到插入意向鎖,都在等待對方事務的 gap 鎖釋放,因而就形成了循環等待,致使死鎖。

案例二

InnoDB 存儲引擎的主鍵索引爲聚簇索引,其它索引爲輔助索引。若是兩個更新事務使用了不一樣的輔助索引,或者一個使用輔助索引,一個使用了聚簇索引,就都有可能致使鎖資源的循環等待,形成死鎖。

步驟:

首先,order_record表存在如下數據。

mysql鎖

而後打開兩個窗口

事務A 事務B
BEGIN; BEGIN;
update order_record set status = 1 where order_no = 4;
mysql> update order_record set status = 1 where id = 4;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction//發生了死鎖

分析:

事務A 事務B
首先獲取idx_order_status輔助索引
獲取主鍵索引的行鎖
根據輔助索引獲取主鍵索引,再獲取主鍵索引的行鎖
更新status列時,須要idx_order_status輔助索引

因此再更新數據時,要儘可能根據主鍵來更新,能夠有效避免死鎖發生。

2、如何避免死鎖

一般有如下手段能夠預防死鎖的發生:

  1. 在編程中儘可能按照固定的順序來處理數據庫記錄,假設有兩個更新操做,分別更新兩條相同的記錄,但更新順序不同,有可能致使死鎖。
  2. 在容許幻讀和不可重複讀的狀況下,儘可能使用 RC 事務隔離級別,能夠避免 gap lock 致使的死鎖問題。
  3. 更新表時,儘可能使用主鍵更新。
  4. 避免長事務,儘可能將長事務拆解,能夠下降與其它事務發生衝突的機率;
  5. 設置鎖等待超時參數,咱們能夠經過 innodb_lock_wait_timeout 設置合理的等待超時閾值,特別是在一些高併發的業務中,咱們能夠儘可能將該值設置得小一些,避免大量事務等待,佔用系統資源,形成嚴重的性能開銷。

若是真的發生了數據庫死鎖,也有如下方式處理:

  1. 查看當前的事務

    SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
  2. 查看當前鎖定的事務

    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
  3. 查看當前等鎖的事務

    SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
  4. 殺死進程 kill pid

並且MySQL默認開啓了死鎖檢測機制,當檢測到死鎖後會選擇一個最小(鎖定資源最少的)的事務進行回滾。

3、總結

日常不多寫MySQL相關的文章,其實MySQL中的門道仍是挺多的,本文關於間隙鎖等概念講的比較簡單,推薦博客《mysql間隙鎖》。 之後可能會再寫一篇關於索引的,也有可能不會(主要是懶😄),若是本文哪裏有錯誤,請多指教。

相關文章
相關標籤/搜索