從ActiveRecord看樂觀鎖

背景

樂觀鎖在并發(fā)控制中有非常廣泛的使用,在并發(fā)更新數(shù)據(jù)時避免了互斥鎖的使用,更新沖突較少時有著良好的性能表現(xiàn)。

在Rails中也集成了樂觀鎖的功能,由無所不能的ActiveRecord實現(xiàn)假颇。使用的方式及其簡單,只需要在對應的model中加入一個lock_version字段:

class CreateOrders < ActiveRecord::Migration[5.1]
  def change
    create_table :orders do |t|
      t.integer :lock_version, default: 0
      t.string :name
      t.integer :leave_count,default: 0
    end
  end
end

model數(shù)據(jù)更新的時候就會自動檢測數(shù)據(jù)版本骨稿,只有持有最新的lock_version數(shù)據(jù)的更新操作能成功笨鸡。

# p1 p2 持有同樣的數(shù)據(jù)版本
p1 = Order.find(1)
p2 = Order.find(1)

p1.name = "zhangsan"
p1.save # 成功, lock_version字段值會自動增加

p2.name = "cuihua"
p2.save # Raises an ActiveRecord::StaleObjectError

當持有舊版本的更新操作會得到一個ActiveRecord::StaleObjectError異常坦冠。具體可以查看官方文檔形耗。

提出疑問

包括官方文檔在內(nèi)的眾多資料只是提供了如何在Rails中使用樂觀鎖的方法,只是反復提到Rails會自動檢測數(shù)據(jù)版本是否過期辙浑,具體實現(xiàn)只字未提激涤。作為一名低端的搬磚工人,我對此感到非常失落判呕。即使是搬磚倦踢,也要知道搬的磚是怎么燒出來的。(主旨點明侠草,本文完)

所以辱挥,不想被拖拉機替代的,接下來我們一起探尋它是如何實現(xiàn)的边涕。這里有兩個問題需要思考:

  • 文檔中說的自動檢測是如何實現(xiàn)的晤碘?
  • 異常由誰產(chǎn)生,數(shù)據(jù)庫還是ActiveRecord功蜓?

稍微注意會發(fā)現(xiàn)這兩個問題的答案異常簡單:

 pry(main)> p1 = Order.find(1)
 pry(main)> p2 = Order.find(1)
 pry(main)> p1.leave_count = 9
=> 9
 pry(main)> p1.save
   (0.2ms)  BEGIN
  SQL (0.5ms)  UPDATE `orders` SET `leave_count` = 9, `updated_at` = '2018-07-15 06:47:28', `lock_version` = 1 WHERE `orders`.`id` = 1 AND `orders`.`lock_version` = 0
   (2.3ms)  COMMIT
=> true

pry(main)> p2.leave_count = 9
=> 9
pry(main)> p2.save
   (0.3ms)  BEGIN
  SQL (0.4ms)  UPDATE `orders` SET `leave_count` = 9, `updated_at` = '2018-07-15 06:47:53', `lock_version` = 1 WHERE `orders`.`id` = 1 AND `orders`.`lock_version` = 0
   (0.1ms)  ROLLBACK
