mysql 解決超賣問題的鎖分析

    解決超賣問題,常見的方式,利用redis 的原子性去遞減;利用隊列,隊列入隊計數。或者直接打到mysql 層。由mysql 保證不超賣,有幾個玩法。利用屬性不同,挺有意思,記錄下。mysql

該文章後續仍在不斷的更新修改中, 請移步到原文地址http://www.dmwan.cc/?p=139&preview=trueredis

    首先,mysql 隔離級別是RR,或者是串行,可是不可能用串行,太慢。sql

    其次,爲何會出現超賣問題?由於這個select and update 是個非原子操做,是兩步操做。性能

    最後,怎麼解決這個問題? 就在原子性上作文章,好比redis 的 lua 封裝,將指令封裝成原子操做。或者mysql 的互斥鎖,再或者,乾脆容許超賣,業務層作二次檢查。lua

   第一種,mysql 在select 的時候不加互斥鎖,這個時候的作法:隊列

        1:開啓事務事務

        2:查詢庫存,並顯示的設置寫鎖(排他鎖):SELECT * FROM table_name WHERE …get

        3:生成訂單it

        4:去庫存,隱示的設置寫鎖(排他鎖):UPDATE goods SET counts = counts – 1 WHERE id = 1io

        5:commit,釋放鎖

    這裏提下mysql 的RC 和RR 區別,mysql 是如何實現可重複讀的? RR隔離級別是在事務開始時刻,確切地說是第一個讀操做建立read view的;RC隔離級別是在語句開始時刻建立read view的。一個read view 能夠理解成一份快照(底層只是事務id 列表),因此,RC 隔離級別下,屢次讀可能受其餘事務commit 致使讀取數據不一致。

    那麼 RR 級別下,select 和 update 的時候, 數據是一個version的,可是上面作法的問題,就在於,幾個事務begin 後,都讀到數據爲1,就開始 update ,就會致使數據最終爲負數,這種方式,避不了爲負數。

    咱們能夠作個二次容錯,檢查update 後數據爲負數後,直接回滾此次事務便可。

    第二種方式是select 的時候就加行鎖,select for update ,直接鎖住這條記錄,不讓其餘事務讀。因此對這個數據,都成串行,這個缺點就是會影響性能,可是不會出現超賣的狀況了。

         1:開啓事務

        2:查詢庫存,並顯示的設置寫鎖(排他鎖):SELECT * FROM table_name WHERE … FOR UPDATE

        3:生成訂單

        4:去庫存,隱示的設置寫鎖(排他鎖):UPDATE goods SET counts = counts – 1 WHERE id = 1

        5:commit,釋放鎖。

    第三種,試着給select 加讀鎖,這種作法是不行,會出現死鎖,什麼狀況會死鎖呢?事務1,2同時加讀鎖,事務1加寫鎖等事務2的寫鎖,事務2的加寫鎖等事務1的讀鎖,相互等待,陷入死鎖。那爲啥 select for update 不會這樣,由於mysql 互斥鎖是可重入鎖啊。

相關文章
相關標籤/搜索