天天看點

18@MySQL中的鎖機制

文章目錄

  • ​​一、資料庫的鎖機制​​
  • ​​1、什麼是鎖​​
  • ​​2、并發控制​​
  • ​​二、鎖的分類​​
  • ​​【DML鎖與DDL鎖】​​
  • ​​三、行級鎖、表級鎖、頁級鎖(粒度劃分)​​
  • ​​1、【行級鎖】​​
  • ​​【行級鎖的分類】​​
  • ​​【行級鎖示意圖】​​
  • ​​【案列】(行級鎖)​​
  • ​​2、【表及鎖】 (偏向于讀)​​
  • ​​【表級鎖的分類】​​
  • ​​【表級鎖示意圖】​​
  • ​​【案列】(表級鎖)​​
  • ​​3、【頁及鎖】​​
  • ​​4、【行級鎖與表及鎖之間的比較】​​
  • ​​四、行級鎖之共享鎖與排他鎖(級别)​​
  • ​​1、行操作的相關sql語句​​
  • ​​2、innodb的加鎖方法​​
  • ​​3、案列​​
  • ​​4、共享鎖與排他鎖​​
  • ​​【共享鎖】(Share Lock)​​
  • ​​【共享鎖的使用方法】​​
  • ​​【排他鎖】​​
  • ​​【排他鎖的使用】​​
  • ​​5、意向鎖​​
  • ​​五、innodb存儲引擎的鎖機制​​
  • ​​1、【innodb的鎖模式分類】​​
  • ​​2、 【行鎖的三種算法】(Record Lock、Gap Lock、Next-Key Lock)​​
  • ​​【算法之間的關系】​​
  • ​​【不同類型鎖之間的相容】​​
  • ​​【間隙鎖實列】​​
  • ​​3、【死鎖】​​
  • ​​【死鎖的示列】​​
  • ​​【死鎖總結】​​
  • ​​4、【表鎖的使用】​​
  • ​​5、【行鎖優化】​​
  • ​​六、樂觀鎖與悲觀鎖​​
  • ​​1、悲觀鎖​​
  • ​​【悲觀鎖介紹】​​
  • ​​【悲觀鎖的基本流程】​​
  • ​​【悲觀鎖案列】​​
  • ​​【悲觀鎖的優缺點】​​
  • ​​2、樂觀鎖​​
  • ​​【樂觀鎖介紹】​​
  • ​​【樂觀鎖的實作】​​
  • ​​【樂觀鎖案列】​​
  • ​​【樂觀鎖的優缺點】​​
  • ​​3、悲觀鎖于樂觀鎖的選擇​​
  • ​​七、多版本并發控制​​

一、資料庫的鎖機制

1、什麼是鎖

鎖是一種保障資料安全的機制

鎖是計算機用以協調多個程序間并發通路同一共享資源的一種機制。

MySQL中為了保證資料通路的一緻性與有效性等功能,實作了鎖機制,

MySQL中的鎖是在伺服器層或者存儲引擎層實作的

#鎖是計算機協調多個程序或線程并發通路某一資源的機制,為什麼要是用鎖機制?
    1)并發的事務并發同時通路一個共享資源時,有可能通路資料不一緻,資料無效的問題,在并發通路情況下,可能會出現髒讀、不可重複讀和幻讀等讀現象
    2)鎖機制可以将并發的資料通路順序化,以保證資料庫中資料的一緻性與有效性
    3)為了保護并發的寫行為時資料安全的


#資料安全性的保障
    1)以互斥鎖為例:多個任務在執行時需要搶到互斥鎖才能運作,同一時間隻有一個任務可以拿到,其它都需要等待,即互斥鎖保障多個任務同一時間隻有一個在運作,保證資料的一緻性      

2、并發控制

并發控制(Concurrency control)是確定及時糾正由并發操作導緻的錯誤的一種機制
#并發控制的實作
     1)資料庫管理系統(DBMS)中的并發控制的任務是確定在多個事務同時存取資料庫中同一資料時不破壞事務的隔離性和統一性以及資料庫的統一性
     2)封鎖、時間戳、樂觀并發控制(樂觀鎖)和悲觀并發控制(悲觀鎖)是并發控制主要采用的技術手段



#示列:
#并發操作帶來的資料不一緻性問題:      

二、鎖的分類

1)按鎖的粒度劃分:行級鎖、表級鎖、頁級鎖(mysql支援)
2)按鎖級别劃分 :共享鎖、排他鎖
3)按使用方式劃分:樂觀鎖、悲觀鎖
4)按加鎖方式劃分:自動鎖、顯式鎖
5)按操作劃分:DML鎖、DDL鎖
18@MySQL中的鎖機制

【DML鎖與DDL鎖】

