鎖機制
共享排他鎖
InnoDB實現(xiàn)了兩種標準行級鎖
- 共享鎖(S)
持有S鎖的事務可以讀取行 - 排他鎖(X)
持有X鎖的事務可以修改或者刪除行
共享鎖與排他鎖的兼容性:
S | X | |
---|---|---|
S | 兼容 | 互斥 |
X | 互斥 | 互斥 |
即:
- 多個事務可以同時獲取S鎖,讀讀可以并行
- 只有一個事務能夠獲取X鎖坞生,寫寫不能并行
- 一條記錄要么是S鎖要么是X鎖,讀寫不能并行
意向鎖
意向鎖是表級鎖,它表明一個事務需要在這個表的行上添加的鎖類型。
意向鎖有兩種:
- 意向共享鎖(IS)
表明一個事務有意向表中的行添加S鎖,加鎖方式SELECT …… LOCK IN SHARE MODE - 意向排他鎖(IX)
表明一個事務有意向表中的行添加X鎖誓焦,加鎖方式 SELECT …… FOR UPDATE
意向鎖的協(xié)議:
- 一個事務獲取S鎖之前,必須獲取IS鎖
- 一個事務獲取X鎖之前着帽,必須獲取IX鎖
意向鎖與共享排他鎖的兼容性:
S | IS | X | IX | |
---|---|---|---|---|
S | 兼容 | 兼容 | 互斥 | 互斥 |
IS | 兼容 | 兼容 | 互斥 | 兼容 |
X | 互斥 | 互斥 | 互斥 | 互斥 |
IX | 互斥 | 兼容 | 互斥 | 兼容 |
記錄鎖杂伟、間隙鎖、臨鍵鎖
假設有一張表:
id | name | no |
---|---|---|
1 | dxy1 | 10 |
2 | dxy2 | 20 |
3 | dxy3 | 30 |
PK:id
Key:name
Key:no
- 記錄鎖
記錄鎖是對記錄行加鎖仍翰,只對主鍵或者唯一索引生效稿壁,并且必須是精確匹配
例如:
事務A先執(zhí)行,不提交:
select * from t where id = 1 for update;
事務B后執(zhí)行:
select * from t where id = 1 for update;
如圖歉备,事務B阻塞傅是,因為id=1的記錄已經有了記錄鎖
- 間隙鎖
間隙鎖是一種開區(qū)間鎖,用來解決插入的問題
例如:
事務A先執(zhí)行蕾羊,不提交:
select * from t where id between 1 and 10 for update;
事務B后執(zhí)行:
insert into t values(8,'dxy8',8);
如圖:事務B阻塞喧笔,因為區(qū)間(1,10)已經加鎖
- 臨鍵鎖
臨鍵鎖是由記錄鎖以及索引記錄之前的間隙組成
例如:
事務A先執(zhí)行龟再,不提交:
select * from t where no = 20 for update;
事務B后執(zhí)行:
insert into t values(19,'dxy19',19);
如圖:事務B被阻塞书闸,因為區(qū)間(10,20]已經加鎖
需要注意的是
- 如果no是唯一索引利凑,那么事務A支持有id=20的記錄鎖
- 如果no是普通索引浆劲,那么會持有(10,20]的臨鍵鎖和(20,30)的間隙鎖
插入意向鎖
插入意向鎖是一種特殊的間隙鎖,由插入操作設置
多個事務插入記錄哀澈,如果在同一區(qū)間內插入位置不沖突牌借,那么不會相互阻塞
例如:
id | name | no |
---|---|---|
1 | dxy1 | 10 |
2 | dxy2 | 20 |
3 | dxy3 | 30 |
事務A先執(zhí)行,不提交:
insert into t values(11,'dxy11',11);
事務B后執(zhí)行:
insert into t values(12,'dxy12',12);
事務B不會被阻塞
但是如果事務B執(zhí)行:
insert into t values(11,'dxy11',11);
如圖:事務B依然會被阻塞割按,因為事務A占有記錄鎖
自增鎖
自增鎖是一種特殊的表級鎖膨报,是由向包含自增列的表中插入數(shù)據(jù)的事務獲取的。
注意:自增鎖的生命周期就是一條語句,而不是事務
例如:
事務A先執(zhí)行现柠,不提交:
insert into t(name,no) values('dxy11',11),('dxy12',12);
事務B后執(zhí)行院领,不提交:
insert into t(name,no) values('dxy13',13);
事務A后執(zhí)行,不提交:
insert into t(name,no) values('dxy14',14);
如圖:事務A兩條語句插入的記錄够吩,id是不連續(xù)的
可以通過innodb_autoinc_lock_mode
設置自增鎖模式:
-
innodb_autoinc_lock_mode=0
(傳統(tǒng)鎖模型) -
innodb_autoinc_lock_mode=1
(連續(xù)鎖模型) -
innodb_autoinc_lock_mode=2
(交叉鎖模型)
更多關于自增鎖模式的描述比然,參考官網(wǎng)文檔
事務隔離性
我們知道事務有ACID四大特性,其中I就是代表的隔離性(isotation)
什么是事務的隔離性周循?
隔離性是指多個事務并發(fā)訪問同一個表數(shù)據(jù)時谈秫,一個事務不會受到另一個事務對數(shù)據(jù)操作的影響。
并發(fā)事務之間的干擾存在哪些問題鱼鼓?
假設表t有數(shù)據(jù):
id | name | no |
---|---|---|
1 | dxy1 | 10 |
- 場景1
事務A先執(zhí)行拟烫,不提交:
insert into t values(4,'dxy11',11);
事務B后執(zhí)行,不提交:
select * from t;
id | name | no |
---|---|---|
1 | dxy1 | 10 |
4 | dxy11 | 11 |
最后事務A回滾
此時迄本,事務B就讀取并處理了一條不存在的數(shù)據(jù)硕淑,這種情況就是臟讀
- 場景2
事務A先執(zhí)行,不提交:
select * from t where id >= 1;
id | name | no |
---|---|---|
1 | dxy1 | 10 |
事務B后執(zhí)行嘉赎,并提交:
update t set name = 'wuming' where id = 1;
事務A再次執(zhí)行:
select * from t where id >= 1;
id | name | no |
---|---|---|
1 | wuming | 10 |
此時置媳,事務A先后兩次查詢的記錄數(shù)不一致,這種情況就是不可重復讀
- 場景3
事務A先執(zhí)行公条,不提交:
select * from t where id = 1;
id | name | no |
---|---|---|
1 | dxy1 | 10 |
事務B后執(zhí)行拇囊,并提交:
insert into t values(4,'dxy11',11);
事務A再次執(zhí)行:
select * from t where id = 1;
id | name | no |
---|---|---|
1 | dxy1 | 10 |
4 | dxy11 | 11 |
此時,事務A先后兩次查詢的記錄數(shù)不一致靶橱,這種情況就是幻讀
因此寥袭,并發(fā)事務可能導致
- 臟讀:一個事務讀取了另一個事務未提交的數(shù)據(jù)
- 不可重復讀:一個事務讀取了另一個事務修改并提交的數(shù)據(jù)
- 幻讀:一個事務讀取了另一個事務增加或刪除并提交的數(shù)據(jù)
為了平衡并發(fā)事務的性能和數(shù)據(jù)一致性、可靠性关霸、再現(xiàn)性传黄,InnoDB支持了SQL:1992表準中全部四種隔離級別
讀未提交(Read Uncommitted)
Select語句不加鎖,事務可能讀取其他事務未提交的數(shù)據(jù)队寇,即臟讀膘掰。因此,這種級別的并發(fā)性最好佳遣,一致性最差-
讀已提交(Read Committed)
- 對于非加鎖讀识埋,每次都讀取最新的快照
- 對于加鎖讀、更新零渐、刪除窒舟,只對記錄加鎖而不對間隙加鎖,即這種級別不支持間隙鎖相恃,因此會導致幻讀
因此辜纲,RC的隔離級別會出現(xiàn)不可重復讀和幻讀
-
可重復讀(Repeatable Read)(默認級別)
- 對于非加鎖讀,同一個事務只在第一次查詢時生成快照拦耐。
- 對于加鎖讀耕腾、更新、刪除存在兩種情況
- 唯一索引精確匹配:只對數(shù)據(jù)加記錄鎖
- 范圍查詢或非唯一索引:使用間隙鎖和臨鍵鎖
因此杀糯,RR級別下可以完全避免幻影行和不可重復讀
序列化(Serializable)
最嚴格的隔離級別扫俺,所有select全部轉換成 select …… lock in share mode。這就導致固翰,如果其他事務修改記錄狼纬,那么select會被阻塞。
全表掃描在RC和RR級別下的加鎖策略是怎樣的呢骂际?
假設有一張表t:
id | name | no |
---|---|---|
1 | dxy1 | 10 |
2 | dxy2 | 20 |
- RR級別
事務A先執(zhí)行疗琉,不提交:
select * from t where no = 10 for update;
事務B后執(zhí)行:
select * from t where no = 20 for update
如圖:事務B被阻塞,因為RR級別下會對所有掃描的數(shù)據(jù)加鎖
- RC級別
事務A先執(zhí)行歉铝,不提交:
select * from t where no = 10 for update;
事務B后執(zhí)行:
select * from t where no = 20 for update
如圖:事務B不會被阻塞盈简,因為只會對no=10的行加鎖