MySQL各存儲引擎使用了三種類型(級別)的鎖定機制:行級鎖定,頁級鎖定和表級鎖定:
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。如:MyISAM和MEMORY存儲引擎;行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。如:BDB存儲引擎;頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般。如:InnoDB存儲引擎。MySQL中兩種使用最為頻繁的存儲引擎MyISAM和Innodb各自的鎖定機制:
MyISAM表級鎖:
加寫鎖:lock tables table_name read; // 其他事務只能讀,不能加寫鎖,要等待更新。加讀鎖:lock tables table_name write; // 其他事務不能讀Innodb行級鎖(行鎖是對索引加鎖):
當一個事務需要給自己需要的某個資源加鎖的時候,如果遇到一個共享鎖正鎖定著自己需要的資源的時候,自己可以再加一個共享鎖,不過不能加排他鎖。但是,如果遇到自己需要鎖定的資源已經被一個排他鎖占有之后,則只能等待該鎖定釋放資源之后自己才能獲取鎖定資源并添加自己的鎖定。而意向鎖的作用就是當一個事務在需要獲取資源鎖定的時候,如果遇到自己需要的資源已經被排他鎖占用的時候,該事務可以需要鎖定行的表上面添加一個合適的意向鎖。如果自己需要一個共享鎖,那么就在表上面添加一個意向共享鎖。而如果自己需要的是某行(或者某些行)上面添加一個排他鎖的話,則先在表上面添加一個意向排他鎖。意向共享鎖可以同時并存多個,但是意向排他鎖同時只能有一個存在。所以,可以說Innodb的鎖定模式實際上可以分為四種:共享鎖(S),排他鎖(X),意向共享鎖(IS)和意向排他鎖(IX),我們可以通過以下表格來總結上面這四種所的共存邏輯關系:
| 鎖 | 共享鎖(S) | 排他鎖(X) | 意向共享鎖(IS) | 意向排他鎖(IX) |
|---|---|---|---|---|
| 共享鎖(S) | 兼容 | 沖突 | 兼容 | 沖突 |
| 排他鎖(X) | 沖突 | 沖突 | 沖突 | 沖突 |
| 意向共享鎖(IS) | 兼容 | 沖突 | 兼容 | 兼容 |
| 意向排他鎖(IX) | 沖突 | 沖突 | 兼容 | 兼容 |
Innodb的鎖定則是通過在指向數據記錄的第一個索引鍵之前和最后一個索引鍵之后的空域空間上標記鎖定信息而實現的。Innodb的這種鎖定實現方式被稱為“NEXT-KEYlocking”(間隙鎖),因為Query執行過程中通過過范圍查找的華,他會鎖定整個范圍內所有的索引鍵值,即使這個鍵值并不存在。間隙鎖有一個比較致命的弱點,就是當鎖定一個范圍鍵值之后,即使某些不存在的鍵值也會被無辜的鎖定,而造成在鎖定的時候無法插入鎖定鍵值范圍內的任何數據。在某些場景下這可能會對性能造成很大的危害。而Innodb給出的解釋是為了組織幻讀的出現,所以他們選擇的間隙鎖來實現鎖定。 除了間隙鎖給Innodb帶來性能的負面影響之外,通過索引實現鎖定的方式還存在其他幾個較大的性能隱患:
當Query無法利用索引的時候,Innodb會放棄使用行級別鎖定而改用表級別的鎖定,造成并發性能的降低;
當Query使用的索引并不包含所有過濾條件的時候,數據檢索使用到的索引鍵所只想的數據可能有部分并不屬于該Query的結果集的行列,但是也會被鎖定,因為間隙鎖鎖定的是一個范圍,而不是具體的索引鍵;
當Query在使用索引定位數據的時候,如果使用的索引鍵一樣但訪問的數據行不同的時候(索引只是過濾條件的一部分),一樣會被鎖定。
行鎖是對索引加鎖(egg):
例子1:
事務1:set autocommit = off; update test set num = 200 where id = 5; 事務2:select * from test where id=5; // 可讀例子2:
事務1:set autocommit = off; update test set num = 200 where id = 5;事務2:set autocommit = off; update test set num = 200 where id = 5; // 事務2等待事務1提交了才能更新。例子3:
事務1:set autocommit = off; update test set num = 200 where id = 5; 事務2:set autocommit = off; update test set num = 200 where id = 6; // 事務2不用等待例子4:
事務1:set autocommit = off; update test set num = 200 where id = 5; 事務2:insert test (id) value (5); // 事務2等待,事務1提交后,事務2報錯Duplicate entry '5' for key '例子5:事務1:set autocommit = off; update test set num = 200 where id = 5; 事務2:insert test (id) value (6); // 事務2不用等待;例子6:
事務1:insert test (id) value (8); 事務2:insert test (id) value (9); // 事務2不用等待;新聞熱點
疑難解答