亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    認(rèn)識什么是鎖,MySQL中鎖如何解決幻讀問題

    MySQL教程欄目介紹鎖如何解決幻讀問題。

    認(rèn)識什么是鎖,MySQL中鎖如何解決幻讀問題

    前言

    今天就為大家介紹一下MySQL中鎖相關(guān)的知識。

    本文在沒有特別聲明的情況下,均是默認(rèn)InnoDB引擎,如涉及到其他引擎或者數(shù)據(jù)庫則會特別指出。

    什么是鎖

    鎖是一種用于保證在并發(fā)場景下每個事務(wù)仍能以一致性的方式讀取和修改數(shù)據(jù)的方式,當(dāng)一個事務(wù)對某一條數(shù)據(jù)上鎖之后,其他事務(wù)就不能修改或者只能阻塞等待鎖的釋放,所以鎖的粒度大小一定程度上可以影響到訪問數(shù)據(jù)庫的性能。

    從鎖的粒度上來說,我們可以將鎖分為表鎖和行鎖。

    表鎖

    顧名思議,表鎖就是直接鎖表,在MyISAM引擎中就只有表鎖。

    表鎖的加鎖方式為:

    LOCK TABLE 表名 READ;--鎖定后表只讀 UNLOCK TABLE; --解鎖復(fù)制代碼

    行鎖

    行鎖,從名字上來看,就是鎖住一行數(shù)據(jù),然而,行鎖的實際實現(xiàn)算法會相對復(fù)雜,有時候并不僅僅只是鎖住某一條數(shù)據(jù),這個后面再展開。

    正常的思路是:鎖住一行數(shù)據(jù)之后,其他事務(wù)就不能來訪問這條數(shù)據(jù)了,那么我們想象,假如事務(wù)A訪問了一條數(shù)據(jù),只是拿出來讀一下,并不想去修改,正好事務(wù)B也來訪問這條數(shù)據(jù),也僅僅只是想拿出來讀一下,并不想去修改,這時候如果因此阻塞了,就有點浪費性能了。所以為了優(yōu)化這種讀數(shù)據(jù)的場景,我們又把行鎖分為了兩大類型:共享鎖和排他鎖。

    共享鎖

    共享鎖,Shared Lock,又稱之為讀鎖,S鎖,就是說一條數(shù)據(jù)被加了S鎖之后,其他事務(wù)也能來讀數(shù)據(jù),可以共享一把鎖。
    我們可以通過如下語句加共享鎖:

    select * from test where id=1 LOCK IN SHARE MODE;復(fù)制代碼

    加鎖之后,直到加鎖的事務(wù)結(jié)束(提交或者回滾)就會釋放鎖。

    排他鎖

    排他鎖,Exclusive Lock,又稱之為寫鎖,X鎖。就是說一條數(shù)據(jù)被加了X鎖之后,其他事務(wù)想來訪問這條數(shù)據(jù)只能阻塞等待鎖的釋放,具有排他性。

    當(dāng)我們在修改數(shù)據(jù),如:insert,update,delete的時候MySQL就會自動加上排他鎖,同樣的,我們可以通過如下sql語句手動加上排他鎖:

    select * from test where id=1 for update;復(fù)制代碼

    在InnoDB引擎中,是允許行鎖和表鎖共存的。

    但是這樣就會有一個問題,假如事務(wù)A給t表其中一行數(shù)據(jù)上鎖了,這時候事務(wù)B想給t表上一個表鎖,這時候怎么辦呢?事務(wù)B怎么知道t表有沒有行鎖的存在,如果采用全表遍歷的情況,當(dāng)表中的數(shù)據(jù)很大的話,加鎖都要加半天,所以MySQL中就又引入了意向鎖

    意向鎖

    意向鎖為表鎖,分為兩種類型,分為:意向共享鎖(Intention Shared Lock)和意向排他鎖(Intention Exclusive Lock),這兩種鎖又分別可以簡稱為IS鎖和IX鎖。

    意向鎖是MySQL自己維護(hù)的,用戶無法手動加意向。

    意向鎖有兩大加鎖規(guī)則:

    • 當(dāng)需要給一行數(shù)據(jù)加上S鎖的時候,MySQL會先給這張表加上IS鎖。
    • 當(dāng)需要給一行數(shù)據(jù)加上X鎖的時候,MySQL會先給這張表加上IX鎖。

    這樣的話上面的問題就迎刃而解了,當(dāng)需要給一張表上表鎖的時候,只需要看這張表是否有對應(yīng)的意向鎖就可以了,無需遍歷整張表。

    各種鎖的兼容關(guān)系

    下面這張圖是各種鎖的兼容關(guān)系,參考自官網(wǎng):

    X IX S IS
    X

    互斥

    互斥

    互斥

    互斥

    IX

    互斥

    共享

    沖突

    共享

    S

    互斥

    互斥

    共享

    共享

    IS

    互斥

    共享

    共享

    共享

    鎖到底鎖的是什么

    建立以下兩張表,并初始化5條數(shù)據(jù),注意test表有2個索引而test2沒有索引:

    CREATE TABLE `test` (   `id` int(11) NOT NULL,   `name` varchar(50) DEFAULT NULL,   PRIMARY KEY (`id`),   KEY `NAME_INDEX` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  INSERT INTO test VALUE(1,'張1'); INSERT INTO test VALUE(5,'張5'); INSERT INTO test VALUE(8,'張8'); INSERT INTO test VALUE(10,'張10'); INSERT INTO test VALUE(20,'張20');  CREATE TABLE `test2` (   `id` varchar(32) NOT NULL,   `name` varchar(32) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  INSERT INTO test2 VALUE(1,'張1'); INSERT INTO test2 VALUE(5,'張5'); INSERT INTO test2 VALUE(8,'張8'); INSERT INTO test2 VALUE(10,'張10'); INSERT INTO test2 VALUE(20,'張20');復(fù)制代碼

    舉例猜測

    在行鎖中,假如我們對一行記錄加鎖,那么到底是把什么東西鎖住了,我們來看下面兩個例子:
    舉例1(操作test表):

    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT * FROM test WHERE id=1 FOR UPDATE;
    SELECT * FROM test WHERE id=1 FOR UPDATE;

    阻塞

    SELECT * FROM test WHERE id=5 FOR UPDATE;

    加鎖成功

    COMMIT;

    (釋放鎖)

    SELECT * FROM test WHERE id=1 FOR UPDATE;

    加鎖成功

    舉例2(操作test2表):

    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT * FROM test2 WHERE id=1 FOR UPDATE;
    SELECT * FROM test2 WHERE id=1 FOR UPDATE;

    阻塞

    SELECT * FROM test2 WHERE id=5 FOR UPDATE;

    阻塞

    COMMIT;

    (釋放鎖)

    SELECT * FROM test2 WHERE id=1 FOR UPDATE;

    加鎖成功

    從上面兩個例子我們可以發(fā)現(xiàn),test表好像確實是鎖住了id=1這一行的記錄,而test2表好像不僅僅是鎖住了id=1這一行記錄,實際上經(jīng)過嘗試我們就知道,test2表是被鎖表了,所以其實MySQL中InnoDB鎖住的是索引,當(dāng)沒有索引的時候就會鎖表。

    接下來再看一個場景:

    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT * FROM test WHERE name=‘張1’ FOR UPDATE;
    SELECT name FROM test WHERE name=‘張1’ FOR UPDATE;

    阻塞

    SELECT id FROM test WHERE id=1 FOR UPDATE;

    阻塞

    COMMIT;

    (釋放鎖)

    SELECT id FROM test WHERE id=1 FOR UPDATE;

    加鎖成功

    這個例子中我們是把name索引鎖住了,然后我們在事務(wù)B中通過主鍵索引只查id,這樣就用到name索引了,但是最后發(fā)現(xiàn)也被阻塞了。所以我們又可以得出下面的結(jié)論,MySQL索引不但鎖住了輔助索引,還會把輔助索引對應(yīng)的主鍵索引一起鎖住。

    到這里,可能有人會有懷疑,那就是我把輔助索引鎖住了,但是假如加鎖的時候,只用到了覆蓋索引,然后我再去查主鍵會怎么樣呢?

    接下來讓我們再驗證一下:

    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT name FROM test WHERE name=‘張1’ FOR UPDATE;
    SELECT name FROM test WHERE name=‘張1’ FOR UPDATE;

    阻塞

    SELECT * FROM test WHERE id=1 FOR UPDATE;

    阻塞

    SELECT id FROM test WHERE id=1 FOR UPDATE;

    阻塞

    COMMIT;

    (釋放鎖)

    SELECT id FROM test WHERE id=1 FOR UPDATE;

    加鎖成功

    我們可以看到,就算只是用到了輔助索引加鎖,MySQL還是會把主鍵索引鎖住,而主鍵索引的B+樹葉子節(jié)點中,又存儲了整條數(shù)據(jù),所以查詢?nèi)魏巫侄味紩绘i定。

    到這里,我們可以明確的給鎖到底鎖住了什么下結(jié)論了:

    結(jié)論

    InnoDB引擎中,鎖鎖的是索引:

    • 假如一張表沒有索引,MySQL會進(jìn)行鎖表(其實鎖住的是隱藏列ROWID的主鍵索引)
    • 假如我們對輔助索引加鎖,那么輔助索引所對應(yīng)的主鍵索引也會被鎖住
    • 主鍵索引被鎖住,實際上就等于是整條記錄都被鎖住了(主鍵索引葉子節(jié)點存儲了整條數(shù)據(jù))

    行鎖的算法

    上一篇介紹事務(wù)的時候我們提到了,MySQL通過加鎖來防止了幻讀,但是如果行鎖只是鎖住一行記錄,好像并不能防止幻讀,所以行鎖鎖住一條記錄的話只是其中一種情況,實際上行鎖有三種算法:記錄鎖(Record Lock),間隙鎖(Gap Lock)和臨鍵鎖(Next-Key Lock),而之所以能做到防止幻讀,正是臨鍵鎖起的作用。

    記錄鎖(Record Lock)

    記錄鎖就是上面介紹的,當(dāng)我們的查詢能命中一條記錄的時候,InnoDB就會使用記錄鎖,鎖住所命中的這一行記錄。

    間隙鎖(Gap Lock)

    當(dāng)我們的查詢沒有命中記錄的時候,這時候InnoDB就會加上一個間隙鎖。

    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT * FROM test WHERE id=1 FOR UPDATE;
    INSERT INTO test VALUE (2,‘張2’);

    阻塞

    INSERT INTO test VALUE (3,‘張3’);

    阻塞

    SELECT * FROM test WHERE id=2 FOR UPDATE;

    加鎖成功

    COMMIT;

    (釋放鎖)

    從上面的例子中,我們可以得出結(jié)論:

    • 間隙鎖與間隙鎖之間不沖突,也就是事務(wù)A加了間隙鎖,事務(wù)B可以在同一個間隙中加間隙鎖。(之所以會用到間隙鎖就是沒有命中數(shù)據(jù)的時候,所以并沒有必要去阻塞讀,也沒有必要阻塞其他事務(wù)對同一個間隙加鎖)
    • 間隙鎖主要是會阻塞插入操作

    間隙是如何確定的

    test表中有5條記錄,主鍵值分別為:1,5,8,10,20。那么就會有如下六個間隙:
    (-∞,1),(1,5),(5,8),(8,10),(10,20),(20,+∞)

    而假如主鍵不是int類型,那么就會轉(zhuǎn)化為ASCII碼之后再確定間隙。

    臨鍵鎖(Next-Key Lock)

    臨鍵鎖就是記錄鎖和間隙鎖的結(jié)合。當(dāng)我們進(jìn)行一個范圍查詢,不但命中了一條或者多條記錄,且同時包括了間隙,這時候就會使用臨鍵鎖,臨鍵鎖是InnoDB中行鎖的默認(rèn)算法。

    注意了,這里僅針對RR隔離級別,對于RC隔離級除了外鍵約束和唯一性約束會加間隙鎖,沒有間隙鎖,自然也就沒有了臨鍵鎖,所以RC級別下加的行鎖都是記錄鎖,沒有命中記錄則不加鎖,所以RC級別是沒有解決幻讀問題的

    臨鍵鎖在以下兩個條件時會降級成為間隙鎖或者記錄鎖:

    • 當(dāng)查詢未命中任務(wù)記錄時,會降級為間隙鎖。
    • 當(dāng)使用主鍵或者唯一索引命中了一條記錄時,會降級為記錄鎖。
    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT * FROM test WHERE id>=2 AND id<=6 FOR UPDATE;
    INSERT INTO test VALUE (2,‘張2’);

    阻塞

    INSERT INTO test VALUE (6,‘張6’);

    阻塞

    INSERT INTO test VALUE (8,‘張8’);

    阻塞

    SELECT * FROM test WHERE id=8 FOR UPDATE;

    阻塞

    INSERT INTO test VALUE (9,‘張9’);

    插入成功

    COMMIT;

    (釋放鎖)

    上面這個例子,事務(wù)A加的鎖跨越了(1,5)和(5,8)兩個間隙,且同時命中了5,然后我們發(fā)現(xiàn)我們對id=8這條數(shù)據(jù)進(jìn)行操作也阻塞了,但是9這條記錄插入成功了。

    臨鍵鎖加鎖規(guī)則

    臨鍵鎖的劃分是按照左開右閉的區(qū)間來劃分的,也就是我們可以把test表中的記錄劃分出如下區(qū)間:(-∞,1],(1,5],(5,8],(8,10],(10,20],(20,+∞)。

    那么臨鍵鎖到底鎖住了哪些范圍呢?

    **臨鍵鎖中鎖住的是最后一個命中記錄的 key 和其下一個左開右閉的區(qū)間**

    那么上面的例子中其實鎖住了(1,5]和(5,8]這兩個區(qū)間。

    臨鍵鎖為何能解決幻讀問題

    臨鍵鎖為什么要鎖住命中記錄的下一個左開右閉的區(qū)間?答案就是為了解決幻讀。

    我們想一想上面的查詢范圍id>=2且id<=6,如果我們事務(wù)A只鎖住了(1,5]這個區(qū)間,假如這時候事務(wù)B插入一條數(shù)據(jù)id=6,那么事務(wù)A再去查詢,就會多出來了一條記錄id=6,就會出現(xiàn)了幻讀,所以我把你下一個區(qū)間5,10]也給鎖住,就可以避免了幻讀。

    當(dāng)然,其實如果我們執(zhí)行的查詢剛好是id>=2且id<=5,那么就算只鎖住了(1,5],同樣能避免幻讀問題,只是我們要考慮到查詢范圍的最大值沒有命中記錄的情況,而鎖住了下一個區(qū)間,可以確保不論是哪種范圍查詢,都可以避免幻讀的產(chǎn)生。

    在我們使用鎖的時候,有一個問題是需要注意和避免的,我們知道,排它鎖有互斥的特性。一個事務(wù)持有鎖的時候,會阻止其他的事務(wù)獲取鎖,這個時候會造成阻塞等待,那么假如事務(wù)一直等待下去,就會一直占用CPU資源,所以,鎖等待會有一個超時時間,在InnoDB引擎中,可以通過參數(shù):innodb_lock_wait_timeout查詢:

    SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';復(fù)制代碼

    默認(rèn)超時時間是50s,超時后會自動釋放鎖回滾事務(wù)。但是我們想一下,假如事務(wù)A在等待事務(wù)B釋放鎖,而事務(wù)B又在等待事務(wù)A釋放鎖,這時候就會產(chǎn)生一個等待環(huán)路了,而這種情況是無論等待多久都不可能會獲取鎖成功的,所以是沒有必要去等50s的,這種形成等待環(huán)路的現(xiàn)象又叫做死鎖。

    死鎖(Dead Lock)

    什么是死鎖

    死鎖是指的兩個或者兩個以上的事務(wù)在執(zhí)行過程中,因為爭奪鎖資源而造成的一種互相等待的現(xiàn)象。

    事務(wù)A 事務(wù)B
    BEGIN;
    SELECT * FROM test WHERE id=10 FOR UPDATE;
    BEGIN;
    SELECT * FROM test WHERE id=20 FOR UPDATE;
    SELECT * FROM test WHERE id=20 FOR UPDATE;
    SELECT * FROM test WHERE id=10 FOR UPDATE;
    ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
    查詢出結(jié)果

    認(rèn)識什么是鎖,MySQL中鎖如何解決幻讀問題
    我們可以看到,發(fā)生死鎖之后就會立刻回滾,而不會漫無目的的去等待50s之后超時再回滾事務(wù),那么MySQL是如何知道產(chǎn)生了死鎖的,是如何檢測死鎖的發(fā)生呢?

    死鎖的檢測

    目前數(shù)據(jù)庫大部分采用wait-for graph(等待圖)的方式來進(jìn)行死鎖檢測,InnoDB引擎也是采用這種方式來檢測死鎖。數(shù)據(jù)庫中會記錄兩種信息:

    • 鎖的信息鏈表
    • 事務(wù)的等待鏈表
      wait-for graph 算法會根據(jù)這兩個信息構(gòu)建一張圖,當(dāng)圖中存在回路,則證明存在死鎖:
      如下圖中,t1和t2之間存在回路,這就證明t1和t2事務(wù)之間存在死鎖
      認(rèn)識什么是鎖,MySQL中鎖如何解決幻讀問題

    死鎖的避免

    • 盡量將長事務(wù)拆分成多個小事務(wù)
    • 查詢時避免沒有where條件語句查詢,并盡可能使用索引查詢
    • 可以的話盡量使用等值查詢

    鎖信息查詢

    InnoDB在information_schema庫下提供了3張表供我們查詢并排查事務(wù)和鎖相關(guān)問題。

    INNODB_TRX

    記錄了當(dāng)前在InnoDB中執(zhí)行的每個事務(wù)的信息,包括事務(wù)是否在等待鎖、事務(wù)何時啟動以及事務(wù)正在執(zhí)行的SQL語句(如果有的話)。

    列名 含義
    trx_id InnoDD引擎中的事務(wù)的唯一ID
    trx_state 事務(wù)狀態(tài):RUNNING, LOCK WAIT, ROLLING BACK,COMMITTING
    trx_started 事務(wù)的開始時間
    trx_requested_lock_id 等待會務(wù)的鎖ID,如果trx_state不為LOCK WAIT時,為null
    trx_wait_started 事務(wù)等待開始的時間
    trx_weight 事務(wù)的權(quán)重,反映了一個事務(wù)修改和鎖住的行數(shù),當(dāng)發(fā)生死鎖時候,InnoDB會選擇該值最小的事務(wù)進(jìn)行回滾
    trx_mysql_thread_id MySQL中的線程ID,可以通過SHOW PROCESSLIST查詢
    trx_query 事務(wù)運行的sql語句
    trx_operation_state 事務(wù)的當(dāng)前操作狀態(tài),如果沒有則為NULL
    trx_tables_in_use 當(dāng)前事務(wù)中執(zhí)行的sql語句用到的表數(shù)量
    trx_tables_locked 已經(jīng)被鎖定表的數(shù)量(因為用的是行鎖,所以雖然顯示一張表被鎖了,但是可能只是鎖定的其中一行或幾行,所以其他行還是可以被其他事務(wù)訪問)
    trx_lock_structs 當(dāng)前事務(wù)保留的鎖數(shù)量
    trx_lock_memory_bytes 當(dāng)前事務(wù)的索結(jié)構(gòu)在內(nèi)存中的大小
    trx_rows_locked 當(dāng)前事務(wù)中鎖住的大致行數(shù),包括已經(jīng)被打上刪除標(biāo)記等物理存在的但是對當(dāng)前事務(wù)不可見的數(shù)據(jù)
    trx_rows_modified 當(dāng)前事務(wù)修改或者插入的行數(shù)
    trx_concurrency_tickets 并發(fā)數(shù),指的是當(dāng)前事務(wù)未結(jié)束前仍然可以執(zhí)行的并發(fā)數(shù),可以通過系統(tǒng)變量innodb_concurrency_tickets設(shè)置
    trx_isolation_level 當(dāng)前事務(wù)隔離級別
    trx_unique_checks 是否為當(dāng)前事務(wù)打開或者關(guān)閉唯一約束:0-否1-是
    trx_foreign_key_checks 是否為當(dāng)前事務(wù)打開或者關(guān)閉外鍵約束:0-否1-是
    trx_last_foreign_key_error 最后一個外鍵錯誤信息,沒有則為空
    trx_adaptive_hash_latched 自適應(yīng)哈希索引是否被當(dāng)前事務(wù)鎖定。在分區(qū)自適應(yīng)哈希索引搜索系統(tǒng)時,單個事務(wù)不會鎖定整個自適應(yīng)哈希索引。自適應(yīng)哈希索引分區(qū)由innodb_adaptive_hash_index_parts控制,默認(rèn)設(shè)置為8。
    trx_adaptive_hash_timeout 是立即放棄自適應(yīng)哈希索引的搜索latch,還是在來自MySQL的調(diào)用中保留它。當(dāng)沒有自適應(yīng)哈希索引爭用時,這個值將保持為零,并且語句會保留latch直到它們完成。在爭用期間,它的計數(shù)減少到零,并且語句在每一行查找之后立即釋放鎖存。當(dāng)自適應(yīng)哈希索引搜索系統(tǒng)被分區(qū)時(由innodb_adaptive_hash_index_parts控制),該值保持為0。
    trx_is_read_only 當(dāng)前事務(wù)是否只讀:0-否1-是
    trx_autocommit_non_locking 值為1表示這是一條不包含for update和lock in share model的語句,而且是在開啟autocommit情況下執(zhí)行的有且僅有這一條語句,當(dāng)這列和TRX_IS_READ_ONLY都為1時,InnoDB會優(yōu)化事務(wù)以減少與更改表數(shù)據(jù)事務(wù)的相關(guān)開銷。

    INNODB_LOCKS

    記錄了事務(wù)請求鎖但未獲得的每個鎖的信息和一個事務(wù)持有鎖但正在阻塞另一個事務(wù)的每個鎖的信息。

    列名 含義
    lock_id 鎖的id(雖然LOCK_ID當(dāng)前包含TRX_ID,但LOCK_ID中的數(shù)據(jù)格式隨時可能更改,不要編寫解析LOCK_ID值的應(yīng)用程序)
    lock_trx_id 上一張表的事務(wù)ID
    lock_mode 鎖的模式: S, X, IS, IX, GAP, AUTO_INC,UNKNOWN
    lock_type 鎖的類型是表鎖還是行鎖
    lock_table 被鎖住的表
    lock_index 被鎖住的索引,表鎖則為NULL
    lock_space 鎖記錄的空間id,表鎖則為NULL
    lock_page 事務(wù)鎖定頁的數(shù)量,表鎖則為NULL
    lock_rec 事務(wù)鎖定行的數(shù)量,表鎖則為NULL
    lock_data 事務(wù)鎖定的主鍵值,表鎖則為NULL

    INNODB_LOCK_WAITS

    記錄了鎖等待的信息。每個被阻塞的InnoDB事務(wù)包含一個或多個行,表示它所請求的鎖以及正在阻塞該請求的任何鎖。

    列名 含義
    lock_id 鎖的id(雖然LOCK_ID當(dāng)前包含TRX_ID,但LOCK_ID中的數(shù)據(jù)格式隨時可能更改,不要編寫解析LOCK_ID值的應(yīng)用程序)
    requesting_trx_id 申請鎖資源的事務(wù)ID
    requested_lock_id 申請的鎖的ID
    blocking_trx_id 阻塞的事務(wù)ID
    blocking_lock_id 阻塞的鎖的ID

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號