共計 7722 個字符,預計需要花費 20 分鐘才能閱讀完成。
本篇文章給大家分享的是有關 MySQL 中鎖機制的底層原理是什么,丸趣 TV 小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著丸趣 TV 小編一起來看看吧。
一、Mysql 為什么要加鎖
鎖機制用于管理對共享資源的并發訪問,是對數據庫的一種保護機制,也是數據庫在事務操作中保證事務數據一致性和完整性的一種機制。當有多個用戶并發的去存取數據時,在數據庫中就可能會產生多個事務同時去操作一行數據的情況,如果我們不對此類并發操作不加以控制的話,就可能會讀取和存儲不正確的數據,最終破壞了數據的一致性;下面請看一種典型的并發更新數據所產生的數據丟失更新問題:
| 事務 A | 事務 B | |--|--| | begin A | | ||begin B| |select salary form tb where id=1(查詢結果為 1000)|| ||select salary form tb where id=1(查詢結果為 1000)| |update tb set salary=1100 where id=1|| ||update tb set salary=1200 where id=1| |commit A|| ||commit B|
異常結果:表中 salary 字段 id 為 1 員工的工資更新為了 1200,但是實際上針對該員工的工資進行了兩次的修改操作,由于事務 B 在事務 A 之后提交,所以首先提交的事務 A 的更新操作被丟失了,所以我們就需要鎖機制來保證這種情況不會發生,保證事務中數據的一致性。
二、鎖類型
表鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突概率高,并發度最低;
行鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度小;發生鎖沖突的概率低,并發度高;
三、MyISAM 存儲引擎:
表級鎖的鎖模式:表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨占寫鎖(Table Write Lock);對于 MyISAM 表的讀操作,不會阻塞其他用戶對同一個表的讀請求,但是會阻塞對同一個表的寫請求;對 MyISAM 表的寫操作,則會阻塞其他用戶對同一個表的讀和寫操作;MyISAM 表的讀操作與寫操作之間,以及寫操作與寫操作之間時串行的。
并發插入(Concurrent Inserts):MyISAM 表的讀和寫是串行的,但這是就總體而言的,在一定的條件下,MyISAM 表也可以支持查詢和插入操作的并發進行;MyISAM 存儲引擎有一個系統變量 concurrent_insert,專門用以控制其并發插入的行為,其值分為可以為 0、1/2。當 concurrent_insert 設置為 0 時,則不允許并發插入;當 concurrent_insert 設置為 1 時,如果 MyISAM 表中沒有空洞(即表的中間沒有被刪除的行),MyISAM 允許在一個進程讀表的同時,另一個進程從表尾插入記錄,這也是 MySQL 的默認設置;當 concurrent_insert 設置為 2 時,無論 MyISAM 表中有沒有空洞,都允許在表尾并發插入記錄。可以利用 MyISAM 存儲引擎此并發插入特性,來解決應用中對同一個表查詢和插入的鎖爭用。例如:將 concurrent_insert 變量的值設為 2,總是允許并發插入操作,同時通過定期在系統空閑時段執行 OPTIMIZE TABLE 語句來整理空間碎片,回收因刪除記錄而產生的中間空洞。
MyISAM 引擎的鎖調度:MyISAM 存儲引擎的讀鎖和寫鎖是互斥的,讀寫操作時串行的。一個進程請求某個 MyISAM 表的讀鎖,同時另一個進程也請求同一個表的寫鎖,寫的優先級比讀的優先級更高,所以寫進程會先獲得鎖,即使讀請求先到鎖的等待隊列中,寫請求后到鎖的等待隊列中,寫鎖頁回插入到讀鎖請求之前執行;我們可以通過一些設置來調節 MyISAM 的調度行為,通過指定啟動參數 low-priority-updates,使 MyISAM 引擎默認給予讀請求以優先的權利;通過執行命令 SET LOW_PRIORITY_UPDATE=1,使該連接發出的更新請求優先級降低;通過指定 INSERT、UPDATE、DELETE 語句的 LOW_PRIORITY 屬性,降低該語句的優先級。另外,MySQL 也提供了一種折中的辦法來調節讀寫沖突,即給系統參數 max_write_lock_count 設置一個合適的值,當一個表的讀鎖達到這個值后,MySQL 就暫時將寫請求的優先級降低,給讀進程一定的獲得鎖的機會。
四、InnoDB 存儲引擎:
樂觀鎖與悲觀鎖是兩種并發控制的思想,可用于解決丟失更新的問題:樂觀鎖會 樂觀的 假定大概率不會發生并發更新沖突,訪問、處理數據過程中不加鎖,只在更新數據時再根據版本號或時間戳判斷是否有沖突,有則處理,無則提交事務;悲觀鎖會 悲觀的 假定大概率會發生并發更新沖突,訪問、處理數據前就加排他鎖,在整個數據處理過程中鎖定數據,事務提交或回滾后才釋放鎖;
InnoDB 存儲引擎標準的行級鎖:共享鎖(S Lock):讀鎖,允許事務讀一行數據;排他鎖(X Lock):寫鎖,允許事務刪除或更新一行數據;
## 事務 1 MariaDB [test] show variables like autocommit +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.00 sec) MariaDB [test] begin; Query OK, 0 rows affected (0.00 sec) MariaDB [test] update tb1 set name= aaa where id=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [test] commit; Query OK, 0 rows affected (0.00 sec) ## 事務 2: MariaDB [test] show variables like autocommit +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.00 sec) MariaDB [test] begin; Query OK, 0 rows affected (0.00 sec) MariaDB [test] update tb1 set name= haha where id=1; Query OK, 1 row affected (12.89 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [test] rollback; Query OK, 0 rows affected (0.00 sec) MariaDB [test] select * from tb1 where id=1; +----+------+ | id | name | +----+------+ | 1 | aaa | +----+------+ 1 row in set (0.00 sec)
InnoDB 行鎖的實現方式:
InnoDB 行鎖是通過給索引上的索引項加鎖的,InnoDB 這種行鎖實現特點意味著:只有通過索引條件檢索數據,InnoDB 才使用行級鎖,否則 InnoDB 將使用表鎖。在不通過索引條件查詢的時候,InnoDB 確實是使用表鎖而不是行鎖;由于 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖沖突的;當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,無論是使用主鍵索引、唯一索引或普通索引,InnoDB 都會使用行鎖來對數據加鎖;即便是在條件中使用了索引字段,但是否使用索引來檢索數據是由 MySQL 通過判斷不同執行計劃的代價來決定的,如果 MySQL 認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 InnoDB 將使用表鎖而不是行鎖。
意向鎖:意向共享鎖(IS Lock):事務想要獲得一張表中某幾行的共享鎖;意向排他鎖(IX Lock):事務想要獲得一張表中某幾行的排它鎖;查看 InnoDB 存儲引擎的鎖信息:
MariaDB [test] show engine innodb status\G;
MariaDB [test] show engine innodb status\G;
MariaDB [test] select * from information_schema.innodb_trx\G; *************************** 1. row *************************** trx_id: 1266629 trx_state: RUNNING trx_started: 2020-01-08 16:24:50 trx_requested_lock_id: NULL trx_wait_started: NULL trx_weight: 0 trx_mysql_thread_id: 36696 trx_query: select * from information_schema.innodb_trx trx_operation_state: NULL trx_tables_in_use: 0 trx_tables_locked: 0 trx_lock_structs: 0 trx_lock_memory_bytes: 376 trx_rows_locked: 0 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: REPEATABLE READ trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 10000 1 row in set (0.00 sec)
MariaDB [test] select * from information_schema.innodb_locks\G; Empty set (0.00 sec)
MariaDB [test] select * from information_schema.innodb_lock_waits\G; Empty set (0.00 sec)
一致性的非鎖定讀 (Consistent Nonlocking Read): 是指 InnoDB 存儲引擎通過很多個版本控制(multi versioning) 的方式來讀取當前執行時間數據庫中的行的數據。如果讀取的行正在執行 DELETE 或 UPDATE 操作,這時讀取操作不會因此去等待行上的鎖的釋放;相反,InnoDB 存儲引擎會去讀取行的一個快照數據,快照數據是指該行的之前的版本的數據,該實現是通過 undo 段來完成的。而 undo 用來事務中國回滾數據,因此快照數據本身是沒有額外的開銷。此外,讀取快照數據是不需要上鎖的,因為沒有事務需要對歷史數據進行修改操作。然而在不同的事務隔離級別下,對于快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據,而在 REPEATABLE READ 事務隔離級別下,對于快照數據,非一致性讀總是讀取事務開始時的行數據版本。
一致性的鎖定讀:顯示地對數據庫讀取操作進行加鎖以保證數據邏輯的一致性;SELECT … FOR UPDATE:對讀取的行記錄加一個 X 鎖,其他事務不能對已鎖定的行加任何的鎖;SELECT … LOCK IN SHARE MODE:對讀取的行記錄加一個 S 鎖,其他事務可以向被鎖定的行加 S 鎖,但是如果加 X 鎖,則會被阻塞;
## 事務 1 MariaDB [test] begin; Query OK, 0 rows affected (0.00 sec) MariaDB [test] select * from tb1 where id=1 for update; +----+------+ | id | name | +----+------+ | 1 | aaa | +----+------+ 1 row in set (0.00 sec) MariaDB [test] rollback; Query OK, 0 rows affected (0.00 sec) ## 事務 2 MariaDB [test] begin; Query OK, 0 rows affected (0.00 sec) MariaDB [test] select * from tb1 where id=1 lock in share mode; +----+------+ | id | name | +----+------+ | 1 | aaa | +----+------+ 1 row in set (11.55 sec) MariaDB [test] rollback; Query OK, 0 rows affected (0.00 sec)
鎖算法:
五、死鎖
死鎖是指兩個或兩個以上的事務在執行過程中,因爭奪資源而造成的一種互相等待的現象;MyISAM 表鎖是 Deadlock Free 的,這時因為 MyISAM 總是一次獲得所需的全部鎖,要么全部滿足,要么等待,因此不會出現死鎖。但在 InnoDB 中,除單個 SQL 組成的事務外,鎖是逐步獲得的,這就決定了在 InnoDB 中發生死鎖是可能的。發生死鎖后,InnoDB 一般都能自動檢測到,并使一個事務釋放鎖并回退,另外一個事務獲得鎖,繼續完成事務。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB 并不能完全自動檢測到死鎖,這需要通過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決,需要說明的是,這個參數并不是用來解決死鎖問題,在并發訪問比較高的情況下,如果大量事務因無法立即獲得所需的鎖而掛起,會占用大量計算機資源,造成嚴重性能問題,甚至拖垮數據庫。我們通過設置合適的鎖等待超時閾值,可以避免這種情況的發生。
## 事務 1 MariaDB [test] begin; Query OK, 0 rows affected (0.00 sec) MariaDB [test] update tb1 set name= jyy where id=1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [test] update tb1 set name= xixi where id=2; Query OK, 1 row affected (8.25 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [test] commit; Query OK, 0 rows affected (0.00 sec) MariaDB [test] select * from tb1 where id in(1,2); +----+------+ | id | name | +----+------+ | 1 | jyy | | 2 | xixi | +----+------+ 2 rows in set (0.00 sec) ## 事務 2 MariaDB [test] begin; Query OK, 0 rows affected (0.00 sec) MariaDB [test] update tb1 set name= haha where id=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [test] update tb1 set name= heihei where id=1; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
避免死鎖的常用方法:
1)在應用中,如果不同的程序會并發存取多個表,應該盡量約定以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會。在上面的例子中,由于兩個 session 訪問表的順序不同,發生死鎖的機會就非常高,但是如果以相同的順序來訪問,死鎖就可以避免;
2)在程序以批量方式處理數據的時候,如果事先對數據排序,保證每個線程按固定的順序來處理記錄,也可以大大降低出現死鎖的可能;
3)在事務中,如果要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不應該先申請共享鎖,從而造成鎖沖突,甚至死鎖;
4)在 REPEATABLE-READ 隔離級別下,如果兩個線程同時對相同條件記錄用 SELECT…FOR UPDATE 加排他鎖,在沒有符合該條件記錄情況下,兩個線程都會加鎖成功。程序發現記錄尚不存在,就試圖插入一條記錄,如果兩個線程都這么做,就會出現死鎖,這種情況下,將隔離級別 READ COMMITTED 就可以避免問題;
5)當隔離級別為 READ COMMITTED 時,如果兩個線程都先執行 SELECT…FOR UPDATE,判斷是否存在符合條件的記錄,如果沒有,就插入記錄。此時,只有一個線程能插入成功,另外一個線程就會出現鎖等待,當第一個線程提交后,第二個線程會因為主鍵沖突出錯,但雖然這個線程出錯了,卻會獲得一個排他鎖,這時如果有第三個線程又來申請排它鎖,也會出現死鎖。
6)如果出現了死鎖,可以使用上面的檢查鎖信息的 SQL 命令來確定最后一個死鎖產生的原因。返回結果中國包括死鎖相關的事務的詳細信息,如引發死鎖的 SQL 語句,事務已經獲得的鎖,正在等待什么鎖,以及被回滾的事務等。據此可以分析死鎖產生的原因和改進措施。
以上就是 MySQL 中鎖機制的底層原理是什么,丸趣 TV 小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注丸趣 TV 行業資訊頻道。