#DML鎖(data locks,資料鎖):
   用于保護資料的完整性,其中包括行級鎖(Row Locks (TX鎖))、表級鎖(table lock(TM鎖))

#DDL鎖(dictionary locks,資料字典鎖)      

三、行級鎖、表級鎖、頁級鎖(粒度劃分)

資料庫管理系統(DBMS)按照粒度把資料庫鎖劃分:

行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)、頁級鎖(BDB引擎 )

引擎對鎖的支援情況:
18@MySQL中的鎖機制

1、【行級鎖】

行級鎖是Mysql中鎖定粒度最細的一種鎖,其加鎖粒度最小,但加鎖的開銷也最大,發生鎖沖突的機率最低,并發度也最高
#行級鎖
   行級鎖是Mysql中鎖定粒度最細的一種鎖,表示隻針對目前操作的行進行加鎖。行級鎖能大大減少資料庫操作的沖突。其加鎖粒度最小,但加鎖的開銷也最大,。行級鎖分為共享鎖 和 排他鎖。


#行級鎖的特點:
   開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的機率最低,并發度也最高


#支援引擎:InnoDB      

【行級鎖的分類】

1)共享鎖(S):加了鎖的記錄,所有事務都能去讀取但不能修改,同時阻止其他事務獲得相同資料集的排他鎖;

2)排他鎖(X):允許已經獲得排他鎖的事務去更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖;

#共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
         示列:  select * from 表 where id = 3 or id=5 lock in share mode;




#排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE
         示列:   insert、update、delete寫操作自帶排他鎖
           select * from 表 where id = 3 or id=5 for update;      

【行級鎖示意圖】

18@MySQL中的鎖機制

【案列】(行級鎖)

SQL的執行為例,講解一下InnoDB對于單行資料的加鎖原理:
#示列:
  update user set age = 10 where id = 49;
  update user set age = 10 where name = 'Tom';


#sql注解:
第一條SQL使用主鍵查詢,隻需要在 id = 49 這個主鍵索引上加上鎖。
第二條 SQL 使用二級索引來查詢,那麼首先在 name = Tom 這個索引上加寫鎖,然後由于使用 InnoDB 二級索引還需再次根據主鍵索引查詢,是以還需要在 id = 49      
sql :update user set age = 10 where id > 49;
sql注解:mysql Server 會根據 WHERE 條件讀取第一條滿足條件的記錄,然後 InnoDB 引擎會将第一條記錄傳回并加鎖,接着 MySQL Server 發起更新改行記錄的 UPDATE 請求,更新這條記錄。一條記錄操作完成,再讀取下一條記錄,直至沒有比對的記錄為止
18@MySQL中的鎖機制

2、【表及鎖】 (偏向于讀)

表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對目前操作的整張表加鎖,它實作簡單,資源消耗較少,被大部分MySQL引擎支援

開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖沖突的機率最高,并發度最低

最常使用的MYISAM與INNODB都支援表級鎖定

表級鎖定分為表共享讀鎖(共享鎖S)與表獨占寫鎖(排他鎖X)

#表級鎖的特點:
    開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖沖突的機率最高,并發度最低。



#支援引擎:      

【表級鎖的分類】

表級鎖也是意向鎖,分為讀意向鎖(IS鎖)和寫意向鎖(IX鎖)

意向鎖之間是不會産生沖突的,它隻會阻塞表級讀鎖或寫鎖。意向鎖不于行級鎖發生沖突

表級鎖定分為表共享讀鎖(共享鎖)與表獨占寫鎖(排他鎖)

鎖之間的相容性:
18@MySQL中的鎖機制
(注意上面的X與S是說表級的X鎖和S鎖,意向鎖不和行級鎖發生沖突)

【表級鎖示意圖】

#表級鎖概述:
    1)表鎖使用的是一次性鎖技術,也就是說,在會話開始的地方使用 lock 指令将後續需要用到的表都加上鎖,在表釋放前,隻能通路這些加鎖的表,不能通路其他表,直到最後通過 unlock tables 釋放所有表鎖
    2)除了使用 unlock tables 顯示釋放鎖之外,會話持有其他表鎖時執行lock table 語句會釋放會話之前持有的鎖;會話持有其他表鎖時執行 start transaction 或者 begin 開啟事務時,也會釋放之前持有的鎖
    3)表鎖由 MySQL Server 實作,行鎖則是存儲引擎實作,不同的引擎實作的不同。在 MySQL 的常用引擎中 InnoDB 支援行鎖,而 MyISAM 則隻能使用 MySQL Server 提供的表鎖      
18@MySQL中的鎖機制

【案列】(表級鎖)

#格式l:ock table 表名 read(write),表名 read(write),.....;


