漫談資料庫鎖,漫談資料庫

來源:互聯網
上載者:User

漫談資料庫鎖,漫談資料庫
前言

       前段時間線上上和項目當中遇到了很多關於用mysql的GET_LOCK()函數擷取鎖導致的一些問題,主要有兩類問題:

        1、一個串連中不能同時擷取兩把鎖,因為擷取後一個的時候會自動釋放前面一把鎖;另外如果獲得鎖或者釋放鎖所使用的connection不一樣,假如獲得鎖的connection被串連池回收了,也可能會導致第一把鎖自動釋放,最終導致你的業務還沒有處理完,別人也同時處理相同的業務,最終導致業務的不一致。

        2、為瞭解決第一個問題,很多同學把獲得鎖和釋放鎖放在一個事務當中,在獲得鎖和釋放鎖中間做了大量的耗時的商務邏輯(甚至這些商務邏輯根本就沒有涉及到資料庫操作),例如調用了幾個外部系統的tr服務,或者批量處理功能;導致一個串連被佔用過長時間,假如並發量一大,不但會導致介面效能下降,還會導致資料庫連接不夠,直接導致系統大面積癱瘓,非常危險。

以下是mysql官方文檔對GET_LOCK()函數的詳細說明,如果大家仔細並研究過這個函數,我相信大家一定不會輕易使用GET_LOCK()的情境。

        加鎖:"SELECTGET_LOCK('{$key}', {$timeout}) AS get_lock";
        解鎖:"SELECTRELEASE_LOCK('{$key}') AS release_lock";

  • GET_LOCK(str,timeout)

        設法使用字串str 給定的名字得到一個鎖, 逾時為timeout 秒。若成功得到鎖,則返回 1,若操作逾時則返回0 (例如,由於另一個用戶端已提前封鎖了這個名字 ),若發生錯誤則返回NULL (諸如缺乏記憶或線程mysqladmin kill 被斷開 )。假如你有一個用GET_LOCK()得到的鎖,當你執行RELEASE_LOCK()或你的串連斷開(正常或非正常)時,這個鎖就會解除。

        這個函數可用於執行應用程式鎖或類比記錄鎖定。名稱被鎖定在伺服器範圍內。假如一個名字已經被一個用戶端封鎖,GET_LOCK() 會封鎖來自另一個用戶端申請封鎖同一個名字的任何請求。這使對一個封鎖名達成協議的用戶端使用這個名字合作執行建議鎖。然而要知道它也允許不在一組合作用戶端中的一個用戶端封鎖名字,不論是故意的還是非故意的,這樣阻止任何合作中的用戶端封鎖這個名字。一個減少這種情況發生的辦法就是使用資料庫特定的或應用程式特定的封鎖名。例如,使用db_name.str或 app_name.str 形式的封鎖名。

       mysql> SELECT GET_LOCK('lock1',10);

               -> 1

       mysql> SELECT IS_FREE_LOCK('lock2');

               -> 1

       mysql> SELECT GET_LOCK('lock2',10);

              -> 1

       mysql> SELECT RELEASE_LOCK('lock2');

               -> 1

       mysql> SELECT RELEASE_LOCK('lock1');

               -> NULL

        注意,第二個 RELEASE_LOCK()調用返回 NULL ,原因是鎖'lock1' 被第二個GET_LOCK()調用解開。

使用另外一種辦法解決和避免上面的問題

        mysql的GET_LOCK()函數除了上面的那個問題之外,還會存在以下問題:

  • 系統嚴重依賴了資料庫類型,也就是嚴重依賴了mysql,假如以後資料庫需要遷移到oracle或者其他資料庫,整個應用程式層的代碼全部都需要修改,這不是一個好的設計方案。

所以為瞭解決這些問題我們必須採用其他的解決方案,目前主要有兩種解決方案:

        1、採用其他類似memcached之類的全域緩衝鎖

        2、採用CAS思想的基於資料庫的樂觀鎖,但不需要依賴資料庫類型

        但有時候,並不是所有的系統都需要tair或者memcached之類的緩衝,如果僅僅為了鎖而申請緩衝,從而導致系統嚴重依賴了一個外部環境,所以並不經濟划算,所以下面主要著重介紹第二種方案。

        其實我們可以為每個庫設計一張非常簡單的表,例如表名叫做lock:

name

version

gmt_modified

lock1

0

2015-04-19 00:00:00

         表的結構非常簡單,只有兩列屬性,一列代表鎖的名稱,一列代表鎖當前的狀態。

         假如我們要擷取鎖,只需要執行一條update lock set version=1 where name='lock1' and version=0,如果執行成功表示獲得鎖成功,否則表示獲得鎖失敗;同樣我們要釋放鎖,我們只需要執行一條update lock set version=0 where name='lock1' and version=1,如果執行成功表示釋放鎖成功,否則表示釋放鎖失敗。

         在擷取鎖和釋放鎖的時候不需要求在同一個事務裡面,也不需要是同一個串連,也不會出現擷取另外一把鎖同時會自動釋放前面一把鎖,你可以在獲得鎖和釋放鎖中間做大量的商務邏輯操作,不會導致佔用資料連線過長時間,並且也沒有強依賴資料庫類型,以後做資料庫遷移也不用改代碼;並且lock也不會太多條資料,耗時基本上在毫秒級,效能上也得到了保證。

         當然,這種方案可能會出現系統發布或者其他異常情況下導致獲得鎖和釋放鎖中間斷開,從而導致一直無法釋放鎖,但是如果我們能夠做好補償機制,例如獲得鎖不成功的次數超過一定量,我們自動釋放鎖;或者鎖佔用的時間超過了一定的時間,我們自動釋放鎖;或者監控警示,然後人工處理等等;這些補償方案也能夠達到我們的預期。

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.