ActiveRecord::StaleObjectError: Attempted to update a stale object: Order.
from /home/dog/.rvm/gems/ruby-2.5.1@study/gems/activerecord-5.1.6/lib/active_record/locking/optimistic.rb:95:in `_update_row'

ActiveRecord會創(chuàng)建一個巧妙的SQL:

UPDATE `orders` SET `leave_count` = 9, `updated_at` = '2018-07-15 06:47:28', `lock_version` = 1 WHERE `orders`.`id` = 1 AND `orders`.`lock_version` = 0

UPDATE本質(zhì)上是先SELECT到對應條件的數(shù)據(jù)园爷,再執(zhí)行數(shù)據(jù)更新。如果當前持有的lock_version過期了式撼,對應的數(shù)據(jù)行不會查詢到童社,也就不會有更新操作,數(shù)據(jù)庫會返回更新數(shù)據(jù)行為0端衰,也不會產(chǎn)生異常叠洗。

通過查看源碼,發(fā)現(xiàn)異常是由ActiveRecord拋出:

# 有刪減
def _update_row(attribute_names, attempted_action = "update")
  return super unless locking_enabled?

  affected_rows = self.class._update_record(
    attributes_with_values(attribute_names),
    self.class.primary_key => id_in_database,
    locking_column => previous_lock_value
  )

  if affected_rows != 1
    raise ActiveRecord::StaleObjectError.new(self, attempted_action)
  end
end

疑問揭曉旅东,非常簡單巧妙。

進一步思考

這種實現(xiàn)是利用了數(shù)據(jù)庫更新時的原子性十艾,例如在MySQL中會有行鎖抵代,這是一個悲觀鎖。那么這樣還能叫樂觀鎖嗎忘嫉? 翻一翻樂觀鎖的定義

Optimistic concurrency control (OCC) is a concurrency control method applied to transactional systems such as relational database management systems and software transactional memory. OCC assumes that multiple transactions can frequently complete without interfering with each other. While running, transactions use data resources without acquiring locks on those resources. Before committing, each transaction verifies that no other transaction has modified the data it has read. If the check reveals conflicting modifications, the committing transaction rolls back and can be restarted.[1] Optimistic concurrency control was first proposed by H.T. Kung and John T. Robinson.

大意是在并發(fā)控制時不會有鎖產(chǎn)生荤牍,在提交時會去檢測數(shù)據(jù)是否已經(jīng)被修改案腺,如沒有則直接更新提交,否則就回滾康吵。這是一種理念劈榨,看看具體的一種實現(xiàn)CAS(比較交換)

CAS 的全稱為compare and swap, 可以這樣理解: A(目標數(shù)據(jù)的地址)晦嵌, currentVersion(位于A的數(shù)據(jù)的最新版本號)同辣,holdVersion(更新者持有的數(shù)據(jù)版本號), B(新數(shù)據(jù))惭载。 如果holdVersion == currentVersion旱函,就將A地址的數(shù)據(jù)更新為B,否則更新失敗描滔。

這樣來看棒妨,ActiveRecord中的實現(xiàn)滿足CAS的理念,可以說是非常簡潔完美的實現(xiàn)含长。

ActiveRecord中確實沒有產(chǎn)生鎖券腔,但是它確實是依賴于數(shù)據(jù)庫更新時的鎖,也就是說有鎖的參與拘泞,這個怎么理解颅眶?(不是無鎖嗎)

實際上,幾乎所有CAS都是由CPU指令實現(xiàn)田弥,由CPU保證執(zhí)行的原子性涛酗,如果是單核CPU的話,指令反正可以理解為是一條一條順序執(zhí)行的偷厦,不會有沖突商叹。

但是在多CPU的情況下呢? 如何保證指令中比較交換等步驟的原子性只泼? 實際上剖笙,經(jīng)查閱資料,這種情況下CPU硬件級別也會有一個鎖请唱,保證CAS指令執(zhí)行的原子性弥咪,還是有鎖的參與。 不過層級不一樣十绑,這是更加底層的實現(xiàn)聚至, 越底層的鎖,開銷越小本橙,上層并不知曉扳躬。

所以,對于樂觀鎖的理解,需要分層來看贷币。 在ActiveRecord這種應用層來說击胜,它所的實現(xiàn)的就是樂觀鎖。只要當前層級的實現(xiàn)中沒有鎖役纹,且滿足樂觀鎖的理念偶摔,那么它就可以認為是樂觀鎖,盡管它底層可能依賴的是悲觀鎖促脉。

有沒有徹徹底底的樂觀鎖呢辰斋?

使用場景

從上面可以知道,當數(shù)據(jù)版本失效時去更新嘲叔,會得到一個異常亡呵。這在代碼中需要寫一個異常捕獲來捕捉這個特定的異常,以便進一步進行選擇是重試還是直接返回失敗硫戈。

如果數(shù)據(jù)會頻繁更新锰什,則數(shù)據(jù)沖突的可能性加大,可能會頻繁重試丁逝。當業(yè)務邏輯讀多寫少汁胆,或?qū)χ卦嚥幻舾校抑卦嚨拇鷥r較小時霜幼,樂觀鎖也許是一種較好的選擇嫩码。

首發(fā)這里

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市罪既,隨后出現(xiàn)的幾起案子铸题,更是在濱河造成了極大的恐慌,老刑警劉巖琢感,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丢间,死亡現(xiàn)場離奇詭異,居然都是意外死亡驹针,警方通過查閱死者的電腦和手機烘挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柬甥,“玉大人饮六,你說我怎么就攤上這事】疗眩” “怎么了卤橄?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撤防。 經(jīng)常有香客問我虽风,道長棒口,這世上最難降的妖魔是什么寄月? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任辜膝,我火速辦了婚禮,結果婚禮上漾肮,老公的妹妹穿的比我還像新娘厂抖。我一直安慰自己,他們只是感情好克懊,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布忱辅。 她就那樣靜靜地躺著,像睡著了一般谭溉。 火紅的嫁衣襯著肌膚如雪墙懂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天扮念,我揣著相機與錄音损搬,去河邊找鬼。 笑死柜与,一個胖子當著我的面吹牛巧勤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弄匕,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼颅悉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迁匠?” 一聲冷哼從身側響起剩瓶,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎城丧,沒想到半個月后延曙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡芙贫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年搂鲫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磺平。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡魂仍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拣挪,到底是詐尸還是另有隱情擦酌,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布菠劝,位于F島的核電站赊舶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笼平,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一园骆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寓调,春花似錦锌唾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痛悯,卻和暖如春余黎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背载萌。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工惧财, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炒考。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓可缚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親斋枢。 傳聞我的和親對象是個殘疾皇子帘靡,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容