#給表加讀鎖或者寫鎖
#列如:
mysql> lock table employee write;
Query OK, 0 rows affected (0.00 sec)
mysql> show open tables where in_use>= 1;
+----------+----------+--------+-------------+
| Database | Table    | In_use | Name_locked |
+----------+----------+--------+-------------+
| ttt      | employee |      1 |           0 |
+----------+----------+--------+-------------+
1 row in set (0.00 sec)


mysql> unlock tables; -- UNLOCK TABLES    #釋放被目前會話持有的任何鎖
Query OK, 0 rows affected (0.00 sec)
 
mysql> show open tables where in_use>= 1;
Empty set (0.00 sec)
 
mysql>      

3、【頁及鎖】

頁級鎖是MySQL中鎖定粒度介于行級鎖和表級鎖中間的一種鎖

表級鎖速度快,但沖突多,行級沖突少,但速度慢。是以取了折中的頁級,一次鎖定相鄰的一組記錄

BDB支援頁級鎖

特點:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般

4、【行級鎖與表及鎖之間的比較】

#表鎖:加鎖過程的開銷小,加鎖的速度快;不會出現死鎖的情況;鎖定的粒度大,發生鎖沖突的幾率大,~~并發度低~~ ;
     一般在執行DDL語句時會對整個表進行加鎖,比如說 ALTER TABLE 等操作;
     如果對InnoDB的表使用行鎖,被鎖定字段不是主鍵,也沒有針對它建立索引的話,那麼将會鎖整張表;
     表級鎖更适合于以查詢為主,并發使用者少,隻有少量按索引條件更新資料的應用,如Web 應用。


#行鎖:加鎖過程的開銷大,加鎖的速度慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的機率最低,~~并發度也最高~~ ;      

四、行級鎖之共享鎖與排他鎖(級别)

共享鎖

排他鎖

1、行操作的相關sql語句

#對于寫操作:
    insert、update、delete  ------> sql語句InnoDB 會自動給涉及的資料加鎖,而且是排他鎖


#對于讀操作:
    select -----> 普通的sql語句,InnoDB不會加任何鎖,需要我們手動自己加,可以加兩種類型的鎖
   1)共享鎖(S):SELECT ... LOCK IN SHARE MODE;    --# 查出的記錄行都會被鎖住
   2)排他鎖(X):SELECT ... FOR UPDATE;            --# 查出的記錄行都會被鎖住      

2、innodb的加鎖方法

意向鎖是 InnoDB 自動加的,不需要使用者幹預;
1)對于UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及的資料集加上排他鎖;
2)對于普通的SELECT語句,InnoDB不會加任何鎖;事務可以通過以下語句顯示給記錄集添加共享鎖或排他鎖:
        共享鎖(S):select * from table_name where ... lock in share mode  此時其他 session 仍然可以查詢記錄,并也可以對該記錄加 share mode 的共享鎖。但是如果目前事務需要對該記錄進行更新操作,則很有可能造成死鎖。
        排他鎖(X):select * from table_name where ... for update   其他session可以查詢記錄,但是不能對該記錄加共享鎖或排他鎖,隻能等待鎖釋放後在加鎖





####################################### 兩種加鎖方式 ################################
1)#select **for update**  (添加排他鎖)
在執行這個 select 查詢語句的時候,會将對應的索引通路條目加上排他鎖(X鎖),也就是說這個語句對應的鎖就相當于update帶來的效果;

#*使用場景*:
    為了讓確定自己查找到的資料一定是最新資料,并且查找到後的資料值允許自己來修改,此時就需要用到select for update語句;

#*性能分析*:
    select for update語句相當于一個update語句。在業務繁忙的情況下,如果事務沒有及時地commit或者rollback可能會造成事務長時間的等待,進而影響資料庫的并發使用效率。






2)#select lock **in share mode** (添加共享鎖)
in share mode 子句的作用就是将查找的資料加上一個share鎖,這個就是表示其他的事務隻能對這些資料進行簡單的 select 操作,而不能進行 DML 操作

#*使用場景*:
    為了確定自己查詢的資料不會被其他事務正在修改,也就是確定自己查詢到的資料是最新的資料,并且不允許其他事務來修改資料。與select for update不同的是,本事務在查找完之後不一定能去更新資料,因為有可能其他事務也對同資料集使用了 in share mode 的方式加上了S鎖;

#*性能分析*:
    select lock in      

3、案列

#資料表準備:
create table employee(
id int primary key auto_increment,
name varchar(20) not null,
age int(3) unsigned not null default 20
);

insert into employee(name) values
('egon'),
('alex'),
('wupeiqi'),
('yuanhao'),
('liwenzhou'),
('jingliyang'),
('jinxin'),
('成龍'),
('歪歪'),
('丫丫'),
('丁丁'),
('星星'),
('格格'),
('張野'),
('程咬金'),
('程咬銀'),
('程咬銅'),
('程咬鐵')
;
update employee set age = 18 where id <=3;




