MySQL 數據庫事務隔離性的實現

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文分享自華爲雲社區《【數據庫事務與鎖機制】- 事務隔離的實現》,原文作者:技術火炬手 。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上在數據庫引擎的實現中並不能實現完全的事務隔離,比如串行化。這種事務隔離方式雖然是比較理想的隔離措施,但是會對併發性能產生比較大的影響,所以在 MySQL 中事務的默認隔離級別是 REPEATABLE READS(可重複讀),下面我們展開討論一下 MySQL 對數據庫隔離性的實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"MySQL 事務隔離性的實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 MySQL InnoDB (下稱 MySQL)中實現事務的隔離性是通過鎖實現的,大家知道在併發場景下我常用的隔離和一致性措施往往是通過鎖實現,所以鎖也是數據庫系統常用的一致性措施。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"MySQL 鎖的分類","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們主要討論 InnoDB 鎖的實現,但是也有必要簡單瞭解 MySQL 中其他數據庫引擎對鎖的實現。整體來說 MySQL 中可以分爲三種鎖的類型 表鎖、行鎖、頁鎖,其中使用表鎖的是 MyISAM 引擎,支持行鎖的是 InnoDB 引擎,同時 InnoDB 也支持表鎖,BDB 支持頁鎖(不是太瞭解)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"表鎖 table-level locking","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表級別的鎖顧名思義就是加鎖的維度是表級別的,是給一個表上鎖,這種鎖的特點是開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,但是併發度也是最低的,表級鎖更適合於以查詢爲主,只有少量按索引條件更新數據的應用。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"MySQL 表鎖的使用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 MySQL 中使用表鎖比較簡單,可以通過 LOCK TABLE 語句對一張表進行加鎖,如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"# 加鎖\nLOCK TABLE T_XXXXXXXXX;\n# 解鎖\nUNLOCK TABLES;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加鎖和解鎖的語法","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"LOCK TABLES\n tbl_name [[AS] alias] lock_type\n [, tbl_name [[AS] alias] lock_type] ...\nlock_type: {\n READ [LOCAL]\n | [LOW_PRIORITY] WRITE\n}\n \nUNLOCK TABLES","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是 LOCK TABLE 是指當前會話的鎖,也就是通過 LOCK TABLE 顯示的爲當前會話獲取表鎖,作用是防止其他會話在需要互斥訪問時修改表的數據,會話只能爲其自身獲取或釋放鎖。一個會話無法獲取另一會話的鎖,也不能釋放另一會話持有的鎖。同時 LOCK TABLE 不單單可以獲取一個表的鎖,也可以是一個視圖,對於視圖鎖定,LOCKTABLES 將視圖中使用的所有基本表添加到要鎖定的表集合中,並自動鎖定它們。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LOCK TABLES 在獲取新鎖之前,隱式釋放當前會話持有的所有表鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"UNLOCK TABLES 顯式釋放當前會話持有的所有表鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"LOCKTABLE 語句有兩個比較重要的參數 lock_type 它可以容許你指定加鎖的模式,是讀鎖還是寫鎖,也就是 READLOCK 和 WRITE LOCK。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"READ 鎖","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讀鎖的特點是 持有鎖的會話可以讀取表但不能寫入表,多個會話可以同時獲取 READ 該表的鎖","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"WRITE 鎖","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"持有鎖的會話可以讀取和寫入表,只有持有鎖的會話才能訪問該表。在釋放鎖之前,沒有其他會話可以訪問它,保持鎖定狀態時,其他會話對錶的鎖定請求將阻塞","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"WRITE 鎖通常比 READ 鎖具有更高的優先級,以確保儘快處理更新。這意味着,如果一個會話獲取了一個 READ 鎖,然後另一個會話請求了一個 WRITE 鎖,則隨後的 READ 鎖請求將一直等待,直到請求該 WRITE 鎖的會話已獲取並釋放了該鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上面對錶鎖的簡單介紹我們引出兩個比較重要的信息,就是讀鎖和寫鎖,那麼答案就浮出水面,在表級別的鎖中其實 MySQL 是通過 共享讀鎖,和排他寫鎖來實現隔離性的,下面我們減少共享讀鎖和排他寫鎖。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"共享讀鎖(Table ReadLock)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"共享鎖又稱爲讀鎖,簡稱 S 鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是隻能讀不能修改","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對 MyISAM 表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;也即當一個 session 給表加讀鎖,其他 session 也可以繼續讀取該表,但所有更新、刪除和插入將會阻塞,直到將表解鎖。MyISAM 引擎在執行 select 時會自動給相關表加讀鎖,在執行 update、delete 和 insert 時會自動給相關表加寫鎖","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"獨佔寫鎖(Table WriteLock)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"排他鎖又稱爲寫鎖,簡稱 X 鎖,顧名思義,排他鎖就是不能與其他所並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就行讀取和修改","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獨佔寫鎖也被稱之爲排他寫鎖,MyISAM 表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;MyISAM 表的讀操作與寫操作之間,以及寫操作之間是串行的。也即當一個 session 給表加寫鎖,其他 session 所有讀取、更新、刪除和插入將會阻塞,直到將表解鎖","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"共享鎖和獨佔鎖的兼容性","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/13/13e64c6c36b5c68ae435cd0da7cf4b8d.png","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"行鎖 Row -level locking","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 MySQL 中 支持行鎖的引擎是 InnoDB,所以我們這裏我們指的行鎖主要是說 InnoDB 的行鎖。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"InnoDB 鎖的實現和 Oracle 非常類似,提供一致性的非鎖定讀、行級鎖支持。行級鎖沒有相關額外的開銷,並可以同時得到併發性和一致性。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"lock 與 latch","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Latch 一般稱爲閂鎖(輕量級的鎖),因爲其要求鎖定的時間必須非常短。若持續的時間長,則應用的性能會非常差。在 InnoDB 中,latch 又可以分爲 mutex(互斥量)和 rwlock(讀寫鎖)。其目的是用來保證併發線程操作臨界資源的正確性,並且通常沒有死鎖檢測的機制。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Lock 的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。並且一般 lock 的對象僅在事務 commit 或 rollback 後進行釋放(不同事務隔離級別釋放的時間可能不同)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"lock 與 latch 的比較","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/35a423d8a23d603e3908f728517face8.png","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"latch 可以通過命令 SHOWENGINE INNODB MUTEX 查看,Lock 可以通過命令 SHOW ENGINE INNODB STATUS 及 information_schema 架構下的表 INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS 來查看","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和上面表鎖中講的一樣 MySQL 行鎖也是通過 共享鎖和獨佔鎖(排他鎖)實現的,所以關於這兩種鎖的概述就不過多簡紹。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"InnoDB 還支持多粒度(granular)鎖定,允許事務同時存在行級鎖和表級鎖,這種種額外的鎖方式,稱爲意向鎖(Intention Lock)。意向鎖是將鎖定的對象分爲多個層次,意向鎖意味着事務希望在更細粒度(fine granularity)上進行加鎖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5b/5b403209c9efaa5da86e5715cf09f96f.jpeg","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果對最下層(最細粒度)的對象上鎖,那麼首先需要對粗粒度的對象上鎖,意向鎖爲表級鎖,不會阻塞除全表掃描以外的任何請求。設計目的主要是爲了在一個事務中揭示下一行將被請求的鎖類型。兩種意向鎖。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"意向共享鎖(IS Lock),事務想要獲得一張表中某幾行的共享鎖","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"意向排他鎖(IX Lock),事務想要獲得一張表中某幾行的排他鎖","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"表級意向鎖與行級鎖的兼容性","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fe/feb8f34558fc81fda34a90f603d58cec.png","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面命令或表都可以查看當前鎖的請求","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"SHOW FULL PROCESSLIST;\nSHOW ENGINE INNODB STATUS;\nSELECT * FROM information_schema.INNODB_TRX;\nSELECT * FROM information_schema.INNODB_LOCKS;\nSELECT * FROM information_schema.INNODB_LOCK_WAITS;","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"一致性非鎖定讀","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一致性的非鎖定讀(consistent nonlocking read)是指 InnoDB 通過行多版本控制(multi versioning)的方式來讀取當前執行時間數據庫中行的數據。如果讀取的行正在執行 DELETE 或 UPDATE 操作,這時不會去等待行上鎖的釋放。而是去讀取行的一個快照數據(之前版本的數據)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個行記錄多個快照數據,一般稱這種技術爲行多版本技術。由此帶來的併發控制,稱之爲多版本併發控制(Multi Version Concurrency Control,MVCC)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之所以稱爲非鎖定讀,因爲不需要等待訪問的行上 X 鎖的釋放。實現方式是通過 undo 段來完成。而 undo 用來在事務中回滾數據,快照數據本身沒有額外的開銷,也不需要上鎖,因爲沒有事務會對歷史數據進行修改操作。非鎖定讀機制極大地提高了數據庫的併發性。在不同事務隔離級別下,讀取的方式不同,並不是在每個事務隔離級別下都是採用非鎖定的一致性讀。此外,即使都是使用非鎖定的一致性讀,但是對於快照數據的定義也不相同。在事務隔離級別 READ COMMITTED 和 REPEATABLE READ 下,InnoDB 使用非鎖定的一致性讀。但對快照數據的定義不相同。在 READCOMMITTED 事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據。而在 REPEATABLEREAD 事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的行數據版本。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"自增長與鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自增長在數據庫中是非常常見的一種屬性,也是首選的主鍵方式。在 InnoDB 的內存結構中,對每個含有自增長值的表都有一個自增長計數器(auto-incrementcounter)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"插入操作會依據這個自增長的計數器值加 1 賦予自增長列。這個實現方式稱做 AUTO-INC Locking,採用了一種特殊的表鎖機制,爲了提高插入的性能,鎖不是在一個事務完成後才釋放,而是在完成對自增長值插入的 SQL 語句後立即釋放。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此 InnoDB 提供了一種輕量級互斥量的自增長實現機制,大大提高了自增長值插入的性能。同時提供了一個參數 innodb_autoinc_lock_mode 來控制自增長的模式,該參數的默認值爲 1。瞭解其實現之前,先對自增長的插入進行分類,如下表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f1/f1bd12021fdf909c80c480ce3912c0be.jpeg","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"參數 innodb_autoinc_lock_mode 的說明","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/37/37690d71c5cda3df1765f3772757a0c6.jpeg","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"InnoDB 中自增長的實現和 MyISAM 不同,MyISAM 存儲引擎是表鎖設計,自增長不用考慮併發插入的問題。如果主從分別使用 InnoDB 和 MyISAM 時,必須考慮這種情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,在 InnoDB 存中,自增長值的列必須是索引,同時必須是索引的第一個列。如果不是第一個列會拋出異常,而 MyISAM 沒有這個問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"外鍵和鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"外鍵主要用於引用完整性的約束檢查。InnoDB 對於一個外鍵列,如果沒有顯式地對這個列加索引,會自動對其加一個索引,可以避免表鎖。而 Oracle 不會自動添加索引,需要手動添加,可能會產生死鎖問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於外鍵值的插入或更新,首先需要查詢(select)父表中的記錄。但是 select 父表操作不是使用一致性非鎖定讀,因爲這會導致數據不一致的問題,因此這時使用的是 SELECT…LOCK IN SHARE MODE 方式,即主動對父表加一個 S 鎖。如果這時父表上已經加了 X 鎖,子表上的操作會被阻塞。如下表:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cd/cda28094a09bda3c328a9357b1649a87.png","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"​行鎖的 3 種算法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"InnoDB 有如下 3 種行鎖的算法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Record Lock:單個行記錄上的鎖。總去鎖住索引記錄,如果表沒有設置任何索引,會使用隱式的主鍵來進行鎖定","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍,並且鎖定記錄本身。行的查詢採用這種鎖定算法","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如一個索引有 10,11,13 和 20 這四個值,那麼該索引可能被 Next-KeyLocking 的區間爲","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9a8ed7d3a581da5156ef6cb8869a772a.jpeg","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/26/266f65949ae77f8cb9223aa77c43f13f.jpeg","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"採用 Next-Key Lock 的鎖定技術稱爲 Next-Key Locking。其設計的目的是爲了解決幻讀問題(Phantom Problem)。Next-Key Lock 是謂詞鎖(predict lock)的一種改進。還有 previous-key locking 技術。同樣上述的索引 10、11、13 和 20,若採用 previous-key locking 技術,那麼鎖定的區間爲","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/03/0397af2ac8fddd578a114bd894a4eedc.png","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當查詢的索引含有唯一屬性時,會對 Next-Key Lock 進行優化。對聚集索引,將其降級爲 Record Lock。對輔助索引,將對下一個鍵值加上 gap lock,即對下一個鍵值的範圍爲加鎖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gap Lock 的作用是爲了阻止多個事務將記錄插入到同一範圍內,而這會產生導致幻讀問題,用戶可以通過以下兩種方式來顯式地關閉 Gap Lock","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將事務的隔離級別設置爲 READ COMMITTED","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將參數 innodb_locks_unsafe_for_binlog 設置爲 1","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述設置破壞了事務的隔離性,並且對於 replication,可能會導致主從數據的不一致。此外,從性能上來看,READCOMMITTED 也不會優於默認的事務隔離級別 READ REPEATABLE。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"解決幻讀問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幻讀問題是指在同一事務下,連續執行兩次同樣的範圍查詢操作,得到的結果可能不同","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Next-KeyLocking 的算法就是爲了避免幻讀問題。對於上述的 SQL 語句,其鎖住的不是單個值,而是對(2,+∞)這個範圍加了 X 鎖。因此任何對於這個範圍的插入不允許,從而避免了幻讀問題。Next-Key Locking 機制在應用層還可以實現唯一性的檢查。例如:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"select * from table_name where col = xxx LOCK IN SHARE MODE;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果用戶通過索引查詢一個值,並對該行加上一個 SLock,那麼即使查詢的值不在,其鎖定的也是一個範圍,因此若沒有返回任何行,那麼新插入的值一定是唯一的。如果此時有多個事務併發操作,那麼這種唯一性檢查機制也不會存在問題。因爲這時會導致死鎖,只有一個事務的插入操作會成功,而其餘的事務會拋出死鎖的錯誤。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 Next-Key Locking 實現應用程序的唯一性檢查:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0e/0ee3825958183568cbb7e2b018ff532b.jpeg","alt":"MySQL 數據庫事務隔離性的實現","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上我們簡單簡紹了 MySQL 如何通過鎖機制實現對事務的隔離,也簡紹了一些實現這些所的算法,如果對細節比較感興趣的同學可以參考 官方文檔 中對 InnoDB 的詳細簡紹。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:華爲雲開發者社區","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鏈接:https://juejin.cn/post/6967555015967768583","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:掘金","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章