- 樂觀鎖
如一個金融系統(tǒng)须肆,當某個操作員讀取用戶的數(shù)據匿乃,并在讀出的用戶數(shù)據的基礎上進行修改時(如更改用戶帳戶余額),
如果采用悲觀鎖機制豌汇,也就意味著整個操作過程中(從操作員讀出數(shù)據幢炸、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間)拒贱,數(shù)據庫記錄始終處于加鎖狀態(tài)宛徊,可以想見,如果面對幾百上千個并發(fā)逻澳,這樣的情況將導致怎樣的后果闸天。
樂觀鎖機制在一定程度上解決了這個問題。
樂觀鎖斜做,大多是基于數(shù)據版本 ( Version )記錄機制實現(xiàn)苞氮。何謂數(shù)據版本?
即為數(shù)據增加一個版本標識瓤逼,在基于數(shù)據庫表的版本解決方案中笼吟,一般是通過為數(shù)據庫表增加一個 “version” 字段來實現(xiàn)。
讀取出數(shù)據時霸旗,將此版本號一同讀出贷帮,之后更新時,對此版本號加一诱告。
此時撵枢,將提交數(shù)據的版本數(shù)據與數(shù)據庫表對應記錄的當前版本信息進行比對,
如果提交的數(shù)據版本號大于數(shù)據庫表當前版本號,則予以更新锄禽,否則認為是過期數(shù)據潜必。
對于上面修改用戶帳戶信息的例子而言,假設數(shù)據庫中帳戶信息表中有一個 version 字段沟绪,當前值為 1 刮便;而當前帳戶余額字段( balance )為 $100 。
- 操作員 A 此時將其讀出( version=1 )绽慈,并從其帳戶余額中扣除 $50( $100-$50 )恨旱。
- 在操作員 A 操作的過程中,操作員B 也讀入此用戶信息( version=1 )坝疼,并從其帳戶余額中扣除 $20 ( $100-$20 )搜贤。
- 操作員 A 完成了修改工作,將數(shù)據版本號加一( version=2 )钝凶,
連同帳戶扣除后余額( balance=$50 )仪芒,提交至數(shù)據庫更新,此時由于提交數(shù)據版本大于數(shù)據庫記錄當前版本耕陷,數(shù)據被更新掂名,數(shù)據庫記錄 version 更新為 2 。 - 操作員 B 完成了操作哟沫,也將版本號加一( version=2 )試圖向數(shù)據庫提交數(shù)據( balance=$80 )饺蔑,但此時比對數(shù)據庫記錄版本時發(fā)現(xiàn),操作員 B 提交的數(shù)據版本號為 2 嗜诀,數(shù)據庫記錄當前版本也為 2 猾警,不滿足 “ 提交版本必須大于記錄當前版本才能執(zhí)行更新 “ 的樂觀鎖策略,因此隆敢,操作員 B 的提交被駁回发皿。
這樣,就避免了操作員 B 用基于 version=1 的舊數(shù)據修改的結果覆蓋操作員A 的操作結果的可能拂蝎。
轉載自樂觀鎖
另外參考:一分鐘教你知道樂觀鎖和悲觀鎖的區(qū)別
- CAS:compare and swap
CAS的含義是穴墅,我認為V的值應該為A,如果是温自,那么將V的值更新為B玄货,否則不修改并告訴V的值實際為多少。(Java并發(fā)編程實戰(zhàn)P263)
附上java.util.concurrent.atomic.AtomicLong中的源碼
public final long getAndAccumulate(long x,
LongBinaryOperator accumulatorFunction) {
long prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsLong(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
還有兩個鏈接CAS(Compare and Swap)理解捣作,非阻塞同步算法與CAS(Compare and Swap)無鎖算法
- MVCC:multiversion concurrency control
我們知道誉结,mysql的innodb采用的是行鎖鹅士,而且采用了多版本并發(fā)控制來提高讀操作的性能券躁。
什么是多版本并發(fā)控制呢 ?其實就是在每一行記錄的后面增加兩個隱藏列,記錄創(chuàng)建版本號和刪除版本號也拜,
而每一個事務在啟動的時候以舒,都有一個唯一的遞增的版本號。
1慢哈、在插入操作時 : 記錄的創(chuàng)建版本號就是事務版本號蔓钟。
比如我插入一條記錄, 事務id 假設是1 ,那么記錄如下:也就是說卵贱,創(chuàng)建版本號就是事務版本號滥沫。
id | name | create version | delete version |
---|---|---|---|
1 | test | 1 |
2、在更新操作的時候键俱,采用的是先標記舊的那行記錄為已刪除兰绣,并且刪除版本號是事務版本號,然后插入一行新的記錄的方式编振。
比如缀辩,針對上面那行記錄,事務Id為2 要把name字段更新
update table set name= 'new_value' where id=1;
id | name | create version | delete version |
---|---|---|---|
1 | test | 1 | 2 |
1 | new_value | 2 |
3踪央、刪除操作的時候臀玄,就把事務版本號作為刪除版本號。比如
delete from table where id=1;
id | name | create version | delete version |
---|---|---|---|
1 | new_value | 2 | 3 |
4畅蹂、查詢操作:
從上面的描述可以看到健无,在查詢時要符合以下兩個條件的記錄才能被事務查詢出來:
- 刪除版本號 大于 當前事務版本號,就是說刪除操作是在當前事務啟動之后做的魁莉。
- 創(chuàng)建版本號 小于或者等于 當前事務版本號 睬涧,就是說記錄創(chuàng)建是在事務中(等于的情況)或者事務啟動之前。
這樣就保證了各個事務互不影響旗唁。從這里也可以體會到一種提高系統(tǒng)性能的思路畦浓,就是:
通過版本號來減少鎖的爭用。
另外检疫,只有read-committed和 repeatable-read 兩種事務隔離級別才能使用MVCC
read-uncommited由于是讀到未提交的讶请,所以不存在版本的問題
而serializable 則會對所有讀取的行加鎖。
轉載自mysql的mvcc(多版本并發(fā)控制)
屎媳,另外參考innodb 多版本并發(fā)控制原理詳解