#驗證insert、update、delete是預設加排他鎖的
############################(事務一)#############################
#步驟一:
start transaction;
select name from employee where id = 1;
#步驟三:
-- 此處的update會阻塞在原地,因為事務二并未送出事務,即尚未釋放排他鎖
update employee set name = concat(name,"_NB") where id = 1;,
#步驟五:
 -- 此處查詢到的結果為EGON_NB
select name from employee where id = 1;
#步驟六:
-- 送出之後,name持久化為EGON_NB
commit;
select name from employee where id = 1;

###########################(事務二)###############################
#步驟二:
-- 把小寫的egon變為大寫,此時的update會自動加鎖
update employee set name = "EGON" where id = 1;
立即檢視修改結果,name變為EGON,但此時還沒有commit
select name from employee where id = 1;
#步驟四:
-- 事務二一旦送出,事務一阻塞在步驟3的操作即會運作成功
commit;
-- 檢視修改結果,name變為大寫的EGON
select name from employee where id = 1;      

4、共享鎖與排他鎖

特例:加過排他鎖的資料行在其他事務種是不能修改資料的,也不能通過for update和lock in share mode鎖的方式查詢資料,但可以直接通過select …from…查詢資料,因為普通select查詢沒有任何鎖機制

【共享鎖】(Share Lock)

共享鎖又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對于同一資料可以共享一把鎖,獲準共享鎖的事務隻能讀資料,不能修改資料直到已釋放所有共享鎖,是以共享鎖可以支援并發讀

如果事務T對資料A加上共享鎖後,則其他事務隻能對A再加共享鎖或不加鎖(在其他事務裡一定不能再加排他鎖,但是在事務T自己裡面是可以加的)

【共享鎖的使用方法】

#文法:SELECT ... LOCK IN SHARE MODE;      

【排他鎖】

排他鎖又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他所并存,如一個事務擷取了一個資料行的排他鎖,其他事務就不能再對該行加任何類型的其他他鎖(共享鎖和排他鎖),但是擷取排他鎖的事務是可以對資料就行讀取和修改

【排他鎖的使用】

#格式: SELECT ... FOR UPDATE;      

5、意向鎖

意向鎖是表級鎖,

其設計目的主要是為了在一個事務中揭示下一行将要被請求鎖的類型

#意向鎖的作用
就是當一個事務在需要擷取資源鎖定的時候,如果遇到自己需要的資源已經被排他鎖占用的時候,該事務可以需要鎖定行的表上面添加一個合适的意向鎖。
如果自己需要一個共享鎖,那麼就在表上面添加一個意向共享鎖。而如果自己需要的是某行(或者某些行)上面添加一個排他鎖的話,則先在表上面添加一個意向排他鎖



#InnoDB中有兩個意向鎖(表鎖)
1)意向共享鎖(IS):事務打算給資料行共享鎖;,事務在給一個資料行加共享鎖前必須先取得該表的IS鎖
2)意向排他鎖(IX):事務打算給資料行加排他鎖;事務在給一個資料行加排他鎖前必須先取得該表的IX鎖      

五、innodb存儲引擎的鎖機制

InnoDB支援行級鎖(row-level locking)和表級鎖,預設為行級鎖(偏向于寫)

1、【innodb的鎖模式分類】

InnoDB的鎖定模式實際上可以分為四種:
共享鎖(S),排他鎖(X),意向共享鎖(IS)和意向排他鎖(IX)
18@MySQL中的鎖機制

2、 【行鎖的三種算法】(Record Lock、Gap Lock、Next-Key Lock)

innodb行鎖的三種算法:(屬于排他鎖)

Record Lock: 适用于單行記錄上的鎖

Gap Lock:間隙鎖

Next-Key Lock:臨鍵鎖,Record Lock結合Gap Lock,Next-Key Lock既鎖定記錄本身也鎖定一個範圍,注意,InnoDB存儲引擎還會對輔助索引下一個鍵值加上gap lock

Insert Intention Lock:插入意向鎖

【算法之間的關系】
18@MySQL中的鎖機制
#算法鎖的詳解:
#記錄鎖(Record Lock)
記錄鎖最簡單的一種行鎖形式,補充下:行鎖是加在索引上的,如果當你的查詢語句不走索引的話,那麼它就會更新到表鎖,最終造成效率低下,是以在寫SQL語句時需要特别注意



