1樓:愛可生
術式之後皆為邏輯,一切皆為需求和實現。希望此文能從需求、現狀和解決方式的角度幫大家理解隔離級別。
隔離級別的產生
在串型執行的條件下,資料修改的順序是固定的、可預期的結果,但是併發執行的情況下,資料的修改是不可預期的,也不固定,為了實現資料修改在併發執行的情況下得到乙個固定、可預期的結果,由此產生了隔離級別。
所以隔離級別的作用是用來平衡資料庫併發訪問與資料一致性的方法。
事務的4種隔離級別
READ UNCOMMITTED 未提交讀,可以讀取未提交的資料。
READ COMMITTED 已提交讀,對於鎖定讀(select
with
for update 或者 for share)、update 和 delete 語句,
InnoDB 僅鎖定索引記錄,而不鎖定它們之間的間隙,因此允許在鎖定的記錄旁邊自由插入新記錄。
Gap locking 僅用於外來鍵約束檢查和重複鍵檢查。
REPEATABLE READ 可重複讀,事務中的一致性讀取讀取的是事務第一次讀取所建立的快照。
SERIALIZABLE序列化
在了解了 4 種隔離級別的需求後,在採用鎖控制隔離級別的基礎上,我們需要了解加鎖的物件(資料本身&間隙),以及了解整個資料範圍的全集組成。
資料範圍全集組成
SQL 語句根據條件判斷不需要掃瞄的資料範圍(不加鎖);
SQL 語句根據條件掃瞄到的可能需要加鎖的資料範圍;
以單個資料範圍為例,資料範圍全集包含:(資料範圍不一定是連續的值,也可能是間隔的值組成)
1. 資料已經填充了整個資料範圍:(被完全填充的資料範圍,不存在資料間隙)
整形,對值具有唯一約束條件的資料範圍 1~5 ,
已有資料1、2、3、4、5,此時資料範圍已被完全填充;
整形,對值具有唯一約束條件的資料範圍 1 和 5 ,
已有資料1、5,此時資料範圍已被完全填充;
2. 資料填充了部分資料範圍:(未被完全填充的資料範圍,是存在資料間隙)
整形的資料範圍 1~5 ,
已有資料 1、2、3、4、5,但是因為沒有唯一約束,
所以資料範圍可以繼續被 1~5 的資料重複填充;
整形,具有唯一約束條件的資料範圍 1~5 ,
已有資料 2,5,此時資料範圍未被完全填充,還可以填充 1、3、4 ;
3. 資料範圍內沒有任何資料(存在間隙)
如下:整形的資料範圍 1~5 ,資料範圍內當前沒有任何資料。
在了解了資料全集的組成後,我們再來看看事務併發時,會帶來的問題。
無控制的併發所帶來的問題
併發事務如果不加以控制的話會帶來一些問題,主要包括以下幾種情況。
1. 範圍內已有資料更改導致的:
更新丟失:當多個事務選擇了同一行,然後基於最初選定的值更新該行時,
由於每個事物不知道其他事務的存在,最後的更新就會覆蓋其他事務所做的更新;
髒讀:乙個事務正在對一條記錄做修改,這個事務完成並提交前,這條記錄就處於不一致狀態。
這時,另外乙個事務也來讀取同一條記錄,如果不加控制,
第二個事務讀取了這些「髒」資料,並據此做了進一步的處理,就會產生提交的資料依賴關係。
這種現象就叫「髒讀」。
2. 範圍內資料量發生了變化導致:
幻讀:乙個事務按相同的查詢條件重新讀取以前檢索過的資料,
卻發現其他事務插入了滿足其查詢條件的新資料,這種現象稱為「幻讀」。
可以簡單的認為滿足條件的資料量變化了。
因為無控制的併發會帶來一系列的問題,這些問題會導致無法滿足我們所需要的結果。因此我們需要控制併發,以實現我們所期望的結果(隔離級別)。
MySQL 隔離級別的實現
InnoDB 通過加鎖的策略來支援這些隔離級別。
行鎖包含:
Record Locks
索引記錄鎖,索引記錄鎖始終鎖定索引記錄,即使表中未定義索引,
這種情況下,InnoDB 建立乙個隱藏的聚簇索引,並使用該索引進行記錄鎖定。
Gap Locks
間隙鎖是索引記錄之間的間隙上的鎖,或者對第一條記錄之前或者最後一條記錄之後的鎖。
間隙鎖是效能和併發之間權衡的一部分。
對於無間隙的資料範圍不需要間隙鎖,因為沒有間隙。
Next-Key Locks
索引記錄上的記錄鎖和索引記錄之前的 gap lock 的組合。
假設索引包含 10、11、13 和 20。
可能的next-key locks包括以下間隔,其中圓括號表示不包含間隔端點,方括號表示包含端點:
(負無窮大, 10]
(10, 11]
(11, 13]
(13, 20]
(20, 正無窮大)
對於最後乙個間隔,next-key將會鎖定索引中最大值的上方,
左右滑動進行檢視
"上確界"偽記錄的值高於索引中任何實際值。
上確界不是乙個真正的索引記錄,因此,實際上,這個 next-key 只鎖定最大索引值之後的間隙。
基於此,當獲取的資料範圍中,資料已填充了所有的資料範圍,那麼此時是不存在間隙的,也就不需要 gap lock。
對於資料範圍內存在間隙的,需要根據隔離級別確認是否對間隙加鎖。
預設的 REPEATABLE READ 隔離級別,為了保證可重複讀,除了對資料本身加鎖以外,還需要對資料間隙加鎖。
READ COMMITTED 已提交讀,不匹配行的記錄鎖在 MySQL 評估了 where 條件後釋放。
對於 update 語句,InnoDB 執行 "semi-consistent" 讀取,這樣它會將最新提交的版本返回到 MySQL,
以便 MySQL 可以確定該行是否與 update 的 where 條件相匹配。
現在我們來驗證以下 MySQL 對於隔離級別的實現是否符合預期。
場景演示
資料準備:
CREATE TABLE `t` (
`a`int(11) NOT NULL,
`b`int(11) DEFAULT NULL,
`c`int(11) DEFAULT NULL,
`d`int(11) DEFAULT NULL,
`e`int(11) DEFAULT NULL,
PRIMARY KEY (`a`),
UNIQUE KEY `c` (`c`,`d`,`e`),
KEY `b` (`b`)
);insert into t values (1,2,1,2,3),(2,2,1,2,4),(3,3,1,2,5),(4,4,2,3,4),(5,5,3,3,5),(6,7,4,5,5),(7,10,5,6,7),(8,13,5,3,1),(9,20,6,9,10),(10,23,7,7,7) ;
左右滑動進行檢視
說明:session A
SQL1
SQL2
....
session B
SQLN
SQLN+1
...上面的語句都是按照時間順序排序。
所有的測試資料都是基於資料準備好的原始資料,每次測試完成,請自我修復現場。
場景一
rr 模式 + 唯一索引篩選:
資料:資料範圍已被資料完全填充(1)
已有資料的更改
session A
start transaction ;
update t set a=11
where a=3持有Record Locks(a=3)
session B
update t set a=12
where a=3等待a=3的Record Locks,
直到session A將其釋放/等鎖超時
資料範圍有資料,但未被完全填充(2)
已有資料的更改
session A
start transaction ;
update t set b=11
where a>=10 ; # 持有Record Locks (a=10),
有Next-Key
Locks(a>10)
session B
update t set b=12
where a=10等待a=10的Record Locks,
直到session A將其釋放/等鎖超時
間隙的填充
session A
start transaction ;
update t set b=11
where a>=10 ; # 持有Record Locks (a=10),
有Next-Key
Locks(a>10)
session B
insert into t values(11,1,21,1,1); # 插入等待,
因為存在a>10的Next-Key
Locks
資料範圍內沒有資料(3)
間隙的填充
session A
start transaction ;
update t set b=11
where a>=100
and a<=200存在Gap Locks
session B
insert into t values(150,1,21,1,1); # 插入等待,
因為存在(10,無窮大)的Gap
Locks,
a最大的乙個值是10
insert into t values(11,1,1,2,2插入等待,
因為存在(10,無窮大)的Gap. Locks,
a最大的乙個值是10
update t set b=8
where a=10更改成功,
因為session A並未持有Record
Locks
update t set b=23
where a=10恢復現場
左右滑動進行檢視
場景二
rc 模式 + 唯一索引篩選:
資料:資料範圍已被資料完全填充(4)
已有資料的更改
session A
start transaction ;
update t set a=11
where a=3持有Record Locks(a=3)
session B
update t set a=12
where a=3等待a=3的Record Locks,
直到session A將其釋放/等鎖超時
資料範圍有資料,但未被完全填充(5)
已有資料的更改
session A
start transaction ;
update t set b=11
where a>=10 ; # 持有Record Locks (a=10),
無Next-Key
Locks(a>10)
session B
update t set b=12
where a=10等待a=10的Record Locks,
直到session A將其釋放/等鎖超時
間隙的填充
session A
start transaction ;
update t set b=11
where a>=10 ; # 持有Record Locks (a=10),
無Next-Key
Locks(a>10)
session B
insert into t values(11,1,21,1,1); # 插入成功
delete
from t where a=11恢復現場
資料範圍內沒有資料(6)
間隙的填充
session A
start transaction ;
update t set b=11
where a>=100
and a<=200 ; # 無對應的索引,
所以無Record
Locks,
無Next-Key
Locks
session B
insert into t values(150,1,21,1,1插入成功
delete
from t where a=150恢復現場
左右滑動進行檢視
場景三
rr 模式 + 非唯一索引篩選:(非唯一索引篩選的情況下,不存在資料完全填充的場景)
資料:資料範圍有資料,但未被完全填充(7)
已有資料的更改**
session A
start transaction ;
update t set e=7
where b=2
and e=4 ; # 獲取非唯一索引,命中b=2的索引值,
b=2的索引值對應多條記錄。
此時有Record
Locks,加在非唯一索引上
session B
update t set e=7
where b=2
and e=10 ; # session A已獲取了b=2的 Record Locks ,
session B等待session A將其釋放/等鎖超時
即使該條件命中的是空記錄
間隙的填充
session A
start transaction ;
update t set d=6
where b>=5
and b<=7 ; # 獲取了b=5和 b=7的 Record Locks,
和(5,7)的Gap
Locks
session B
insert into t values(11,6,11,12,13) ; # 插入b=6的記錄,插入等待,
存在b的資料範圍(5,7)的 Gap
Locks。
資料範圍內沒有資料(8)
間隙的填充
session A
start transaction ;
update t set b=100
where b>=120b>=120命中了空的資料範圍,
所以無Record
Locks,
但存在(23,正無窮)的Gap
Locks
session B
insert into t values(200,200,200,200,200) ; # 插入等待,
等待(23,正無窮)的Gap
Locks的釋放
insert into t values(100,24,3,4,60插入等待,
等待(23,正無窮)的Gap locks的釋放
update t set b=12
where b=23 ; # 更新成功,
因為session A並未獲取Record
Locks,
所以不會阻止已有行的更新
update t set b=23
where b=12 ; # 恢復現場
左右滑動進行檢視
場景四
rc 模式 + 非唯一索引篩選:(非唯一索引篩選的情況下,不存在資料完全填充的場景)
資料:資料範圍有資料,但未被完全填充(9)
已有資料的更改
session A
start transaction ;
update t set e=7
where b=2
and e=4 ; # 獲取非唯一索引,命中b=2的索引值,
b=2的索引值對應多條記錄。
此時有Record
Locks,加在非唯一索引上
session B
update t set e=7
where b=2
and e=10 ; # session A已獲取了b=2的Record Locks ,
session B等待session A將其釋放/等鎖超時,
即使該條件命中的是空記錄
間隙的填充
session A
start transaction ;
update t set d=6
where b>=5
and b<=7 ; # 獲取了b=5和 b=7的 Record Locks,
無Next-Key
Locks
session B
insert into t values(11,6,11,12,13) ; # 插入成功,因為無 Next-Key Locks
資料範圍內沒有資料(10)
間隙的填充
session A
start transaction ;
update t set b=100
where b>=120b>=120命中了空的資料範圍,
所以無Record
Locks,
無Next-Key
Locks
session B
insert into t values(200,200,200,200,200) ; # 插入成功,因為無Next-Key Locks
左右滑動進行檢視
場景五
rr 模式 + 無索引篩選:(無索引篩選的情況下,不存在資料完全填充的場景)
資料:資料範圍有資料,但未被完全填充(11)
已有資料的更改
session A
start transaction ;
update t set c=100
where d=2d=2 對應a=1、a=2、a=3的記錄,
因為where條件未使用索引,故只能全表掃瞄,
並對所有行加Record
Locks,
並且在間隙中加上Gap
Locks
session B
update t set b=100
where a=1等待session A獲取的a=1的X鎖
間隙的填充
因為where條件並未使用索引,所以最終加鎖都回歸到了對基表的聚簇索引加鎖,
但是where條件未使用索引,自然更無唯一約束,
所以邏輯上可以認為除where命中的行以外的其他範圍都是間隙。
而實際上因為通過聚簇索引加鎖,所以在每兩個聚簇索引之間才會加上Gap
Locks。
session A
start transaction ;
update t set c=100
where d=2d=2 對應a=1、a=2、a=3的記錄,
因為where條件未使用索引,故只能全表掃瞄,
並對所有聚簇索引加Record
Locks,
並且加上Next-Key
Locks
session B
update t set b=50
where a=8等待session A獲取的a=8的X鎖
insert into t values(110,30,30,40,500); # 等待Next-Key Locks
資料範圍內沒有資料(12)
間隙的填充
因為where條件並未使用索引,所以最終加鎖都回歸到了對基表的聚簇索引加鎖,
但是where條件未使用索引,自然更無唯一約束,
所以邏輯上可以認為除where命中的行以外的其他範圍都是間隙。
而實際上因為通過聚簇索引加鎖,所以在每兩個聚簇索引之間才會加上Gap
Locks。
session A
start transaction ;
update t set c=100
where d=20因為where條件未使用索引,
故只能全表掃瞄,
並對所有聚簇索引加Record
Locks,
並且加上Next-Key
Locks
session B
update t set b=50
where a=8等待session A獲取的a=8的X鎖
insert into t values (100,3,100,20,4) ; # 等待Next-Key Locks
左右滑動進行檢視
場景六
rc 模式 + 無索引篩選:(無索引篩選的情況下,不存在資料完全填充的場景)
資料:資料範圍有資料,但未被完全填充(13)
已有資料的更改
session A
start transaction ;
update t set c=100
where d=2對應a=1、a=2、a=3的記錄,
因為where條件未使用索引,故只能全表掃瞄,
並對a=1、a=2、a=3聚簇索引加Record
Locks,
但是無Next-Key
Locks
session B
update t set b=100
where a=1等待session A獲取的a=1的Record Locks
間隙的填充
因為where條件並未使用索引,所以最終加鎖都回歸到了對基表的聚簇索引加鎖,
但是where條件未使用索引,自然更無唯一約束,
所以邏輯上可以認為除where命中的行以外的其他範圍都是間隙。
而實際上因為通過聚簇索引加鎖,所以在每兩個聚簇索引之間才會加上Gap
Locks。
session A
start transaction ;
update t set c=100
where d=2對應a=1、a=2、a=3的記錄,
因為where條件未使用索引,故只能全表掃瞄,
並對a=1、a=2、a=3聚簇索引加Record
Locks,
但是無Next-Key
Locks
session B
update t set b=50
where a=8成功,因為a=8並未被session A加鎖
insert into t values(110,30,30,2,500); # 成功,因為不存在Gap Locks &
next-Key
Locks
資料範圍內沒有資料(14)
間隙的填充*
session A
start transaction ;
update t set c=100
where d=20無Record Locks,
也無Next-Key
Locks
session B
insert into t values (100,3,100,20,4) ; # 成功
delete
from t where a=100恢復現場
左右滑動進行檢視
總結&延展:
唯一索引存在唯一約束,所以變更後的資料若違反了唯一約束的原則,則會失敗。
當 where 條件使用二級索引篩選資料時,會對二級索引命中的條目和對應的聚簇索引都加鎖;所以其他事務變更命中加鎖的聚簇索引時,都會等待鎖。
行鎖的增加是一行一行增加的,所以可能導致併發情況下死鎖的發生。
例如,在 session A 對符合條件的某聚簇索引加鎖時,可能 session B 已持有該聚簇索引的 Record Locks,而 session B 正在等待 session A 已持有的某聚簇索引的 Record Locks。
session A 和 session B 是通過兩個不相干的二級索引定位到的聚簇索引。
session A 通過索引 idA,session B通過索引 idB 。
當 where 條件獲取的資料無間隙時,無論隔離級別為 rc 或 rr,都不會存在間隙鎖。
比如通過唯一索引獲取到了已完全填充的資料範圍,此時不需要間隙鎖。
間隙鎖的目的在於阻止資料插入間隙,所以無論是通過 insert 或 update 變更導致的間隙內資料的存在,都會被阻止。
rc 隔離級別模式下,查詢和索引掃瞄將禁用 gap locking,此時 gap locking 僅用於外來鍵約束檢查和重複鍵檢查(主要是唯一性檢查)。
rr 模式下,為了防止幻讀,會加上 Gap Locks。
事務中,SQL 開始則加鎖,事務結束才釋放鎖。
就鎖型別而言,應該有優化鎖,鎖公升級等,例如rr模式未使用索引查詢的情況下,是否可以直接公升級為表鎖。
就鎖的應用場景而言,在回放場景中,如果確定事務可併發,則可以考慮不加鎖,加快回放速度。
鎖只是併發控制的一種粒度,只是乙個很小的部分:
從不同場景下是否需要控制併發,(已知無交集且有序的資料的變更,MySQL 的 MTS 相同前置事務的多事務併發回放)
併發控制的粒度,(鎖是一種邏輯粒度,可能還存在物理層和其他邏輯粒度或方式)
相同粒度下的優化,(鎖本身存在優化,如IX、IS型別的優化鎖)
粒度載入的安全&效能(如獲取行鎖前,先獲取頁鎖,頁鎖在執行獲取行鎖操作後即釋放,無論是否獲取成功)等多個層次去思考併發這玩意。
Mysql事務隔離級別的設定是否會與表鎖 行鎖衝突
李晨曦 MyISAM不支援事物,所以這些隔離級別是沒有意義的。然後再說一下這些隔離級別和鎖之間的關係 如在InnoDB中,支援行級鎖 首先乙個事物由begin開始,由commit 成功執行 或rollback 執行失敗,需要回滾 終止,所以考慮事物問題的時候要考慮到事物的整個生命週期,對同一行資料的...
有什麼平價好用的隔離和防曬
TEN 防曬的話 uv 忘記中文叫什麼了,好像是三個字 量多不油膩塗上去沒有負擔感,也不假白,就感覺馬上能成膜的那種不會搓泥隔離的話紐西之謎吧 我自己還沒用過 感覺之前風挺大的,也沒爆出什麼特別難用的地方,有時候做活動買的話價效比也挺高的 Dobby 我覺得防曬不能用太便宜的不過看個人我是輕微敏感肌...
情感隔離和迴避依戀之間有什麼關係?
我諮詢了很多有過情感隔離經歷的迴避型依戀人格,Ta們在受到情感上的傷害時,會採用一直策略,壓抑被傷害的痛苦,這種痛苦Ta承受不了,所以會抑制,也同時把對方的感情也抑制掉了,就是感受不到對對方的愛和感情。年紀小的人,容易自閉,可能三個月或者更久,年紀大的人可能內心稍微強大一點,也會做情感隔離,但是不會...