MySQL InnoDB存儲引擎之鎖

    概念:
        鎖是用來管理對共享文件的併發訪問。innodb會在行級別上對數據庫上鎖。不過innodb存儲引擎會在數據庫內部其他多個地方使用鎖,從而允許對不同資源提供併發訪問。例如操作緩衝池中的LRU列表,刪除,添加,移動LRU列表中的元素,爲了保證一致性,必須有鎖的介入。MyISAM引擎是表鎖,而InnoDB提供一致性的非鎖定讀、行級鎖,且行級鎖沒有相關額外的開銷。
    鎖
        table-level locking(表級鎖)
            整個表被客戶鎖定。根據鎖定的類型,其他客戶不能向表中插入記錄,甚至從中讀數據也受到限制MyISAM、MEMORY默認鎖級別,個別時候,InnoDB也會升級爲表級鎖
        row-level locking(行級鎖)
            只有線程當前使用的行被鎖定,其他行對於其他線程都是可用的InnoDB默認行級鎖。是基於索引數據結構來實現的,而不是像ORACLE的鎖,是基於block的。InnoDB也會升級爲表級鎖,全表/全索引更新,請求autoinc鎖等
        page-level locking(頁級鎖)
            鎖定表中某些行集合(稱做頁),被鎖定的行只對鎖定最初的線程是可行。如果另外一個線程想要向這些行寫數據,它必須等到鎖被釋放。不過其他頁的行仍然可以使用BDB默認頁級鎖
    lock與latch
        latch稱爲閂鎖(輕量級的鎖),因爲其要求鎖定的時間必須非常短。若持續的時間長,則應用的性能會非常差。在InnoDB存儲引擎中,又可以分爲mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證併發線程操作臨界資源的正確性,並且通常沒有死鎖檢測的機制。latch可以通過命令show engine innodb mutex來進行查看。如圖:
        由上圖可以看出列Type顯示的總是InnoDB,列Name顯示latch的信息以及所在源碼的行數,列Status中顯示的os_waits表示操作系統等待的次數。
        lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。並且一般lock的對象僅在事務commit或者rollback後釋放(不同事務隔離級別釋放的時間可能不一樣)。有死鎖機制。二則的區別如下:
       
    特點:
    InnoDB是通過對索引上的索引項加鎖來實現行鎖。這種特點也就意味着,只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。
    鎖的類型:
        有兩種標準的行級鎖:
            共享鎖(S lock):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖.SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
            排它鎖(X lock):允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他鎖.SELECT * FROM table_name WHERE ... FOR UPDATE
        InnoDB存儲引擎支持意向鎖且設計比較簡練,分爲兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。(意向鎖是InnoDB自動加的)
            意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖.
            意向獨佔鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖.
        表級意向鎖與行級鎖的兼容情況如下圖:
           
    鎖的查看
        在InnoDB1.0版本之前只能通過show engine innodb status(transactions行中查看) 或者 show full processlist來查看當前庫中鎖的請求。但是在這之後在information_schema架構下新增innodb_trx、innodb_locks和innodb_lock_waits三張表記錄當前庫中鎖的情況。
        三個表的字段說明如下圖
    一致性非鎖定讀(consistent nonlocking read)
        一致性的非鎖定讀是指InnoDB存儲引擎通過行多版本控制(multi_versioning)的方式來讀取當前執行時間數據庫中行的數據。如果讀取的行正在執行delete或者update操作,這時讀取操作不會因此去等待行上鎖的釋放。相反地,InnoDB存儲引擎會去讀取行的一個快照數據(當前行數據的歷史版本)。快照數據是指該行的之前版本的數據,該實現是通過undo段來完成。而undo用來在事務回滾數據,因此快照數據本身是沒有額外開銷。而且,讀取快照數據是不需要上鎖的。一致性非鎖定讀是InnoDB存儲引擎的默認讀取方式(在讀取不會佔用和等待表上的鎖)。但是在不同事務隔離級別下,讀取的方式不同,並不是在每個事務隔離級別下都是採用非鎖定的一致性讀。即使都是使用非鎖定的一致性讀,但是對於快照數據的定義格式也各不相同。在事務隔離級別READ COMMITTED(RC)和REPEATABLE READ(RR,InnoDB存儲引擎的默認事務隔離級別)下,InnoDB存儲引擎使用非鎖定的一致性讀。然而,對於快照數據的定義去不相同。在RC事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據。而在RR事務隔離解綁下,對於快照數據,非一致性讀總是讀取事務開始時的行數據版本。
    一致性鎖定讀
        有上文知道,默認的事務隔離級別(RR)模式下,InnoDB存儲引擎的select操作使用一致性非鎖定讀。但是在某些情況下,用戶需要顯式地對數據庫讀操作加鎖以保證數據邏輯的一致性。InnoDB存儲引擎對於select語句支持兩種一致性的鎖定讀操作:
            select ... for update:對讀取的行記錄加X鎖,其他事物不能對該行加任何鎖。
            select ... lock in share mode:對讀取的行記錄加S鎖,其他事物可以對該行加S鎖,但是如果加X鎖,則會被阻塞。
    自增長與鎖
        在InnoDB存儲引擎的內存結構中,對每個含有自增長值的表都有一個自增長計數器(auto-increment counter)。當對含有自增長的計數器的表進行插入操作是,這個計數器會被初始化,執行如下的語句來得到計數器的值:select max(auto_inc_col) from t for update。插入操作會依據這個自增長的計數器值加1賦予自增長列。這個實現方式稱爲AUTO-INC Locking,這是一種特殊的表鎖機制,爲了提高插入的性能,鎖不是在一個事務完成之後才釋放,而是在完成對自增長值插入的SQL語句後會立即釋放。AUTO-INC Locking在一定程度上提高了併發插入的效率,但是還存在一些性能上的問題。首先,對於有自增長值的列的併發插入性能較差,事務必須等待前一個插入的完成(不用等待事務的完成)。其次,對於insert ... select的大數據量的插入會影響插入的性能,因爲另一個事務中的插入會被阻塞。從MySQL5.1.22版本開始,InnoDB存儲引擎提供了一種輕量級互斥量的自增長實現機制,這種機制大大提高了自增長值插入的性能。通過參數innodb_autoinc_lock_mode來控制自增長的模式(默認爲1)。自增長的插入進行分類如圖:
        innodb_autoinc_lock_mode的參數值及其對自增長的影響如下圖:
        MyISAM存儲引擎是表鎖,自增長不用考慮併發插入的問題。需要注意的是:在InnoDB存儲引擎中,自增長值的列必須是索引,同時必須是索引的第一個列,如果不是第一個列,MySQL是會拋出異常的。異常如圖
    外鍵與鎖
        外鍵主要用於完整性的約束檢查。在InnoDB存儲引擎中,對於一個外鍵列,如果沒有顯式地對這個列加索引,InnoDB存儲引擎會自動對其加一個索引,避免表鎖。對於外鍵值的插入或者更新,首先需要查詢父表中的記錄,對於父表的select操作,不是使用的一致性非鎖定讀的方式,因爲這樣會發生數據不一致的問題,所以這時使用的是select ... lock in share mode方式,即主動給父表加一個S鎖。
    鎖的問題
        dirty read 髒讀
            髒讀就是讀取到髒數據(未提交的數據)。一個事務(A)讀取到另一個事務(B)中修改後但尚未提交的數據,並在這個數據的基礎上操作。這時,如果B事務回滾,那麼A事務讀到的數據是無效的。不符合一致性。如圖
            首先事務的隔離級別有默認的RR改爲RU,由上述例子可以看出會話B中兩次select操作取得了不同的結果,並且這2條記錄是會話A中並未提交的數據,這就產生了髒讀。由此可以得出結論:髒讀發生的條件是事務的隔離級別爲RU。
        unrepeatable read 不可重複讀
            事務(A)讀取到了另一個事務(B)已經提交的更改數據,不符合隔離性。不可重複讀和髒讀的區別是:髒讀是讀到未提交的數據,而不可重複讀則讀到的是已經提交的數據。首先將事務隔離級別調整爲RC,然後操作下邊的例子:
        phantom read 幻讀
            事務(A)讀取到了另一個事務(B)提交的新增數據,不符合隔離性。
    鎖的範圍(鎖的算法):
        1.Record Lock :單個記錄上的鎖,總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。
        2.Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身。
        3.Next-key Lock: 鎖定一個範圍和本身 Record Lock + Gap Lock,防止幻讀。
        主鍵索引和唯一輔助索引 = record lock
        非唯一輔助索引 = next-key lock
    阻塞
        不同鎖之間的兼容性關係,在有些時刻一個事務中的鎖需要等待另外一個事務中的鎖釋放它所佔用的資源,這就是阻塞。阻塞並不是一件壞事,其是爲了確保事務可以併發且正常地運行。在InnoDB存儲引擎中,參數innodb_lock_wait_timeout用來動態的控制等待的時間(默認50秒),innodb_rollback_on_timeout用來靜態的設定釋放在等待超時時對進行的事務進行回滾操作(默認OFF,代表不回滾)。
    死鎖

        死鎖是指兩個或者兩個以上的事務在執行過程中,因爭奪資源而造成的一種相互等待的現象。解決死鎖最簡單的一種方式是超時,即當兩個事務相互等待是,當一個等待時間超過設置的某一閥值時,其中一個事務進行回滾,另外一個等待的事務就能繼續進行。在InnoDB存儲引擎中,參數innodb_lock_wait_timeout用來設置超時時間。但若超時的事務所佔權重比較大,如果事務操作更新了很多行,佔用了較多的undo log,這時採用FIFO的方式就不合適啦,因爲回滾這個事務的時間相對於另一個事務所佔用的時間可能會很多。因此,除了超時機制,當前數據庫都普遍採用wait-for graph(等待圖)的方式來進行死鎖檢測。要求數據庫報錯以下兩種信息:a.鎖的信息鏈表;b.事務等待鏈表。通過上述鏈表可以構造一張圖,而在這個圖中若存在迴路,就代表存在死鎖。在wait-for graph中,事務爲圖中的節點。如圖:

        如圖可以發現存在迴路(1,2),因此存在死鎖,這時InnoDB存儲引擎選擇回滾undo量最小的事務。wait-for graph的死鎖檢測通常採用深度優先的算法實現。        
    注意:
        1.S X IS IX,表示的是,本鎖和其他鎖共存的方式,是互斥還是兼容
        2.RECORD LOCK、GAP LOCK、NEXT-KEY LOCK,表示的是,這些鎖要加載的範圍,是行記錄本身,還是行記錄+間隙,甚至更大的範圍
    重要的結論:
        1、任何輔助索引上的鎖,或者非索引列上的鎖,最終都要回溯到主鍵上,在主鍵上也要加一把鎖
        2、任何葉子節點上的S或X鎖之前,都會在根節點加一個IS或IX鎖,也就是表級別的IS、IX鎖
        3、主鍵索引上的鎖,都是record lock
        4、唯一索引輔助索引上的鎖,也都是record lock
        5、非唯一索引輔助索引上的鎖,則是next-key lock
        6、不會有單獨的gap lock出現,只會伴隨着record lock出現,依附於它

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章