#間隙鎖(Gap Lock)
 1)間隙鎖是所在兩個存在的索引之間,是一個開區間,像最開始的那張索引圖,15和18之間,是有(16,17)這個間隙存在的
 2)當我們使用範圍條件而不是相等條件去檢索,并請求鎖時,InnoDB就會給符合條件的記錄的索引項加上鎖;而對于鍵值在條件範圍内但并不存在(參考上面所說的空閑塊)的記錄,就叫做間隙
 3)間隙鎖是可以共存的,共享間隙鎖與獨占間隙鎖之間是沒有差別的,兩者之間并不沖突。其存在的目的都是防止其他事務往間隙中插入新的紀錄,故而一個事務所采取的間隙鎖是不會去阻止另外一個事務在同一個間隙中加鎖的




#臨鍵鎖(Next-key Lock)
 1)臨鍵鎖是記錄鎖與與間隙鎖的結合,是以臨鍵鎖與間隙鎖是一個同時存在的概念,并且臨鍵鎖是個左開有閉的卻比如(16, 18]
 2)MySQL 預設隔離級别是RR,在這種級别下,如果你使用 select in share mode 或者 select for update 語句,那麼InnoDB會使用臨鍵鎖(記錄鎖 + 間隙鎖),因而可以防止幻讀




#插入意向鎖(Insert Intention Lock)
 1)插入意圖鎖是一種間隙鎖,在行執行 INSERT 之前的插入操作設定。如果多個事務 INSERT 到同一個索引間隙之間,但沒有在同一位置上插入,則不會産生任何的沖突。假設有值為4和7的索引記錄,現在有兩事務分别嘗試插入值為 5 和 6 的記錄,在獲得插入行的排他鎖之前,都使用插入意向鎖鎖住 4 和 7 之間的間隙,但兩者之間并不會互相阻塞,因為這兩行并不沖突。
 2)插入意向鎖隻會和 間隙或者 Next-key 鎖沖突,正如上面所說,間隙鎖作用就是防止其他事務插入記錄造成幻讀,正是由于在執行 INSERT 語句時需要加插入意向鎖,而插入意向鎖和間隙鎖沖突,進而阻止了插入操作的執行      

【不同類型鎖之間的相容】

18@MySQL中的鎖機制

【間隙鎖實列】

# 準備資料
create table t1(
id int,
key idx_id(id)
)engine=innodb;
 
insert t1
values
(1),
(5),
(7),
(11);
 
mysql> explain select * from t1 where id=7 for update;  -- key字段為idx_id,命中索引,即會采用行鎖而不是表鎖
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
|  1 | SIMPLE      | t1    | ref  | idx_id        | idx_id | 5       | const |    1 | Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)


#實驗步驟
###################### (事務一) #######################
步驟一:
start transaction;
步驟四:
-- 下述sql全都會阻塞在原地
insert t1 values(5);
insert t1 values(6);
insert t1 values(7);
insert t1 values(8);
insert t1 values(9);
insert t1 values(10);
-- 下述等sql均不會阻塞
insert t1 values(11); 
insert t1 values(1); 
insert t1 values(2);
insert t1 values(3);
insert t1 values(4);   
步驟五:
-- 送出一下事務,不要影響下一次實驗
commit;




###################### (事務二) #######################
步驟二:
-- 開啟事務
start transaction;

步驟三:
-- 加排他鎖
select * from t1 where id=7 for update;  -- 須知
-- 1、上述語句命中了索引,是以加的是行鎖
-- 2、InnoDB對于行的查詢都是采用了Next-Key Lock的算法,鎖定的不是單個值,而是一個範圍(GAP)
表記錄的索引值為1,5,7,11,其記錄的GAP區間如下:(-∞,1],(1,5],(5,7],(7,11],(11,+∞)
因為記錄行預設就是按照主鍵自增的,是以是一個左開右閉的區間
其中上述查詢條件id=7處于區間(5,7]中,是以Next-Key lock會鎖定該區間的記錄,但是還沒完
-- 3、InnoDB存儲引擎還會對輔助索引下一個鍵值加上gap lock。
區間(5,7]的下一個Gap是(7,11],是以(7,11]也會被鎖定
綜上所述,最終确定5-11之間的值都會被鎖定
步驟六:
-- 送出一下事務,不要影響下一次實驗
commit;



#逾時時間設定:      

3、【死鎖】

MyISAM中是不會産生死鎖的,因為MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待

而在InnoDB中,鎖是逐漸獲得的,就造成了死鎖的可能

第一個案例:每個事務執行兩條SQL,分别持有了一把鎖,然後加另一把鎖,産生死鎖
18@MySQL中的鎖機制
第二個案列:隻有多個事務同時運作的情況下才可能出現,但隐蔽性極強,雖然每個Session都隻有一條語句,仍舊會産生死鎖。要分析這個死鎖,首先必須用到本文前面提到的MySQL加鎖的規則。針對Session 1,從name索引出發,讀到的[hdc, 1],[hdc, 6]均滿足條件,不僅會加name索引上的記錄X鎖,而且會加聚簇索引上的記錄X鎖,加鎖順序為先[1,hdc,100],後[6,hdc,10]。而Session 2,從pubtime索引出發,[10,6],[100,1]均滿足過濾條件,同樣也會加聚簇索引上的記錄X鎖,加鎖順序為[6,hdc,10],後[1,hdc,100]。發現沒有,跟Session 1的加鎖順序正好相反,如果兩個Session恰好都持有了第一把鎖,請求加第二把鎖,死鎖就發生了
18@MySQL中的鎖機制

