MySQL主要有表鎖,行鎖和頁鎖,頁鎖用得少,本文主要介紹表鎖和行鎖。
一、鎖的分類
從對數據的操作類型來分,可以分爲讀鎖和寫鎖;從對數據操作粒度來分,可分爲表鎖和行鎖。
讀鎖(共享鎖):針對同一份數據,多個讀操作可以同時進行而不會互相影響;
寫鎖(排他鎖):當前寫操作沒有完成前,會阻斷其他寫鎖和讀鎖;
表鎖:鎖住被操作的整張表;
行鎖:鎖住被操作表中的被操作行,其他行不受影響。
二、表鎖
1. 介紹:
表鎖偏向MyISAM存儲引擎,開銷小,加鎖快,無死鎖,粒度大,併發性差。下面建表演示表鎖的用法。
create table mylock (
id int not null primary key auto_increment,
name varchar(20)
) engine myisam;
insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
這裏用了MyISAM引擎,這個引擎是寫優先的,加了寫鎖後,其他線程不能對被鎖的表做任何操作,即使是查詢,所以如果寫操作很多,就會導致其他線程的讀操作難以執行,大量的查詢sql被阻塞。
- 增加表鎖的語法:
lock table 表名1 read(write), 表名2 read(write) ……;
- 查看錶上加過的鎖;
show open tables;
- 給mylock表加讀鎖,tblA加寫鎖:
lock table mylock read, tblA write;
- 釋放鎖:
unlock tables;
2. 表鎖演示:
讀鎖:
首先給mylock
表加上讀鎖,然後打開兩個session,暫且將左邊的稱爲session1,右邊的稱爲session2,如下:
然後進行如下操作:
在session1中執行
lock table mylock read
,然後執行select * from mylock;
,結果是可以查詢出數據。即自己加了讀鎖,自己是可以查的;在session2中執行
select * from mylock;
,結果也是可以查詢出數據。說明讀鎖,大家都可以讀數據;在session1中執行
update mylock set name = 'aa' where id = 1;
,結果報瞭如下錯誤:
ERROR 1099 (HY000): Table 'mylock' was locked with a READ lock and can't be updated
- session1給mylock表加了讀鎖,那麼session1能讀其他的表嗎?我現在執行
select * from tblA;
,結果是不行的,報瞭如下的錯誤:
ERROR 1100 (HY000): Table 'tblA' was not locked with LOCK TABLES
session2能讀tblA表嗎?執行
select * from tblA;
,結果是可以的。session2中執行
update mylock set name = 'aa' where id = 1;
,結果如下:
一直卡着不動,說明阻塞了,要直到mylock表解鎖才能成功。
表讀鎖總結:
操作 | 當前session | 其他session |
---|---|---|
讀當前表 | Y | Y |
讀其他表 | N | Y |
寫當前表 | N | 阻塞,直到鎖被釋放 |
寫其他表 | N | Y |
寫鎖:
給mylock
表加上寫鎖,lock table mylock write
,然後在session1和session2中對當前表和其他表進行讀寫操作,最後結論如下:
操作 | 當前session | 其他session |
---|---|---|
讀當前表 | Y | 阻塞,直至鎖被釋放 |
讀其他表 | N | Y |
寫當前表 | Y | 阻塞,直到鎖被釋放 |
寫其他表 | N | Y |
對於表讀鎖和表寫鎖,總結起來就是加了讀鎖,當前session只能讀當前表,其他session只有寫當前表會被阻塞;加了寫鎖,當前session只能對當前表進行讀寫,其他session對當前表的讀寫都會被阻塞。所以表鎖一般偏讀,也就是一般不會加表寫鎖,加寫鎖可能會導致大量的查詢被阻塞。
3. 表鎖分析:
MySQL中有兩個變量,可以記錄表的鎖定情況,如下:
Table_locks_immediate:表示可以立即獲取鎖的查詢次數,每次加1;
Table_locks_waited:出現表級鎖爭用而發生等待的次數,每次加1;
查看這兩個變量的值的sql:
show status like 'table%';
三、行鎖
1. 介紹:
行鎖偏向InnoDB存儲引擎,開銷大,加鎖慢,會出現死鎖,粒度小,併發性好。InnoDB支持事務,而MyISAM是不支持事務的,InnoDB默認採用的也是行鎖,下面建表演示表鎖的用法。
create table col_lock(
id int not null primary key auto_increment,
name varchar(20)
) engine innodb;
insert into col_lock(name) values('a');
insert into col_lock(name) values('b');
insert into col_lock(name) values('c');
insert into col_lock(name) values('d');
insert into col_lock(name) values('e');
2. 行鎖總結:
innodb支持事務,並且默認是自動提交,爲了演示行鎖,先執行下面的sql把自動提交關閉。
set autocommint = 0;
接下來看看session1和session2的各種操作情況:
操作 | 當前session | 其他session |
---|---|---|
讀當前行 | Y | Y |
寫當前行 | Y | 阻塞,直到鎖被釋放 |
兩個session操作不同的行 | Y | Y |
3. 分析行鎖:
我們可以通過如下sql查看行鎖的爭奪情況:
show status like 'innodb_row_lock%';
執行結果是:
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 57446 |
| Innodb_row_lock_time_avg | 28723 |
| Innodb_row_lock_time_max | 51618 |
| Innodb_row_lock_waits | 2 |
+-------------------------------+-------+
- Innodb_row_lock_current_waits:當前正在等待鎖定的數量
- Innodb_row_lock_time:從系統啓動到現在鎖定總時長
- Innodb_row_lock_time_avg:每次等待所花的平均時間
- Innodb_row_lock_time_max:從系統啓動到現在獲取鎖等待最久的一次花的時間
- Innodb_row_lock_waits:系統啓動到現在獲取鎖等待的總次數
四、索引失效行鎖變表鎖問題
這個是比較隱蔽的問題,很難發現,但確實存在。比如之前說的varchar類型的沒加單引號,會導致索引失效,那麼這時候行鎖就會變爲表鎖。比如col_lock表的name字段是varchar類型的,先在name字段加索引,然後關閉自動提交,執行下面的語句:
update col_lock set name = aa where id = 1;
然後再另一個session中執行:
update col_lock set name = 'bb' where id = 2;
本來操作的是不同的行,即使第一條語句還沒commit,第二條應該也能執行,但實際上不行,因爲aa沒加單引號,索引失效了,行鎖變成了表鎖。
五、間隙鎖的危害
有個tblA表,age字段是加了索引的,數據如下:
我們在這session1中執行下面的update操作:
update tblA set birth = now() where age > 20 and age < 25;
其實也就是3條記錄都會被更新。執行後,先不提交,在session2中執行如下語句:
insert tblA(age,birth) values(22,now());
表中沒有age爲22的,那現在就插入一條age爲22的記錄,行鎖,兩邊操作不同的行,應該不會有任何影響的,但是現在情況如下:
直接等待鎖都超時了,這就是間隙鎖。session1中commit了之後,session2中的insert語句才能執行成功。
- 間隙:當我們使用範圍條件檢索數據,請求共享或排他鎖時,innodb會給符合條件的已有數據記錄的索引項加鎖,對於在條件範圍內但是不存在的記錄,比如age爲22在
age > 20 and age <25
這個範圍內,但是不存在這條記錄,這個就叫做間隙。innodb會對這個間隙加鎖,這就叫間隙鎖。