MySQL鎖系列之鎖的種類和概念

在mysql當中,關於innodb的鎖類型總共能夠分爲四種,包含了行鎖和表鎖,分別是mysql

  • 基本鎖 - [ 共享鎖(Shared Locks:S鎖)和排它鎖(Exclusive Locks:X鎖)]
  • 意向鎖 - [ intention lock,分爲意向共享鎖(IS鎖)和意向排他鎖(IX鎖)]
  • 行鎖 - [ record Locks、gap locks、next-key locks、Insert Intention Locks ]
  • 自增鎖 - [ auto-inc locks ]

下面是各類鎖之間的對應兼容狀況(ps:在某一篇博客上看到,忘了是哪一篇,以爲好就截下來了嘻嘻):算法

InnoDB三種行鎖的算法:sql

  • Record Lock:單個行記錄上的鎖,只鎖定記錄自己
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄自己。 目的是爲了防止同一個事物的兩次當前讀,出現幻讀的狀況
  • Next-Key Lock:1+2,鎖定一個範圍,並鎖定記錄自己。目的:解決幻讀

共享鎖

共享鎖shared locks(S鎖)也稱讀鎖,容許其餘事物再加S鎖,不容許其餘事物再加X鎖數據庫

加鎖方式:bash

select...lock in share mode複製代碼

注意:數據結構

  • 對於使用共享鎖的事務,其餘事務只能讀,不可寫
  • 若是執行了更新操做則會一直等待,直到當前事務commit或者rollback
  • 若是當前事務也執行了其餘事務處於等待的那條sql語句,當前事務將會執行成功,而其餘事務會報死鎖
  • 而且容許其餘鎖共存

排它鎖

排它鎖Exclusive Locks(X鎖)也稱寫鎖,不容許其餘事務再加S鎖或者X鎖併發

加鎖方式測試

select ... for update複製代碼

→ for update:InnoDB默認是行級別的鎖,當有明確指定的主鍵時,使用的是行鎖;不然使用的是表鎖。使用狀況詳細以下:ui

  • 明確指定主鍵,而且由此記錄,行級鎖。例:

    select name,age from tb_user where id = '1' for update(id是主鍵)複製代碼
  • 明確指定主鍵/索引,若查無記錄,無鎖。例:

    select name,age from tb_user where id = '1' for update(id是主鍵,但不存在id = 1的數據)複製代碼
  • 無主鍵/索引,表級鎖。例:

    select name,age from tb_user where age = 12 for update(age是普通字段)複製代碼
  • 主鍵/索引不明確,表級鎖。例:

    select name,age from tb_user where age = 12,id = '1' for update(id是主鍵,age不是,但數據庫有此數據)複製代碼

注意:spa

  • 對於排它鎖的事務,其餘事物可讀,但不可進行更新操做
  • for update僅使用與InnoDB,而且必須開啓事務,在begin和commit之間才生效
  • 當一個事務進行for update的時候,另外一個事務也有for update時會一直等待,直到以前的事務commit或rollback或斷開鏈接釋放鎖纔拿到鎖進行後面的操做(排它鎖不能共存)
  • innoDB引擎.默認對update,delete,insert加排他鎖,select語句默認不加鎖

樂觀鎖

讀取出記錄,並將此版本一同讀出,執行更新操做並對記錄的版本號+1。此時,將待提交記錄的版本號與數據庫對應表的記錄版本好進行對比,若是大於數據庫原有版本號的話,予以更新;不然認爲是過時數據,更新失敗。目的是爲了用於解決併發問題。

例:A、B兩我的同時修改同一條記錄,設數據庫原有金額是100元,A對金額+100,B往數據庫-50,正常結果是150,但因爲併發,結果有多是200,或者50

解決:A B同時讀取出數據版本爲1,A對金額+100,並修改數據版本爲2,提交數據,此時數據版本爲1,更新成功。B讀取數據版本1,對金額-50,此時結果爲50,並修改數據版本爲2,提交數據,對比數據庫原版本2,沒有比原版本高,更新失敗

間隙鎖

間隙鎖是在索引記錄之間的間隙的鎖定,或在最後一個索引記錄以前或以後的間隙上的鎖定

使用惟一索引搜索惟一一行的一句不須要間隙鎖鎖定(不包括搜索條件包含多列惟一索引的某些列的狀況,查詢出的多條記錄,會發生間隙索引),詳細例子以下:

  • 前提:(id是主鍵索引)因爲搜索結果是惟一的一條記錄,因此不會使用間隙鎖

    select id,name,age from tb_user where id = '1'複製代碼
  • 前提:(id是主鍵索引、age是非索引字段)因爲搜索結果可能不止一條記錄,因此會使用間隙鎖select id,name,age from tb_user where id = '1' and age = 13
演示:
→ 數據結構:

CREATE TABLE `tb_user` (
  `id` int(10) NOT NULL,
  `name` varchar(255) NOT NULL DEFAULT '',
  KEY `index_id` (`id`),
  KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8複製代碼

→ 初始化數據:


ps:我如今使用的是數據庫默認的隔離級別:repeatable read,並在本地開啓兩個客戶端進行測試。

→ 客戶端1:開啓事務,在客戶端中修改id爲1-6之間的數據,此時id=2這行記錄是不存在的


→ 客戶端2:開啓事務,往數據庫中添加id爲2的記錄時會發現該操做會被阻塞!


-->上述狀況就說明了有間隙鎖的存在

--> 接下來我修改了隔離級別爲read commited,能夠發現上述添加操做,即id =2 的記錄會添加成功,說明read commited的隔離級別不會使用間隙鎖。

注意:

  • 間隙鎖在InnoDB的惟一做用就是防止其它事務的插入操做,以此來達到防止幻讀的發生,因此間隙鎖不分什麼共享鎖與排它鎖。
  • 若是InnoDB掃描的是一個主鍵/惟一索引,那麼InnoDB只會採用行鎖(Record Lock)方式來加鎖,而不會使用間隙鎖(Next-Key Lock)的方式。
  • 間隙鎖只是阻止其餘事物插入到間隙當中,並不阻止不一樣的事物在同一間隙上得到間隙鎖。
  • 將隔離級別設置爲read_commited或啓用innodb_locks_unsafe_for_binlog系統變量(現已被棄用)可明確禁止使用間隙鎖

MVCC(Snapshot read vs current read)

MVCC,基於多版本的併發控制協議,最典型的是讀不加鎖,讀寫不衝突,其包含兩種讀操做,即快照讀(snapshot read)與當前讀(current read)。

  • 快照讀:讀取記錄的可見版本,不加鎖。
  • 當前讀:讀取記錄的最新版本,當前讀返回的記錄,都會加鎖,保證其餘事物不會再修改這條記錄

那具體哪些操做爲當前讀,哪些操做又是快照讀呢,讓咱們來看一下:

→ 快照讀:簡單的讀操做,屬於快照讀,不加鎖。(不過有些會有點小例外)

例:select * from tb_user where ?

→ 當前讀:特殊的讀操做,屬於當前讀,須要加鎖。

  • select * from table where ? lock in share mode;

  • select * from table where ? for update;

  • insert into table values (…);

  • update table set ? where ?;

  • delete from table where ?;

全部以上的語句,都屬於當前讀,讀取記錄的最新版本。而且,讀取以後,還須要保證其餘併發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其餘的操做,都加的是X鎖 (排它鎖)。

以上是我這幾天研究數據庫鎖作出的總結,有什麼不足歡迎指正哈~

最後推薦一位大牛寫的博客哈,裏面寫的很詳細:hedengcheng.com/?p=771#_Toc…

相關文章
相關標籤/搜索