【死鎖的示列】

(
id int not null unique auto_increment,
name varchar(20) not null,
sex enum('male','female') not null default 'male', #大部分是男的
age int(3) unsigned not null default 28,
hire_date date not null,
post varchar(50)
);


#插入記錄
#三個部門:教學,銷售,營運
insert into emp(name,sex,age,hire_date,post) values
('egon','male',78,'20170301','老男孩駐沙河辦事處外交大使'), #以下是教學部
('zxx','male',19,'20150302','teacher'),
('wupeiqi','male',81,'20130305','teacher'),
('yuanhao','male',73,'20140701','teacher'),
('liwenzhou','male',28,'20121101','teacher'),
('jingliyang','female',18,'20110211','teacher'),
('jinxin','male',18,'19000301','teacher'),
('成龍','male',48,'20101111','teacher'),

('歪歪','female',48,'20150311','sale'),#以下是銷售部門
('丫丫','female',38,'20101101','sale'),
('丁丁','female',18,'20110312','sale'),
('星星','female',18,'20160513','sale'),
('格格','female',28,'20170127','sale'),

('張野','male',28,'20160311','operation'), #以下是營運部門
('程咬金','male',18,'19970312','operation'),
('程咬銀','female',18,'20130311','operation'),
('程咬銅','male',18,'20150411','operation'),
('程咬鐵','female',18,'20140512','operation')
;


create index idx_name on emp(name);
create index idx_age on emp(age);



#事務1與事務2同時運作,會出現死鎖
事務1
begin;
update emp set post="IT" where name="egon" or name="zxx";  -- 索引的有序性,'zxx'>'egon',是以在輔助索引中zxx對應的記錄在後
                                                           -- 即 ('egon',主鍵字段為1),('zxx',主鍵id為2),在鎖完輔助索引後鎖主鍵索引時,先鎖主鍵1對應的記錄再鎖2

事務2

begin;
select * from emp where age=78 or age = 19 for update;      

【死鎖總結】

#1、關于死鎖問題需要儲備的知識
    在MySQL中,行級鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。 在UPDATE、DELETE操作時,MySQL不僅鎖定WHERE條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的next-key locking。

#2、死鎖産生的本質原理
    死鎖的發生與否,并不在于事務中有多少條SQL語句,死鎖的關鍵在于:兩個(或以上)的Session加鎖的順序不一緻。而使用本文上面提到的,分析MySQL每條SQL語句的加鎖規則,分析出每條語句的加鎖順序,然後檢查多個并發SQL間是否存在以相反的順序加鎖的情況,就可以分析出各種潛在的死鎖情況,也可以分析出線上死鎖發生的原因。      

InnoDB一般都可以檢測到,并使一個事務釋放鎖回退,另一個擷取鎖完成事務。

有多種方法可以避免死鎖

(1)如果不同程式會并發存取多個表,盡量約定以相同的順序通路表,可以大大降低死鎖機會。
(2)在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖産生機率;
(3)對于非常容易産生死鎖的業務部分,可以嘗試使用更新鎖定顆粒度,通過表級鎖定來減少死鎖産生的機率;
(4)在程式以批量方式處理資料的時候,如果事先對資料排序,保證每個線程按固定的順序來處理記錄,也可以大大降低出現死鎖的可能      

4、【表鎖的使用】

1》事務需要更新大部分資料,表又較大,
   若使用預設的行鎖,不僅該事務執行效率低(因為需要對較多行加鎖,加鎖是需要耗時的); 而且可能造成其他事務長時間鎖等待和鎖沖突; 這種情況下可以考慮使用表鎖來提高該事務的執行速度


2》事務涉及多個表,較複雜,很可能引起死鎖,造成大量事務復原
   這種情況也可以考慮一次性鎖定事務涉及的表,進而避免死鎖、減少資料庫因事務復原帶來的開銷當然,應用中這兩種事務不能太多,否則,就應該考慮使用MyISAM      

5、【行鎖優化】

show status like ‘innodb_row_lock%’;//檢視行鎖的狀态
1) 盡可能讓所有資料檢索都通過索引來完成, 進而避免無索引行鎖更新為表鎖
2) 合理設計索引,盡量縮小鎖的範圍
3) 盡可能減少檢索條件,避免間隙鎖
4) 盡量控制事務大小,減少鎖定資源量和時間長度
5)      

