MySQL中表鎖和行鎖機(jī)制淺析(源碼篇)
前言
眾所周知,MySQL的存儲引擎有MyISAM和InnoDB,鎖粒度分別是表鎖和行鎖。
后者的出現(xiàn)從某種程度上是彌補(bǔ)前者的不足,比如:MyISAM不支持事務(wù),InnoDB支持事務(wù)。表鎖雖然開銷小,鎖表快,但高并發(fā)下性能低。行鎖雖然開銷大,鎖表慢,但高并發(fā)下相比之下性能更高。事務(wù)和行鎖都是在確保數(shù)據(jù)準(zhǔn)確的基礎(chǔ)上提高并發(fā)的處理能力。下面分別進(jìn)行介紹:
行鎖
行鎖的劣勢:
- 開銷大;
- 加鎖慢;
- 會出現(xiàn)死鎖
行鎖的優(yōu)勢:
- 鎖的粒度小,發(fā)生鎖沖突的概率低;
- 處理并發(fā)的能力強(qiáng)
加鎖的方式:
- 自動(dòng)加鎖:對于UPDATE、DELETE和INSERT語句,InnoDB會自動(dòng)給涉及數(shù)據(jù)集加排他鎖;
- 無鎖:對于普通SELECT語句,InnoDB不會加任何鎖;當(dāng)然我們也可以顯示的加鎖:
- 共享鎖:select * from tableName where ... + lock in share more
- 排他鎖:select * from tableName where ... + for update
InnoDB和MyISAM的最大不同點(diǎn)有兩個(gè):
- InnoDB支持事務(wù)(transaction);
- 默認(rèn)采用行級鎖。
加鎖可以保證事務(wù)的一致性,下面我們來學(xué)習(xí)一下MySQL的事務(wù)知識.
MySQL 事務(wù)屬性
事務(wù)是由一組SQL語句組成的邏輯處理單元,事務(wù)具有ACID屬性。 原子性(Atomicity):事務(wù)是一個(gè)原子操作單元。在當(dāng)時(shí)原子是不可分割的最小元素,其對數(shù)據(jù)的修改,要么全部成功,要么全部都不成功。 一致性(Consistent):事務(wù)開始到結(jié)束的時(shí)間段內(nèi),數(shù)據(jù)都必須保持一致狀態(tài)。 隔離性(Isolation):數(shù)據(jù)庫系統(tǒng)提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的"獨(dú)立"環(huán)境執(zhí)行。 持久性(Durable):事務(wù)完成后,它對于數(shù)據(jù)的修改是永久性的,即使出現(xiàn)系統(tǒng)故障也能夠保持。
事務(wù)常見問題
更新丟失(Lost Update) 原因:當(dāng)多個(gè)事務(wù)選擇同一行操作,并且都是基于最初選定的值,由于每個(gè)事務(wù)都不知道其他事務(wù)的存在,就會發(fā)生更新覆蓋的問題。類比github提交沖突。
臟讀(Dirty Reads) 原因:事務(wù)A讀取了事務(wù)B已經(jīng)修改但尚未提交的數(shù)據(jù)。若事務(wù)B回滾數(shù)據(jù),事務(wù)A的數(shù)據(jù)存在不一致性的問題。
不可重復(fù)讀(Non-Repeatable Reads) 原因:事務(wù)A第一次讀取最初數(shù)據(jù),第二次讀取事務(wù)B已經(jīng)提交的修改或刪除數(shù)據(jù)。導(dǎo)致兩次讀取數(shù)據(jù)不一致。不符合事務(wù)的隔離性。
幻讀(Phantom Reads) 原因:事務(wù)A根據(jù)相同條件第二次查詢到事務(wù)B提交的新增數(shù)據(jù),兩次數(shù)據(jù)結(jié)果集不一致。不符合事務(wù)的隔離性。
幻讀和臟讀有點(diǎn)類似 臟讀是事務(wù)B里面修改了數(shù)據(jù), 幻讀是事務(wù)B里面新增了數(shù)據(jù)。
事務(wù)的隔離級別
數(shù)據(jù)庫的事務(wù)隔離越嚴(yán)格,并發(fā)副作用越小,但付出的代價(jià)也就越大。這是因?yàn)槭聞?wù)隔離實(shí)質(zhì)上是將事務(wù)在一定程度上"串行"進(jìn)行,這顯然與"并發(fā)"是矛盾的。根據(jù)自己的業(yè)務(wù)邏輯,權(quán)衡能接受的最大副作用。從而平衡了"隔離" 和 "并發(fā)"的問題。MySQL默認(rèn)隔離級別是可重復(fù)讀。 臟讀,不可重復(fù)讀,幻讀,其實(shí)都是數(shù)據(jù)庫讀一致性問題,必須由數(shù)據(jù)庫提供一定的事務(wù)隔離機(jī)制來解決。
+------------------------------+---------------------+--------------+--------------+--------------+ | 隔離級別 | 讀數(shù)據(jù)一致性 | 臟讀 | 不可重復(fù) 讀 | 幻讀 | +------------------------------+---------------------+--------------+--------------+--------------+ | 未提交讀(Read uncommitted) | 最低級別 | 是 | 是 | 是 | +------------------------------+---------------------+--------------+--------------+--------------+ | 已提交讀(Read committed) | 語句級 | 否 | 是 | 是 | +------------------------------+---------------------+--------------+--------------+--------------+ | 可重復(fù)讀(Repeatable read) | 事務(wù)級 | 否 | 否 | 是 | +------------------------------+---------------------+--------------+--------------+--------------+ | 可序列化(Serializable) | 最高級別,事務(wù)級 | 否 | 否 | 否 | +------------------------------+---------------------+--------------+--------------+--------------+
查看當(dāng)前數(shù)據(jù)庫的事務(wù)隔離級別:show variables like 'tx_isolation';
mysql> show variables like 'tx_isolation'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+
間隙鎖
當(dāng)我們用范圍條件檢索數(shù)據(jù),并請求共享或排他鎖時(shí),InnoDB會給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做"間隙(GAP)"。InnoDB也會對這個(gè)"間隙"加鎖,這種鎖機(jī)制就是所謂的間隙鎖(Next-Key鎖)。
Transaction-A mysql> update innodb_lock set k=66 where id >=6; Query OK, 1 row affected (0.63 sec) mysql> commit; ? Transaction-B mysql> insert into innodb_lock (id,k,v) values(7,'7','7000'); Query OK, 1 row affected (18.99 sec)
危害(坑):若執(zhí)行的條件是范圍過大,則InnoDB會將整個(gè)范圍內(nèi)所有的索引鍵值全部鎖定,很容易對性能造成影響。
排他鎖
排他鎖,也稱寫鎖,獨(dú)占鎖,當(dāng)前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
# Transaction_A mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 for update; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4000 | +----+------+------+ 1 row in set (0.00 sec) ? mysql> update innodb_lock set v='4001' where id=4; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 ? mysql> commit; Query OK, 0 rows affected (0.04 sec) # Transaction_B mysql> select * from innodb_lock where id=4 for update; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (9.53 sec)
共享鎖
共享鎖,也稱讀鎖,多用于判斷數(shù)據(jù)是否存在,多個(gè)讀操作可以同時(shí)進(jìn)行而不會互相影響。當(dāng)如果事務(wù)對讀鎖進(jìn)行修改操作,很可能會造成死鎖。如下圖所示。
# Transaction_A mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 lock in share mode; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (0.00 sec) ? mysql> update innodb_lock set v='4002' where id=4; Query OK, 1 row affected (31.29 sec) Rows matched: 1 Changed: 1 Warnings: 0 # Transaction_B mysql> set autocommit=0; mysql> select * from innodb_lock where id=4 lock in share mode; +----+------+------+ | id | k | v | +----+------+------+ | 4 | 4 | 4001 | +----+------+------+ 1 row in set (0.00 sec) ? mysql> update innodb_lock set v='4002' where id=4; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
分析行鎖定
通過檢查InnoDB_row_lock 狀態(tài)變量分析系統(tǒng)上的行鎖的爭奪情況 show status like 'innodb_row_lock%'
mysql> show status like 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+
innodb_row_lock_current_waits: 當(dāng)前正在等待鎖定的數(shù)量 innodb_row_lock_time: 從系統(tǒng)啟動(dòng)到現(xiàn)在鎖定總時(shí)間長度;非常重要的參數(shù), innodb_row_lock_time_avg: 每次等待所花平均時(shí)間;非常重要的參數(shù), innodb_row_lock_time_max: 從系統(tǒng)啟動(dòng)到現(xiàn)在等待最常的一次所花的時(shí)間; innodb_row_lock_waits: 系統(tǒng)啟動(dòng)后到現(xiàn)在總共等待的次數(shù);非常重要的參數(shù)。直接決定優(yōu)化的方向和策略。
行鎖優(yōu)化
1 盡可能讓所有數(shù)據(jù)檢索都通過索引來完成,避免無索引行或索引失效導(dǎo)致行鎖升級為表鎖。 2 盡可能避免間隙鎖帶來的性能下降,減少或使用合理的檢索范圍。 3 盡可能減少事務(wù)的粒度,比如控制事務(wù)大小,而從減少鎖定資源量和時(shí)間長度,從而減少鎖的競爭等,提供性能。 4 盡可能低級別事務(wù)隔離,隔離級別越高,并發(fā)的處理能力越低。
表鎖
表鎖的優(yōu)勢:開銷??;加鎖快;無死鎖 表鎖的劣勢:鎖粒度大,發(fā)生鎖沖突的概率高,并發(fā)處理能力低 加鎖的方式:自動(dòng)加鎖。查詢操作(SELECT),會自動(dòng)給涉及的所有表加讀鎖,更新操作(UPDATE、DELETE、INSERT),會自動(dòng)給涉及的表加寫鎖。也可以顯示加鎖: 共享讀鎖:lock table tableName read; 獨(dú)占寫鎖:lock table tableName write; 批量解鎖:unlock tables;
共享讀鎖
對MyISAM表的讀操作(加讀鎖),不會阻塞其他進(jìn)程對同一表的讀操作,但會阻塞對同一表的寫操作。只有當(dāng)讀鎖釋放后,才能執(zhí)行其他進(jìn)程的寫操作。在鎖釋放前不能取其他表。
Transaction-A mysql> lock table myisam_lock read; Query OK, 0 rows affected (0.00 sec) ? mysql> select * from myisam_lock; 9 rows in set (0.00 sec) ? mysql> select * from innodb_lock; ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES ? mysql> update myisam_lock set v='1001' where k='1'; ERROR 1099 (HY000): Table 'myisam_lock' was locked with a READ lock and can't be updated ? mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) Transaction-B mysql> select * from myisam_lock; 9 rows in set (0.00 sec) ? mysql> select * from innodb_lock; 8 rows in set (0.01 sec) ? mysql> update myisam_lock set v='1001' where k='1'; Query OK, 1 row affected (18.67 sec)
獨(dú)占寫鎖
對MyISAM表的寫操作(加寫鎖),會阻塞其他進(jìn)程對同一表的讀和寫操作,只有當(dāng)寫鎖釋放后,才會執(zhí)行其他進(jìn)程的讀寫操作。在鎖釋放前不能寫其他表。
Transaction-A mysql> set autocommit=0; Query OK, 0 rows affected (0.05 sec) ? mysql> lock table myisam_lock write; Query OK, 0 rows affected (0.03 sec) ? mysql> update myisam_lock set v='2001' where k='2'; Query OK, 1 row affected (0.00 sec) ? mysql> select * from myisam_lock; 9 rows in set (0.00 sec) ? mysql> update innodb_lock set v='1001' where k='1'; ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES ? mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) Transaction-B mysql> select * from myisam_lock; 9 rows in set (42.83 sec)
總結(jié):表鎖,讀鎖會阻塞寫,不會阻塞讀。而寫鎖則會把讀寫都阻塞。
查看加鎖情況
show open tables; 1表示加鎖,0表示未加鎖。
mysql> show open tables where in_use > 0; +----------+-------------+--------+-------------+ | Database | Table | In_use | Name_locked | +----------+-------------+--------+-------------+ | lock | myisam_lock | 1 | 0 | +----------+-------------+--------+-------------+
分析表鎖定
可以通過檢查table_locks_waited 和 table_locks_immediate 狀態(tài)變量分析系統(tǒng)上的表鎖定:show status like 'table_locks%'
mysql> show status like 'table_locks%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Table_locks_immediate | 104 | | Table_locks_waited | 0 | +----------------------------+-------+
table_locks_immediate: 表示立即釋放表鎖數(shù)。 table_locks_waited: 表示需要等待的表鎖數(shù)。此值越高則說明存在著越嚴(yán)重的表級鎖爭用情況。
此外,MyISAM的讀寫鎖調(diào)度是寫優(yōu)先,這也是MyISAM不適合做寫為主表的存儲引擎。因?yàn)閷戞i后,其他線程不能做任何操作,大量的更新會使查詢很難得到鎖,從而造成永久阻塞。
什么場景下用表鎖
InnoDB默認(rèn)采用行鎖,在未使用索引字段查詢時(shí)升級為表鎖。MySQL這樣設(shè)計(jì)并不是給你挖坑。它有自己的設(shè)計(jì)目的。 即便你在條件中使用了索引字段,MySQL會根據(jù)自身的執(zhí)行計(jì)劃,考慮是否使用索引(所以explain命令中會有possible_key 和 key)。如果MySQL認(rèn)為全表掃描效率更高,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時(shí),別忘了檢查SQL的執(zhí)行計(jì)劃,以確認(rèn)是否真正使用了索引。
第一種情況:全表更新。事務(wù)需要更新大部分或全部數(shù)據(jù),且表又比較大。若使用行鎖,會導(dǎo)致事務(wù)執(zhí)行效率低,從而可能造成其他事務(wù)長時(shí)間鎖等待和更多的鎖沖突。
第二種情況:多表級聯(lián)。事務(wù)涉及多個(gè)表,比較復(fù)雜的關(guān)聯(lián)查詢,很可能引起死鎖,造成大量事務(wù)回滾。這種情況若能一次性鎖定事務(wù)涉及的表,從而可以避免死鎖、減少數(shù)據(jù)庫因事務(wù)回滾帶來的開銷。
頁鎖
開銷和加鎖時(shí)間介于表鎖和行鎖之間;會出現(xiàn)死鎖;鎖定粒度介于表鎖和行鎖之間,并發(fā)處理能力一般。只需了解一下。
補(bǔ)充:行級鎖與死鎖
MyISAM中是不會產(chǎn)生死鎖的,因?yàn)镸yISAM總是一次性獲得所需的全部鎖,要么全部滿足,要么全部等待。而在InnoDB中,鎖是逐步獲得的,就造成了死鎖的可能。
在MySQL中,行級鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關(guān)的主鍵索引。 在UPDATE、DELETE操作時(shí),MySQL不僅鎖定WHERE條件掃描過的所有索引記錄,而且會鎖定相鄰的鍵值,即所謂的next-key locking。
當(dāng)兩個(gè)事務(wù)同時(shí)執(zhí)行,一個(gè)鎖住了主鍵索引,在等待其他相關(guān)索引。另一個(gè)鎖定了非主鍵索引,在等待主鍵索引。這樣就會發(fā)生死鎖。
發(fā)生死鎖后,InnoDB一般都可以檢測到,并使一個(gè)事務(wù)釋放鎖回退,另一個(gè)獲取鎖完成事務(wù)。
有多種方法可以避免死鎖,這里只介紹常見的三種
1、如果不同程序會并發(fā)存取多個(gè)表,盡量約定以相同的順序訪問表,可以大大降低死鎖機(jī)會。
2、在同一個(gè)事務(wù)中,盡可能做到一次鎖定所需要的所有資源,減少死鎖產(chǎn)生概率;
3、對于非常容易產(chǎn)生死鎖的業(yè)務(wù)部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產(chǎn)生的概率;
總結(jié)
1 InnoDB 支持表鎖和行鎖,使用索引作為檢索條件修改數(shù)據(jù)時(shí)采用行鎖,否則采用表鎖。 2 InnoDB 自動(dòng)給修改操作加鎖,給查詢操作不自動(dòng)加鎖 3 行鎖可能因?yàn)槲词褂盟饕墳楸礞i,所以除了檢查索引是否創(chuàng)建的同時(shí),也需要通過explain執(zhí)行計(jì)劃查詢索引是否被實(shí)際使用。 4 行鎖相對于表鎖來說,優(yōu)勢在于高并發(fā)場景下表現(xiàn)更突出,畢竟鎖的粒度小。 5 當(dāng)表的大部分?jǐn)?shù)據(jù)需要被修改,或者是多表復(fù)雜關(guān)聯(lián)查詢時(shí),建議使用表鎖優(yōu)于行鎖。 6 為了保證數(shù)據(jù)的一致完整性,任何一個(gè)數(shù)據(jù)庫都存在鎖定機(jī)制。鎖定機(jī)制的優(yōu)劣直接影響到一個(gè)數(shù)據(jù)庫的并發(fā)處理能力和性能。
到此這篇關(guān)于MySQL中表鎖和行鎖機(jī)制淺析的文章就介紹到這了,更多相關(guān)MySQL表鎖和行鎖機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL中進(jìn)行數(shù)據(jù)的加密和解密存儲的操作方法
在MySQL中,我們可以使用對稱加密算法和非對稱加密算法對數(shù)據(jù)進(jìn)行加密和解密存儲,通過合理選擇加密算法和密鑰管理,可以有效提高數(shù)據(jù)的安全性,防止敏感信息的泄露和非法獲取,下面給大家分享MySQL中進(jìn)行數(shù)據(jù)的加密和解密存儲方法,感興趣的朋友一起看看吧2024-05-05Mysql合并結(jié)果接橫向拼接字段的實(shí)現(xiàn)步驟
這篇文章主要給大家介紹了關(guān)于Mysql合并結(jié)果接橫向拼接字段的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01MySQL count(1)、count(*)、count(字段)的區(qū)別
COUNT在數(shù)據(jù)庫行數(shù)統(tǒng)計(jì)中被廣泛使用,那么你知道MySQL count(1)、count(*)、count(字段)的區(qū)別嗎,本文就想的介紹一下,感興趣的可以了解一下2021-12-12mysql/Java服務(wù)端對emoji的支持與問題解決方法詳解
這篇文章主要介紹了mysql/Java服務(wù)端對emoji的支持與問題解決方法,結(jié)合實(shí)例形式分析了mysql/Java服務(wù)端對emoji字符集存儲及支持問題解決方法,需要的朋友可以參考下2019-10-10MySql版本問題sql_mode=only_full_group_by的完美解決方案
這篇文章主要介紹了MySql版本問題sql_mode=only_full_group_by的完美解決方案,需要的朋友可以參考下2017-07-07