亚洲狠狠久久综合一区二区三区

<progress id="73rr5"></progress>
<tbody id="73rr5"><pre id="73rr5"></pre></tbody>

    <tbody id="73rr5"></tbody><dd id="73rr5"><track id="73rr5"></track></dd>
    PHP面試指南2020-MySQL鎖-詳解

    成人自考/成人高考/教師資格證/會計從業資格證/建造師/造價師,一個小程序就夠啦。

    用途

    多個查詢需要在同一時刻修改數據,會產生并發控制的問題。使用鎖可以有效解決這個問題

    鎖的分類

    按照鎖的粒度劃分:行鎖、表鎖、頁鎖
    按照鎖的使用方式劃分:共享鎖、排它鎖(悲觀鎖的一種實現)
    還有兩種思想上的鎖:悲觀鎖、樂觀鎖
    InnoDB中有幾種行級鎖類型:Record Lock(在索引記錄上加鎖)、Gap Lock(間隙鎖)、Next-key Lock(臨鍵鎖)

    行鎖

    行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。**行級鎖能大大減少數據庫操作的沖突。其加鎖粒度最小,但加鎖的開銷也最大。有可能會出現死鎖的情況。**行級鎖按照使用方式分為共享鎖和排他鎖。

    • 共享鎖:同一時刻可以同時讀取同一個資源

    • 排他鎖:一個寫鎖會阻塞其他的寫鎖和讀鎖

    共享鎖用法(S鎖 讀鎖)

    若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。

    select ... lock in share mode;

    共享鎖就是允許多個線程同時獲取一個鎖,一個鎖可以同時被多個線程擁有。

    排它鎖用法(X 鎖 寫鎖)

    若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。

    select ... for update

    排它鎖,也稱作獨占鎖,一個鎖在某一時刻只能被一個線程占有,其它線程必須等待鎖被釋放之后才可能獲取到鎖。

    表鎖

    表級鎖是mysql鎖中粒度最大的一種鎖,表示當前的操作對整張表加鎖,資源開銷比行鎖少,不會出現死鎖的情況,但是發生鎖沖突的概率很大。被大部分的mysql引擎支持,MyISAM和InnoDB都支持表級鎖,但是InnoDB默認的是行級鎖。

    共享鎖用法:

    LOCK TABLE table_name [ AS alias_name ] READ

    排它鎖用法:

    LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE

    解鎖用法:

    unlock tables;

    頁鎖

    頁級鎖是MySQL中鎖定粒度介于行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但沖突多,行級沖突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。BDB支持頁級鎖
    。

    悲觀鎖和樂觀鎖

    無論是悲觀鎖還是樂觀鎖,都是人們定義出來的概念,可以認為是一種思想。其實不僅僅是數據庫系統中有樂觀鎖和悲觀鎖的概念,像memcache、hibernate、tair等都有類似的概念。

    針對于不同的業務場景,應該選用不同的并發控制方式。所以,不要把樂觀并發控制和悲觀并發控制狹義的理解為DBMS中的概念,更不要把他們和數據中提供的鎖機制(行鎖、表鎖、排他鎖、共享鎖)混為一談。其實,在DBMS中,悲觀鎖正是利用數據庫本身提供的鎖機制來實現的。

    MySQL InnoDB中使用悲觀鎖

    要使用悲觀鎖,我們必須關閉mysql數據庫的自動提交屬性,因為MySQL默認使用autocommit模式,也就是說,當你執行一個更新操作后,MySQL會立刻將結果進行提交。 set autocommit=0;

        //0.開始事務            
        begin;/begin work;/start transaction; (三者選一就可以)              
        //1.查詢出商品信息               
        select status from t_goods where id=1 for update;              
        //2.根據商品信息生成訂單                
        insert into t_orders (id,goods_id) values (null,1);              
        //3.修改商品status為2               
        update t_goods set status=2;              
        //4.提交事務             
        commit;

    上面的查詢語句中,我們使用了 select…for update 的方式,這樣就通過開啟排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id為1的 那條數據就被我們鎖定了,其它的事務必須等本次事務提交之后才能執行。這樣我們可以保證當前的數據不會被其它事務修改。

    上面我們提到,使用 select…for update 會把數據給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB默認行級鎖。行級鎖都是基于索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住,這點需要注意。

    優點與不足

    悲觀并發控制實際上是“先取鎖再訪問”的保守策略,為數據處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,還有增加產生死鎖的機會;另外,在只讀型事務處理中由于不會產生沖突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了并行性,一個事務如果鎖定了某行數據,其他事務就必須等待該事務處理完才可以處理那行數。

    樂觀鎖

    在關系數據庫管理系統里,樂觀并發控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種并發控制的方法。它假設多用戶并發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分數據。在提交數據更新之前,每個事務會先檢查在該事務讀取數據后,有沒有其他事務又修改了該數據。如果其他事務有更新的話,正在提交的事務會進行回滾。樂觀事務控制最早是由孔祥重(H.T.Kung)教授提出。

    樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為數據一般情況下不會造成沖突,所以在數據進行提交更新的時候,才會正式對數據的沖突與否進行檢測,如果發現沖突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。

    相對于悲觀鎖,在對數據庫進行處理的時候,樂觀鎖并不會使用數據庫提供的鎖機制。一般的實現樂觀鎖的方式就是記錄數據版本。

    數據版本,為數據增加的一個版本標識。當讀取數據時,將版本標識的值一同讀出,數據每更新一次,同時對版本標識進行更新。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的版本標識進行比對,如果數據庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認為是過期數據。

    實現數據版本有兩種方式,第一種是使用版本號,第二種是使用時間戳,方法類似,下面舉例說明版本號的做法。

    使用版本號實現樂觀鎖

    使用數據版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式。何謂數據版本?即為數據增加一個版本標識,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現。當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加一。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對,如果數據庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期數據

    1.數據庫表設計

    task表有三個字段,分別是id,value,version

    2.實現

    1)先讀task表的數據(實際上這個表只有一條記錄),得到version的值為versionValue

    2)每次更新task表中的value字段時,為了防止發生沖突,需要這樣操作

    update task set value = newValue,version =  versionValue + 1   where version = versionValue;

    只有這條語句執行了,才表明本次更新value字段的值成功

    如假設有兩個節點A和B都要更新task表中的value字段值,差不多在同一時刻,A節點和B節點從task表中讀到的version值為2,那么A節點和B節點在更新value字段值的時候,都操作 update task set value = newValue,version = 3 where version = 2;,實際上只有1個節點執行該SQL語句成功,假設A節點執行成功,那么此時task表的version字段的值是3,B節點再操作update task set value = newValue,version = 3 where version = 2;這條SQL語句是不執行的,這樣就保證了更新task表時不發生沖突。

    鎖粒度

    • 表鎖:開銷最小,對表進行寫操作,需要獲得寫鎖,會阻塞該表的所有讀寫操作

    • 行級鎖:最大鎖開銷,可以最大程度地支持并發處理

    行鎖與表鎖的轉變

    InnoDB 行級鎖是通過給索引上的索引項加鎖來實現的,InnoDB行級鎖只有通過索引條件檢索數據,才使用行級鎖;否則,InnoDB使用表鎖

    在不通過索引(主鍵)條件查詢的時候,InnoDB是表鎖而不是行鎖。也就是說,在沒有使用索引的情況下,使用的就是表鎖。

    間隙鎖

    間隙鎖可以理解為是對于一定范圍內的數據進行鎖定,如果說這個區間沒有這條數據的話也是會鎖住的;主要是解決幻讀的問題,如果沒有添加間隙鎖,如果其他事物中添加id在1到100之間的某條記錄,此時會發生幻讀;另一方面,視為了滿足其恢復和賦值的需求(幻讀的概念在事務隔離文章中有提到)。

    默認情況下,innodb_locks_unsafe_for_binlog是0(禁用),這意味著啟用了間隙鎖定:InnoDB使用下一個鍵鎖進行搜索和索引掃描。若要啟用該變量,請將其設置為1。這將導致禁用間隙鎖定:InnoDB只使用索引記錄鎖進行搜索和索引掃描。

    innodb自動使用間隙鎖的條件:

    必須在RR級別下
    檢索條件必須有索引(沒有索引的話,mysql會全表掃描,那樣會鎖定整張表所有的記錄,包括不存在的記錄,此時其他事務不能修改不能刪除不能添加)

    間隙鎖的目的是為了防止幻讀,其主要通過兩個方面實現這個目的:

    防止間隙內有新數據被插入
    防止已存在的數據,更新成間隙內的數據(例如防止numer=3的記錄通過update變成number=5)
    下面將通過例子來詳細了解一下間隙鎖的出現場景:

    create table y (id int primary key ,num int);

    其中id為主鍵索引,a為二級索引。

    數據如下:

    id	num1	23	45	57	59	8

    場景:

    間隙區間:從查找的字段向上和向下去找。

    通過上面的場景,我們可以先找到間隙區間(2,4)(4,5),因此我們可以確定 id 在 1-3,3-5之間,也就是為id為2,4的記錄,number在上述間隙區間的值不能夠插入。

    死鎖

    為什么會產生死鎖

    兩個事務都持有對方需要的鎖,并且在等待對方釋放,并且雙方都不會釋放自己的鎖。

    出現死鎖的原因

    1. 系統資源不足

    2. 進程運行推進的順序不當

    3. 資源分配不當

    產生死鎖的四個必要條件

    1. 互斥條件: 一個資源只能被一個進程使用

    2. 請求和保持條件:進行獲得一定資源,又對其他資源發起了請求,但是其他資源被其他線程占用,請求阻塞,但是也不會釋放自己占用的資源。

    3. 不可剝奪條件: 指進程所獲得的資源,不可能被其他進程剝奪,只能自己釋放

    4. 環路等待條件: 進程發生死鎖,必然存在著進程-資源之間的環形鏈

    處理死鎖的方法

    預防,避免,檢查,解除死鎖

    減少死鎖的方法

    使用事務,不使用 lock tables 。
    保證沒有長事務。
    操作完之后立即提交事務,特別是在交互式命令行中。
    如果在用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE
    MODE),嘗試降低隔離級別。
    修改多個表或者多個行的時候,將修改的順序保持一致。
    創建索引,可以使創建的鎖更少。
    最好不要用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE)。
    如果上述都無法解決問題,那么嘗試使用 lock tables t1, t2, t3 鎖多張表

    可以參考

    訪客
    郵箱
    網址

    Top 亚洲狠狠久久综合一区二区三区
    <progress id="73rr5"></progress>
    <tbody id="73rr5"><pre id="73rr5"></pre></tbody>

      <tbody id="73rr5"></tbody><dd id="73rr5"><track id="73rr5"></track></dd>