六、樂觀鎖與悲觀鎖

資料庫管理系統(DBMS)中的并發控制的任務是確定在多個事務同時存取資料庫中同一資料時不破壞事務的隔離性和統一性以及資料庫的統一性

樂觀并發控制(樂觀鎖)和悲觀并發控制(悲觀鎖)是并發控制主要采用的技術手段

1、悲觀鎖

【悲觀鎖介紹】

當我們要對一個資料庫中的一條資料進行修改的時候,為了避免同時被其他人修改,最好的辦法就是直接對該資料進行加鎖以防止并發,這種借助資料庫鎖機制在修改資料之前先鎖定,再修改的方式被稱之為悲觀并發控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)
#悲觀鎖簡述:
1)在關系資料庫管理系統裡,悲觀并發控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)是一種并發控制的方法。它可以阻止一個事務以影響其他使用者的方式來修改資料。如果一個事務執行的操作都某行資料應用了鎖,那隻有當這個事務把鎖釋放,其他事務才能夠執行與該鎖沖突的操作。

2)悲觀并發控制主要用于資料争用激烈的環境,以及發生并發沖突時使用鎖保護資料的成本要低于復原事務的成本的環境中


ps : 悲觀鎖,正如其名,它指的是對資料被外界(包括本系統目前的其他事務,以及來自外部系統的事務處理)修改持保守态度(悲觀),是以,在整個資料處理過程中,将資料處于鎖定狀态。 
     悲觀鎖的實作,往往依靠資料庫提供的鎖機制 (也隻有資料庫層提供的鎖機制才能真正保證資料通路的排他性,否則,即使在本系統中實作了加鎖機制,也無法保證外部系統不會修改資料),現在網際網路高并發的架構中,受到fail-fast思路的影響,悲觀鎖已經非常少見了      

【悲觀鎖的基本流程】

#使用基本流程
1) 在對任意記錄進行修改前,先嘗試為該記錄加上排他鎖(exclusive locking)。

2) 如果加鎖失敗,說明該記錄正在被修改,那麼目前查詢可能要等待或者抛出異常。 具體響應方式由開發者根據實際需要決定。

3) 如果成功加鎖,那麼就可以對記錄做修改,事務完成後就會解鎖了。

4) 其間如果有其他對該記錄做修改或加排他鎖的操作,都會等待我們解鎖或直接抛出異常



ps : 行鎖、表鎖、讀鎖、寫鎖都是在操作之前先上排他鎖







#悲觀鎖資料表中的實作
MySQL使用悲觀鎖,開啟顯示送出,必須先關閉MySQL的自動送出功能
set autocommit=0;        #MySQL預設使用自動送出autocommit模式,在執行完sql後會自動送出并釋放鎖      

【悲觀鎖案列】

假設商品表中有一個字段quantity表示目前該商品的庫存量。假設有一件Dulex套套,其id為100,quantity=8個;如果不使用鎖,那麼操作方法
#步驟分析一
1) #查出商品剩餘量
 select quantity from items where id=100;

2)#如果剩餘量大于0,則根據商品資訊生成訂單
 insert into orders(id,item_id) values(null,100);

3)# 修改商品的庫存
 update Items set quantity=quantity-1 where id=100;      
18@MySQL中的鎖機制

1》對于以上寫法,在小作坊真的很正常,No Problems,但是在高并發環境下可能出現問題

2》其實在step1或者step2環節,已經有人下單并且減完庫存了,這個時候仍然去執行step3,就造成了超賣。

3》但是使用悲觀鎖,就可以解決這個問題,在上面的場景中,商品資訊從查詢出來到修改,中間有一個生成訂單的過程,使用悲觀鎖的原理就是,當我們在查詢出items資訊後就把目前的資料鎖定,直到我們修改完畢後再解鎖。那麼在這個過程中,因為資料被鎖定了,就不會出現有第三者來對其進行修改了。而這樣做的前提是需要将要執行的SQL語句放在同一個事物中,否則達不到鎖定資料行的目的

#步驟分析二
step1: #查出商品狀态
select quantity from items where id=100 for update;

step2: #根據商品資訊生成訂單
insert into orders(id,item_id) values(null,100);

step3: #修改商品的庫存
update Items set quantity=quantity-2 where id=100;      
select…for update是MySQL提供的實作悲觀鎖的方式。此時在items表中,id為100的那條資料就被我們鎖定了,其它的要執行select quantity from items where id=100 for update的事務必須等本次事務送出之後才能執行。這樣我們可以保證目前的資料不會被其它事務修改

【悲觀鎖的優缺點】

