<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一張圖徹底搞懂 MySQL 的鎖機(jī)制

          共 12777字,需瀏覽 26分鐘

           ·

          2021-04-27 04:58

          導(dǎo)語

          鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程并發(fā)訪問某一資源的機(jī)制。在數(shù)據(jù)庫中,除傳統(tǒng)的 計(jì)算資源(如 CPU、RAM、I/O 等)的爭(zhēng)用以外,數(shù)據(jù)也是一種供許多用戶共享的資源。如何保證數(shù)據(jù)并發(fā)訪問的一致性、有效性是所有數(shù)據(jù)庫必須解決的一 個(gè)問題,鎖沖突也是影響數(shù)據(jù)庫并發(fā)訪問性能的一個(gè)重要因素。從這個(gè)角度來說,鎖對(duì)數(shù)據(jù)庫而言顯得尤其重要,也更加復(fù)雜。本章我們著重討論 MySQL 鎖機(jī)制 的特點(diǎn),常見的鎖問題,以及解決 MySQL 鎖問題的一些方法或建議。Mysql 用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。這些鎖統(tǒng)稱為悲觀鎖(Pessimistic Lock)。

          相關(guān)書籍推薦

          MySQL 鎖概述

          相對(duì)其他數(shù)據(jù)庫而言,MySQL 的鎖機(jī)制比較簡單,其最 顯著的特點(diǎn)是不同的存儲(chǔ)引擎支持不同的鎖機(jī)制。比如,MyISAM 和 MEMORY 存儲(chǔ)引擎采用的是表級(jí)鎖(table-level locking);BDB 存儲(chǔ)引擎采用的是頁面鎖(page-level locking),但也支持表級(jí)鎖;InnoDB 存儲(chǔ)引擎既支持行級(jí)鎖(row-level locking),也支持表級(jí)鎖,但默認(rèn)情況下是采用行級(jí)鎖。表級(jí)鎖:開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。行級(jí)鎖:開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。頁面鎖:開銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般 從上述特點(diǎn)可見,很難籠統(tǒng)地說哪種鎖更好,只能就具體應(yīng)用的特點(diǎn)來說哪種鎖更合適!僅從鎖的角度 來說:表級(jí)鎖更適合于以查詢?yōu)橹鳎挥猩倭堪此饕龡l件更新數(shù)據(jù)的應(yīng)用,如 Web 應(yīng)用;而行級(jí)鎖則更適合于有大量按索引條件并發(fā)更新少量不同數(shù)據(jù),同時(shí)又有 并發(fā)查詢的應(yīng)用,如一些在線事務(wù)處理(OLTP)系統(tǒng)。

          MyISAM 表鎖

          MySQL 的表級(jí)鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨(dú)占寫鎖(Table Write Lock)。對(duì) MyISAM 表的讀操作,不會(huì)阻塞其他用戶對(duì)同一表的讀請(qǐng)求,但會(huì)阻塞對(duì)同一表的寫請(qǐng)求;對(duì) MyISAM 表的寫操作,則會(huì)阻塞其他用戶對(duì)同一表的讀和寫操作;MyISAM 表的讀操作與寫操作之間,以及寫操作之間是串行的!根據(jù)如表 20-2 所示的 例子可以知道,當(dāng)一個(gè)線程獲得對(duì)一個(gè)表的寫鎖后,只有持有鎖的線程可以對(duì)表進(jìn)行更新操作。其他線程的讀、寫操作都會(huì)等待,直到鎖被釋放為止。

          MyISAM 存儲(chǔ)引擎的寫鎖阻塞讀例子:當(dāng)一個(gè)線程獲得對(duì)一個(gè)表的寫鎖后,只有持有鎖的線程可以對(duì)表進(jìn)行更新操作。其他線程的讀、寫操作都會(huì)等待,直到鎖被釋放為止。MyISAM 存儲(chǔ)引擎的讀鎖阻塞寫例子:一個(gè) session 使用 LOCK TABLE 命令給表 film_text 加了讀鎖,這個(gè) session 可以查詢鎖定表中的記錄,但更新或訪問其他表都會(huì)提示錯(cuò)誤;同時(shí),另外一個(gè) session 可以查詢表中的記錄,但更新就會(huì)出現(xiàn)鎖等待。

          如何加表鎖

          MyISAM 在執(zhí)行查詢語句(SELECT)前,會(huì)自動(dòng)給涉及的所有表加讀鎖,在執(zhí)行更新操作 (UPDATE、DELETE、INSERT 等)前,會(huì)自動(dòng)給涉及的表加寫鎖,這個(gè)過程并不需要用戶干預(yù),因此,用戶一般不需要直接用 LOCK TABLE 命令給 MyISAM 表顯式加鎖。在示例中,顯式加鎖基本上都是為了演示而已,并非必須如此。給 MyISAM 表顯示加鎖,一般是為了在一定程度模擬事務(wù)操作,實(shí)現(xiàn)對(duì)某一時(shí)間點(diǎn)多個(gè)表的一致性讀取。例如, 有一個(gè)訂單表 orders,其中記錄有各訂單的總金額 total,同時(shí)還有一個(gè)訂單明細(xì)表 order_detail,其中記錄有各訂單每一產(chǎn)品的金額小計(jì) subtotal,假設(shè)我們需要檢查這兩個(gè)表的金額合計(jì)是否相符,可能就需要執(zhí)行如下兩條 SQL:

          Select sum(total) from orders;
          Select sum(subtotal) from order_detail;

          這時(shí),如果不先給兩個(gè)表加鎖,就可能產(chǎn)生錯(cuò)誤的結(jié)果,因?yàn)榈谝粭l語句執(zhí)行過程中,order_detail 表可能已經(jīng)發(fā)生了改變。因此,正確的方法應(yīng)該是:

          Lock tables orders read local, order_detail read local;
          Select sum(total) from orders;
          Select sum(subtotal) from order_detail;
          Unlock tables;

          要特別說明以下兩點(diǎn)內(nèi)容:1、上面的例子在 LOCK TABLES 時(shí)加了“l(fā)ocal”選項(xiàng),其作用就是在滿足 MyISAM 表并發(fā)插入條件的情況下,允許其他用戶在表尾并發(fā)插入記錄,有關(guān) MyISAM 表的并發(fā)插入問題,在后面還會(huì)進(jìn)一步介紹。

          2、在用 LOCK TABLES 給表顯式加表鎖時(shí),必須同時(shí)取得所有涉及到表的鎖,并且 MySQL 不支持鎖升級(jí)。也就是說,在執(zhí)行 LOCK TABLES 后,只能訪問顯式加鎖的這些表,不能訪問未加鎖的表;同時(shí),如果加的是讀鎖,那么只能執(zhí)行查詢操作,而不能執(zhí)行更新操作。其實(shí),在自動(dòng)加鎖的 情況下也基本如此,MyISAM 總是一次獲得 SQL 語句所需要的全部鎖。這也正是 MyISAM 表不會(huì)出現(xiàn)死鎖(Deadlock Free)的原因。

          當(dāng)使用 LOCK TABLES 時(shí),不僅需要一次鎖定用到的所有表,而且,同一個(gè)表在 SQL 語句中出現(xiàn)多少次,就要通過與 SQL 語句中相同的別名鎖定多少次,否則也會(huì)出錯(cuò)!舉例說明如下。(1)對(duì) actor 表獲得讀鎖:

          mysql> lock table actor read;
          Query OK, 0 rows affected (0.00 sec)

          (2)但是通過別名訪問會(huì)提示錯(cuò)誤:

          mysql> select a.first_name,a.last_name,b.first_name,b.last_name
          from actor a,actor b
          where a.first_name = b.first_name and a.first_name = 'Lisa' and a.last_name = 'Tom'
          and a.last_name <> b.last_name;

          (3)需要對(duì)別名分別鎖定:

          mysql> lock table actor as a read,actor as b read;
          Query OK, 0 rows affected (0.00 sec)

          (4)按照別名的查詢可以正確執(zhí)行:

          mysql> select a.first_name,a.last_name,b.first_name,b.last_name
          from actor a,actor b where a.first_name = b.first_name
          and a.first_name = 'Lisa' and a.last_name = 'Tom'
          and a.last_name <> b.last_name;
          +————+———–+————+———–+
          | first_name | last_name | first_name | last_name |
          +————+———–+————+———–+
          | Lisa | Tom | LISA | MONROE |
          +————+———–+————+———–+
          1 row in set (0.00 sec)

          查詢表級(jí)鎖爭(zhēng)用情況

          可以通過檢查 table_locks_waited 和 table_locks_immediate 狀態(tài)變量來分析系統(tǒng)上的表鎖定爭(zhēng)奪:

          mysql> show status like 'table%';
          Variable_name | Value
          Table_locks_immediate | 2979
          Table_locks_waited | 0
          2 rows in set (0.00 sec))

          如果 Table_locks_waited 的值比較高,則說明存在著較嚴(yán)重的表級(jí)鎖爭(zhēng)用情況。

          并發(fā)插入(Concurrent Inserts)

          上文提到過 MyISAM 表的讀和寫是串行的,但這是就總體而言的。在一定條件下,MyISAM 表也支持查詢和插入操作的并發(fā)進(jìn)行。

          MyISAM 存儲(chǔ)引擎有一個(gè)系統(tǒng)變量 concurrent_insert,專門用以控制其并發(fā)插入的行為,其值分別可以為 0、1 或 2。

          當(dāng) concurrent_insert 設(shè)置為 0 時(shí),不允許并發(fā)插入。當(dāng) concurrent_insert 設(shè)置為 1 時(shí),如果 MyISAM 表中沒有空洞(即表的中間沒有被刪除的行),MyISAM 允許在一個(gè)進(jìn)程讀表的同時(shí),另一個(gè)進(jìn)程從表尾插入記錄。這也是 MySQL 的默認(rèn)設(shè)置。

          當(dāng) concurrent_insert 設(shè)置為 2 時(shí),無論 MyISAM 表中有沒有空洞,都允許在表尾并發(fā)插入記錄。

          在下面的例子中,session_1 獲得了一個(gè)表的 READ LOCAL 鎖,該線程可以對(duì)表進(jìn)行查詢操作,但不能對(duì)表進(jìn)行更新操作;其他的線程(session_2),雖然不能對(duì)表進(jìn)行刪除和更新操作,但卻可以對(duì)該表進(jìn)行并發(fā)插入操作,這里假設(shè)該表中間不存在空洞。

          MyISAM 存儲(chǔ)引擎的讀寫(INSERT)并發(fā)例子:可以利用 MyISAM 存儲(chǔ)引擎的并發(fā)插入特性,來解決應(yīng) 用中對(duì)同一表查詢和插入的鎖爭(zhēng)用。例如,將 concurrent_insert 系統(tǒng)變量設(shè)為 2,總是允許并發(fā)插入;同時(shí),通過定期在系統(tǒng)空閑時(shí)段執(zhí)行 OPTIMIZE TABLE 語句來整理空間碎片,收回因刪除記錄而產(chǎn)生的中間空洞。

          MyISAM 的鎖調(diào)度

          前面講過,MyISAM 存儲(chǔ)引擎的讀鎖和寫鎖是互斥的,讀寫操作是串行的。**那么,一個(gè)進(jìn)程請(qǐng)求某個(gè) MyISAM 表的讀鎖,同時(shí)另一個(gè)進(jìn)程也請(qǐng)求同一表的寫鎖,MySQL 如何處理呢?答案是寫進(jìn)程先獲得鎖。不僅如此,即使讀請(qǐng)求先到鎖等待隊(duì)列,寫請(qǐng)求后 到,寫鎖也會(huì)插到讀鎖請(qǐng)求之前!這是因?yàn)?MySQL 認(rèn)為寫請(qǐng)求一般比讀請(qǐng)求要重要。**這也正是 MyISAM 表不太適合于有大量更新操作和查詢操作應(yīng)用的原 因,因?yàn)?,大量的更新操作?huì)造成查詢操作很難獲得讀鎖,從而可能永遠(yuǎn)阻塞。這種情況有時(shí)可能會(huì)變得非常糟糕!幸好我們可以通過一些設(shè)置來調(diào)節(jié) MyISAM 的調(diào)度行為。

          1.通過指定啟動(dòng)參數(shù) low-priority-updates,使 MyISAM 引擎默認(rèn)給予讀請(qǐng)求以優(yōu)先的權(quán)利。

          2.通過執(zhí)行命令 SET LOW_PRIORITY_UPDATES=1,使該連接發(fā)出的更新請(qǐng)求優(yōu)先級(jí)降低。

          3.通過指定 INSERT、UPDATE、DELETE 語句的 LOW_PRIORITY 屬性,降低該語句的優(yōu)先級(jí)。

          雖然上面 3 種方法都是要么更新優(yōu)先,要么查詢優(yōu)先的方法,但還是可以用其來解決查詢相對(duì)重要的應(yīng)用(如用戶登錄系統(tǒng))中,讀鎖等待嚴(yán)重的問題。另外,MySQL 也提供了一種折中的辦法來調(diào)節(jié)讀寫沖突,即給系統(tǒng)參數(shù) max_write_lock_count 設(shè)置一個(gè)合適的值,當(dāng)一個(gè)表的讀鎖達(dá)到這個(gè)值后,MySQL 就暫時(shí)將寫請(qǐng)求的優(yōu)先級(jí)降低,給讀進(jìn)程一定獲得鎖的機(jī)會(huì)。

          上面已經(jīng)討論了寫優(yōu)先調(diào)度機(jī)制帶來的問題和解決辦法。這 里還要強(qiáng)調(diào)一點(diǎn):一些需要長時(shí)間運(yùn)行的查詢操作,也會(huì)使寫進(jìn)程“餓死”!因此,應(yīng)用中應(yīng)盡量避免出現(xiàn)長時(shí)間運(yùn)行的查詢操作,不要總想用一條 SELECT 語 句來解決問題,因?yàn)檫@種看似巧妙的 SQL 語句,往往比較復(fù)雜,執(zhí)行時(shí)間較長,在可能的情況下可以通過使用中間表等措施對(duì) SQL 語句做一定的“分解”,使每 一步查詢都能在較短時(shí)間完成,從而減少鎖沖突。如果復(fù)雜查詢不可避免,應(yīng)盡量安排在數(shù)據(jù)庫空閑時(shí)段執(zhí)行,比如一些定期統(tǒng)計(jì)可以安排在夜間執(zhí)行。

          InnoDB 鎖

          InnoDB 與 MyISAM 的最大不同有兩點(diǎn):一是支持事務(wù)(TRANSACTION);二是采用了行級(jí)鎖。行級(jí)鎖與表級(jí)鎖本來就有許多不同之處,另外,事務(wù)的引入也帶來了一些新問題。

          1、事務(wù)(Transaction)及其 ACID 屬性事務(wù)是由一組 SQL 語句組成的邏輯處理單元,事務(wù)具有 4 屬性,通常稱為事務(wù)的 ACID 屬性。

          原子性(Actomicity):事務(wù)是一個(gè)原子操作單元,其對(duì)數(shù)據(jù)的修改,要么全都執(zhí)行,要么全都不執(zhí)行。

          一致性(Consistent):在事務(wù)開始和完成時(shí),數(shù)據(jù)都必須保持一致狀態(tài)。這意味著所有相關(guān)的數(shù)據(jù)規(guī)則都必須應(yīng)用于事務(wù)的修改,以操持完整性;事務(wù)結(jié)束時(shí),所有的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如 B 樹索引或雙向鏈表)也都必須是正確的。

          隔離性(Isolation):數(shù)據(jù)庫系統(tǒng)提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行。這意味著事務(wù)處理過程中的中間狀態(tài)對(duì)外部是不可見的,反之亦然。

          持久性(Durable):事務(wù)完成之后,它對(duì)于數(shù)據(jù)的修改是永久性的,即使出現(xiàn)系統(tǒng)故障也能夠保持。2、并發(fā)事務(wù)帶來的問題相對(duì)于串行處理來說,并發(fā)事務(wù)處理能大大增加數(shù)據(jù)庫資源的利用率,提高數(shù)據(jù)庫系統(tǒng)的事務(wù)吞吐量,從而可以支持可以支持更多的用戶。但并發(fā)事務(wù)處理也會(huì)帶來一些問題,主要包括以下幾種情況。

          更新丟失(Lost Update):當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行,然后基于最初選定的值更新該行時(shí),由于每個(gè)事務(wù)都不知道其他事務(wù)的存在,就會(huì)發(fā)生丟失更新問題——最后的更新覆蓋了其他事務(wù)所做的更新。例如,兩個(gè)編輯人員制作了同一文檔的電子副本。每個(gè)編輯人員獨(dú)立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文檔。最后保存其更改保存其更改副本的編輯人員覆蓋另一個(gè)編輯人員所做的修改。如果在一個(gè)編輯人員完成并提交事務(wù)之前,另一個(gè)編輯人員不能訪問同一文件,則可避免此問題。

          **臟讀(Dirty Reads):**一個(gè)事務(wù)正在對(duì)一條記錄做修改,在這個(gè)事務(wù)并提交前,這條記錄的數(shù)據(jù)就處于不一致狀態(tài);這時(shí),另一個(gè)事務(wù)也來讀取同一條記錄,如果不加控制,第二個(gè)事務(wù)讀取了這些“臟”的數(shù)據(jù),并據(jù)此做進(jìn)一步的處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象被形象地叫做“臟讀”。不可重復(fù)讀(Non-Repeatable Reads):一個(gè)事務(wù)在讀取某些數(shù)據(jù)已經(jīng)發(fā)生了改變、或某些記錄已經(jīng)被刪除了!這種現(xiàn)象叫做“不可重復(fù)讀”。

          幻讀(Phantom Reads):一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù),這種現(xiàn)象就稱為“幻讀”。

          3、事務(wù)隔離級(jí)別在并發(fā)事務(wù)處理帶來的問題中,“更新丟失”通常應(yīng)該是完全避免的。但防止更新丟失,并不能單靠數(shù)據(jù)庫事務(wù)控制器來解決,需要應(yīng)用程序?qū)σ碌臄?shù)據(jù)加必要的鎖來解決,因此,防止更新丟失應(yīng)該是應(yīng)用的責(zé)任。

          “臟讀”、“不可重復(fù)讀”和“幻讀”,其實(shí)都是數(shù)據(jù)庫讀一致性問題,必須由數(shù)據(jù)庫提供一定的事務(wù)隔離機(jī)制來解決。數(shù)據(jù)庫實(shí)現(xiàn)事務(wù)隔離的方式,基本可以分為以下兩種。

          一種是在讀取數(shù)據(jù)前,對(duì)其加鎖,阻止其他事務(wù)對(duì)數(shù)據(jù)進(jìn)行修改。另一種是不用加任何鎖,通過一定機(jī)制生成一個(gè)數(shù)據(jù)請(qǐng)求時(shí)間點(diǎn)的一致性數(shù)據(jù)快照(Snapshot),并用這個(gè)快照來提供一定級(jí)別(語句級(jí)或事務(wù)級(jí))的一致性讀取。從用戶的角度,好像是數(shù)據(jù)庫可以提供同一數(shù)據(jù)的多個(gè)版本,因此,這種技術(shù)叫做數(shù)據(jù)多版本并發(fā)控制(M ultiVersion Concurrency Control,簡稱 MVCC 或 MCC),也經(jīng)常稱為多版本數(shù)據(jù)庫。在 MVCC 并發(fā)控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當(dāng)前讀 (current read)??煺兆x,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。當(dāng)前讀,讀取的是記錄的最新版本,并且,當(dāng)前讀返回的記錄,都會(huì)加上鎖,保證其他事務(wù)不會(huì)再并發(fā)修改這條記錄。在一個(gè)支持 MVCC 并發(fā)控制的系統(tǒng)中,哪些讀操作是快照讀?哪些操作又是當(dāng)前讀呢?以 MySQL InnoDB 為例:

          快照讀:簡單的 select 操作,屬于快照讀,不加鎖。(當(dāng)然,也有例外)

          select * from table where ?;

          當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,需要加鎖。下面語句都屬于當(dāng)前讀,讀取記錄的最新版本。并且,讀取之后,還需要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,對(duì)讀取記錄加鎖。其中,除了第一條語句,對(duì)讀取記錄加 S 鎖 (共享鎖)外,其他的操作,都加的是 X 鎖 (排它鎖)。

          select * from table where ? lock in share mode;
          select * from table where ? for update;
          insert into table values (…);
          update table set ? where ?;
          delete from table where ?;

          數(shù)據(jù)庫的事務(wù)隔離越嚴(yán)格,并發(fā)副作用越小,但付出的代價(jià)也就越大,因?yàn)槭聞?wù)隔離實(shí)質(zhì)上就是使事務(wù)在一定程度上 “串行化”進(jìn)行,這顯然與“并發(fā)”是矛盾的。同時(shí),不同的應(yīng)用對(duì)讀一致性和事務(wù)隔離程度的要求也是不同的,比如許多應(yīng)用對(duì)“不可重復(fù)讀”和“幻讀”并不敏 感,可能更關(guān)心數(shù)據(jù)并發(fā)訪問的能力。

          為了解決“隔離”與“并發(fā)”的矛盾,ISO/ANSI SQL92 定義了 4 個(gè)事務(wù)隔離級(jí)別,每個(gè)級(jí)別的隔離程度不同,允許出現(xiàn)的副作用也不同,應(yīng)用可以根據(jù)自己的業(yè)務(wù)邏輯要求,通過選擇不同的隔離級(jí)別來平衡 “隔離”與“并發(fā)”的矛盾。下表很好地概括了這 4 個(gè)隔離級(jí)別的特性。

          獲取 InonoD 行鎖爭(zhēng)用情況

          mysql> show status like 'innodb_row_lock%';

          如果發(fā)現(xiàn)鎖爭(zhēng)用比較嚴(yán)重,如 InnoDB_row_lock_waits 和 InnoDB_row_lock_time_avg 的值比較高,還可以通過設(shè)置 InnoDB Monitors 來進(jìn)一步觀察發(fā)生鎖沖突的表、數(shù)據(jù)行等,并分析鎖爭(zhēng)用的原因。

          InnoDB 的行鎖模式及加鎖方法

          InnoDB 實(shí)現(xiàn)了以下兩種類型的行鎖。

          共享鎖(s):又稱讀鎖。允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。若事務(wù) T 對(duì)數(shù)據(jù)對(duì)象 A 加上 S 鎖,則事務(wù) T 可以讀 A 但不能修改 A,其他事務(wù)只能再對(duì) A 加 S 鎖,而不能加 X 鎖,直到 T 釋放 A 上的 S 鎖。這保證了其他事務(wù)可以讀 A,但在 T 釋放 A 上的 S 鎖之前不能對(duì) A 做任何修改。

          排他鎖(X):又稱寫鎖。允許獲取排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同的數(shù)據(jù)集共享讀鎖和排他寫鎖。若事務(wù) T 對(duì)數(shù)據(jù)對(duì)象 A 加上 X 鎖,事務(wù) T 可以讀 A 也可以修改 A,其他事務(wù)不能再對(duì) A 加任何鎖,直到 T 釋放 A 上的鎖。

          對(duì)于共享鎖大家可能很好理解,就是多個(gè)事務(wù)只能讀數(shù)據(jù)不能改數(shù)據(jù)。

          對(duì)于排他鎖大家的理解可能就有些差別,我當(dāng)初就犯了一個(gè)錯(cuò)誤,以為排他鎖鎖住一行數(shù)據(jù)后,其他事務(wù)就不能讀取和修改該行數(shù)據(jù),其實(shí)不是這樣的。排他鎖指的是一個(gè)事務(wù)在一行數(shù)據(jù)加上排他鎖后,其他事務(wù)不能再在其上加其他的鎖。mysql InnoDB 引擎默認(rèn)的修改數(shù)據(jù)語句:update,delete,insert 都會(huì)自動(dòng)給涉及到的數(shù)據(jù)加上排他鎖,select 語句默認(rèn)不會(huì)加任何鎖類型,如果加排他鎖可以使用 select …for update 語句,加共享鎖可以使用 select … lock in share mode 語句。所以加過排他鎖的數(shù)據(jù)行在其他事務(wù)種是不能修改數(shù)據(jù)的,也不能通過 for update 和 lock in share mode 鎖的方式查詢數(shù)據(jù),但可以直接通過 select …from…查詢數(shù)據(jù),因?yàn)槠胀ú樵儧]有任何鎖機(jī)制。另外,為了允許行鎖和表鎖共存,實(shí)現(xiàn)多粒度鎖機(jī)制,InnoDB 還有兩種內(nèi)部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

          意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行共享鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的 IS 鎖。

          意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加排他鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的 IX 鎖。

          InnoDB 行鎖模式兼容性列表:如果一個(gè)事務(wù)請(qǐng)求的鎖模式與當(dāng)前的鎖兼容,InnoDB 就請(qǐng)求的鎖授予該事務(wù);反之,如果兩者兩者不兼容,該事務(wù)就要等待鎖釋放。意向鎖是 InnoDB 自動(dòng)加的,不需用戶干預(yù)。對(duì)于 UPDATE、DELETE 和 INSERT 語句,InnoDB 會(huì)自動(dòng)給涉及數(shù)據(jù)集加排他鎖(X);對(duì)于普通 SELECT 語句,InnoDB 不會(huì)加任何鎖。事務(wù)可以通過以下語句顯式給記錄集加共享鎖或排他鎖:共享鎖(S):mysql SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。排他鎖(X):mysql SELECT * FROM table_name WHERE ... FOR UPDATE。用mysql SELECT ... IN SHARE MODE獲得共享鎖,主要用在需要數(shù)據(jù)依存關(guān)系時(shí)來確認(rèn)某行記錄是否存在,并確保沒有人對(duì)這個(gè)記錄進(jìn)行 UPDATE 或者 DELETE 操作。但是如果當(dāng)前事務(wù)也需要對(duì)該記錄進(jìn)行更新操作,則很有可能造成死鎖,對(duì)于鎖定行記錄后需要進(jìn)行更新操作的應(yīng)用,應(yīng)該使用 SELECT… FOR UPDATE 方式獲得排他鎖。

          InnoDB 行鎖實(shí)現(xiàn)方式

          InnoDB 行鎖是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,這一點(diǎn) MySQL 與 Oracle 不同,后者是通過在數(shù)據(jù)塊中對(duì)相應(yīng)數(shù)據(jù)行加鎖來實(shí)現(xiàn)的。InnoDB 這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過索引條件檢索數(shù)據(jù),InnoDB 才使用行級(jí)鎖,否則,InnoDB 將使用表鎖!在實(shí)際應(yīng)用中,要特別注意 InnoDB 行鎖的這一特性,不然的話,可能導(dǎo)致大量的鎖沖突,從而影響并發(fā)性能。下面通過一些實(shí)際例子來加以說明。(1)在不通過索引條件查詢的時(shí)候,InnoDB 確實(shí)使用的是表鎖,而不是行鎖。

          mysql> create table tab_no_index(id int,name varchar(10)) engine=innodb;
          Query OK, 0 rows affected (0.15 sec)
          mysql> insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');
          Query OK, 4 rows affected (0.00 sec)
          Records: 4 Duplicates: 0 Warnings: 0

          在上面的例子中,看起來 session_1 只給一行加了排他鎖,但 session_2 在請(qǐng)求其他行的排他鎖時(shí),卻出現(xiàn)了鎖等待!原因就是在沒有索引的情況下,InnoDB 只能使用表鎖。當(dāng)我們給其增加一個(gè)索引后,InnoDB 就只鎖定了符合條件的行,如下例所示:創(chuàng)建 tab_with_index 表,id 字段有普通索引:

          mysql> create table tab_with_index(id int,name varchar(10)) engine=innodb;
          mysql> alter table tab_with_index add index id(id);

          (2)由于 MySQL 的行鎖是針對(duì)索引加的鎖,不是針對(duì)記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會(huì)出現(xiàn)鎖沖突的。應(yīng)用設(shè)計(jì)的時(shí)候要注意這一點(diǎn)。在下面的例子中,表 tab_with_index 的 id 字段有索引,name 字段沒有索引:

          mysql> alter table tab_with_index drop index name;
          Query OK, 4 rows affected (0.22 sec) Records: 4 Duplicates: 0
          Warnings: 0
          mysql> insert into tab_with_index  values(1,'4');
          Query OK, 1 row affected (0.00 sec)
          mysql> select * from tab_with_index where id = 1;

          InnoDB 存儲(chǔ)引擎使用相同索引鍵的阻塞例子.(3)當(dāng)表有多個(gè)索引的時(shí)候,不同的事務(wù)可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB 都會(huì)使用行鎖來對(duì)數(shù)據(jù)加鎖。在下面的例子中,表 tab_with_index 的 id 字段有主鍵索引,name 字段有普通索引:

          mysql> alter table tab_with_index add index name(name);
          Query OK, 5 rows affected (0.23 sec) Records: 5 Duplicates: 0
          Warnings: 0

          InnoDB 存儲(chǔ)引擎的表使用不同索引的阻塞例子(4)即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由 MySQL 通過判斷不同執(zhí)行計(jì)劃的代價(jià)來決 定的,如果 MySQL 認(rèn)為全表掃描效率更高,比如對(duì)一些很小的表,它就不會(huì)使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。因此,在分析鎖沖突 時(shí),別忘了檢查 SQL 的執(zhí)行計(jì)劃,以確認(rèn)是否真正使用了索引。比如,在 tab_with_index 表里的 name 字段有索引,但是 name 字段是 varchar 類型的,檢索值的數(shù)據(jù)類型與索引字段不同,雖然 MySQL 能夠進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換,但卻不會(huì)使用索引,從而導(dǎo)致 InnoDB 使用表鎖。通過用 explain 檢查兩條 SQL 的執(zhí)行計(jì)劃,我們可以清楚地看到了這一點(diǎn)。

          mysql> explain select * from tab_with_index where name = 1 \G
          mysql> explain select * from tab_with_index where name = '1' \G

          間隙鎖(Next-Key 鎖)當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù),并請(qǐng)求共享或排他鎖時(shí),InnoDB 會(huì)給符合條件的已有數(shù)據(jù)記錄的 索引項(xiàng)加鎖;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”,InnoDB 也會(huì)對(duì)這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖 (Next-Key 鎖)。舉例來說,假如 emp 表中只有 101 條記錄,其 empid 的值分別是 1,2,…,100,101,下面的 SQL:

          Select * from  emp where empid > 100 for update;

          是一個(gè)范圍條件的檢索,InnoDB 不僅會(huì)對(duì)符合條件的 empid 值為 101 的記錄加鎖,也會(huì)對(duì) empid 大于 101(這些記錄并不存在)的“間隙”加鎖。

          InnoDB 使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關(guān)隔離級(jí)別的要求,對(duì)于上面的例子,要是不使 用間隙鎖,如果其他事務(wù)插入了 empid 大于 100 的任何記錄,那么本事務(wù)如果再次執(zhí)行上述語句,就會(huì)發(fā)生幻讀;另外一方面,是為了滿足其恢復(fù)和復(fù)制的需 要。有關(guān)其恢復(fù)和復(fù)制對(duì)鎖機(jī)制的影響,以及不同隔離級(jí)別下 InnoDB 使用間隙鎖的情況,在后續(xù)的章節(jié)中會(huì)做進(jìn)一步介紹。

          很顯然,在使用范圍條件檢索并鎖定記錄時(shí),InnoDB 這種加鎖機(jī)制會(huì)阻塞符合條件范圍內(nèi)鍵值的并發(fā)插入,這往往會(huì)造成嚴(yán)重的鎖等待。因此,在實(shí)際應(yīng)用開發(fā)中,尤其是并發(fā)插入比較多的應(yīng)用,我們要盡量優(yōu)化業(yè)務(wù)邏輯,盡量使用相等條件來訪問更新數(shù)據(jù),避免使用范圍條件。

          還要特別說明的是,InnoDB 除了通過范圍條件加鎖時(shí)使用間隙鎖外,如果使用相等條件請(qǐng)求給一個(gè)不存在的記錄加鎖,InnoDB 也會(huì)使用間隙鎖!下面這個(gè)例子假設(shè) emp 表中只有 101 條記錄,其 empid 的值分別是 1,2,……,100,101。InnoDB 存儲(chǔ)引擎的間隙鎖阻塞例子小結(jié)本文重點(diǎn)介紹了 MySQL 中 MyISAM 表級(jí)鎖和 InnoDB 行級(jí)鎖的實(shí)現(xiàn)特點(diǎn),并討論了兩種存儲(chǔ)引擎經(jīng)常遇到的鎖問題和解決辦法。

          對(duì)于 MyISAM 的表鎖,主要討論了以下幾點(diǎn):(1)共享讀鎖(S)之間是兼容的,但共享讀鎖(S)與排他寫鎖(X)之間,以及排他寫鎖(X)之間是互斥的,也就是說讀和寫是串行的。

          (2)在一定條件下,MyISAM 允許查詢和插入并發(fā)執(zhí)行,我們可以利用這一點(diǎn)來解決應(yīng)用中對(duì)同一表查詢和插入的鎖爭(zhēng)用問題。

          (3)MyISAM 默認(rèn)的鎖調(diào)度機(jī)制是寫優(yōu)先,這并不一定適合所有應(yīng)用,用戶可以通過設(shè)置 LOW_PRIORITY_UPDATES 參數(shù),或在 INSERT、UPDATE、DELETE 語句中指定 LOW_PRIORITY 選項(xiàng)來調(diào)節(jié)讀寫鎖的爭(zhēng)用。

          (4)由于表鎖的鎖定粒度大,讀寫之間又是串行的,因此,如果更新操作較多,MyISAM 表可能會(huì)出現(xiàn)嚴(yán)重的鎖等待,可以考慮采用 InnoDB 表來減少鎖沖突。

          對(duì)于 InnoDB 表,本文主要討論了以下幾項(xiàng)內(nèi)容:(1)InnoDB 的行鎖是基于索引實(shí)現(xiàn)的,如果不通過索引訪問數(shù)據(jù),InnoDB 會(huì)使用表鎖。(2)介紹了 InnoDB 間隙鎖(Next-key)機(jī)制,以及 InnoDB 使用間隙鎖的原因。

          在不同的隔離級(jí)別下,InnoDB 的鎖機(jī)制和一致性讀策略不同。在了解 InnoDB 鎖特性后,用戶可以通過設(shè)計(jì)和 SQL 調(diào)整等措施減少鎖沖突和死鎖,包括:

          1.盡量使用較低的隔離級(jí)別;精心設(shè)計(jì)索引,并盡量使用索引訪問數(shù)據(jù),使加鎖更精確,從而減少鎖沖突的機(jī)會(huì);

          2.選擇合理的事務(wù)大小,小事務(wù)發(fā)生鎖沖突的幾率也更??;

          3.給記錄集顯式加鎖時(shí),最好一次性請(qǐng)求足夠級(jí)別的鎖。比如要修改數(shù)據(jù)的話,最好直接申請(qǐng)排他鎖,而不是先申請(qǐng)共享鎖,修改時(shí)再請(qǐng)求排他鎖,這樣容易產(chǎn)生死鎖;

          4.不同的程序訪問一組表時(shí),應(yīng)盡量約定以相同的順序訪問各表,對(duì)一個(gè)表而言,盡可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機(jī)會(huì);

          5.盡量用相等條件訪問數(shù)據(jù),這樣可以避免間隙鎖對(duì)并發(fā)插入的影響;不要申請(qǐng)超過實(shí)際需要的鎖級(jí)別;除非必須,查詢時(shí)不要顯示加鎖;

          6.對(duì)于一些特定的事務(wù),可以使用表鎖來提高處理速度或減少死鎖的可能。

          推薦閱讀

          開發(fā)人員必備的MySQL事務(wù)原理分析與總結(jié)

          MySQL中的日志文件 你全都了解嗎?

          MySQL函數(shù)sum使用場(chǎng)景解讀



          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  特级大胆西西4444人体 | 岛国电影av| 国产乱伦免费视频 | 日韩三级不卡 | 卡一卡二卡三高清无码在线观看 |