悲觀并發控制主要用于資料争用激烈的環境,以及發生并發沖突時使用鎖保護資料的成本要低于復原事務的成本的環境中
1》#優點:
   悲觀并發控制實際上是“先取鎖再通路”的保守政策,為資料處理的安全提供了保證。



2》#缺點:      

2、樂觀鎖

【樂觀鎖介紹】

在關系資料庫管理系統裡,樂觀并發控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種并發控制的方法。它假設多使用者并發的事務在處理時不會彼此互相影響,各事務能夠在不産生鎖的情況下處理各自影響的那部分資料。在送出資料更新之前,每個事務會先檢查在該事務讀取資料後,有沒有其他事務又修改了該資料。如果其他事務有更新的話,正在送出的事務會進行復原。樂觀事務控制最早是由孔祥重(H.T.Kung)教授提出
#樂觀鎖概述:
 1》樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成沖突,是以在資料進行送出更新的時候,才會正式對資料的沖突與否進行檢測,如果發現沖突了,則讓傳回使用者錯誤的資訊,讓使用者決定如何去做。

 2》相對于悲觀鎖,在對資料庫進行處理的時候,樂觀鎖并不會使用資料庫提供的鎖機制。一般的實作樂觀鎖的方式就是記錄資料版本。






ps :      

【樂觀鎖的實作】

1》#使用版本号實作
   每一行資料多一個字段version,每次更新資料對應版本号+1,
 原理:
   讀出資料,将版本号一同讀出,之後更新,版本号+1,送出資料版本号大于資料庫目前版本号,則予以更新,否則認為是過期資料,重新讀取資料

2》#使用時間戳實作      

【樂觀鎖案列】

在使用mysql資料庫存儲資料的前提下,有一個搶任務系統,一個任務隻能配置設定給n個人,如果有高并發請求,如何保證資料完整性
#分析步驟一:
1》使用者請求搶任務接口
2》讀取資料庫剩餘數量
3》如果大于0,剩餘數量減1,更新資料庫剩餘數量(update task set count=count-1 where id=‘任務id’)
4》傳回資料      
樂觀鎖的應用: 假設使用者1和使用者2同時調用請求搶任務接口,并且資料庫隻剩下一個任務可搶,任務剩餘數量使用count字段儲存; 使用者1和使用者2請求接口情況模拟,表格的每一行表示一個時間點
18@MySQL中的鎖機制
通過以上的問題,我們不難知道,本來隻有一個任務可搶的,現在被兩個使用者同時搶了,而且資料庫還出現了-1的情況,而這種情況再高并發的時候經常會遇到
解決高并發帶來的問題,就可以利用樂觀鎖的概念來解決: 1》将上面中的第3個步驟中是sql語句改為(update task set count=count-1 where id=1 and count=1)當然,其中的count=1中的1是步驟2讀取出來的資料總數 2》可以給表加一個版本号version字段,預設為1,每次執行更新的時候自增1,并在where語句後帶上讀取到的版本号,以免再讀取和更新資料之間,有第三者更新了資料庫
18@MySQL中的鎖機制
#分析步驟二:
1》使用者請求搶任務接口
2》讀取資料庫剩餘數量
3》如果大于0,剩餘數量減1,更新資料庫剩餘數量(update task set count=count-1 where id=‘任務id’ and count=‘讀取到的剩餘數量’)
4》傳回資料


ps : 以上SQL其實還是有一定的問題的,就是一旦發上高并發的時候,就隻有一個線程可以修改成功,那麼就會存在大量的失敗
有一條比較好的建議,可以減小樂觀鎖力度,最大程度的提升吞吐率,提高并發能力,如下:
//修改商品庫存 
update task set count=count-1 where id=‘任務id’ and count=‘讀取到的剩餘數量’ and count-1 >= 0;

   以上SQL語句中,通過count-1>0的方式進行樂觀鎖控制,商品個數count至少要有1件才可以。
   以上update語句,在執行過程中,會在一次原子操作中自己查詢一遍count的值,并将其扣減掉1。
  沒錯!你參加過的天貓、淘寶秒殺、聚劃算,跑的就是上述這條SQL,通過挑選樂觀鎖,可以減小鎖力度,進而提升吞吐      

【樂觀鎖的優缺點】

現在網際網路高并發的架構中,受到fail-fast思路的影響,悲觀鎖已經非常少見
優點與不足
樂觀并發控制相信事務之間的資料競争(data race)的機率是比較小的,是以盡可能直接做下去,直到送出的時候才去鎖定,是以不會産生任何鎖和死鎖      

3、悲觀鎖于樂觀鎖的選擇

1》 樂觀鎖并未真正加鎖,效率高。一旦鎖的粒度掌握不好,更新失敗的機率就會比較高,容易發生業務失敗。

2》 悲觀鎖依賴資料庫鎖,效率低。更新失敗的機率比較低。





   ps      

七、多版本并發控制