1. MySQL鎖概論:
Mysql的鎖機制比較簡單,其最顯著的特定就是:不同存儲引擎支持不同的鎖機制!!!
MyISAM和MEMORY存儲引擎采用的是表級鎖(table-level locking);BDB存儲引擎采用的是頁面鎖(page-level locking),但也支持表級鎖;InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但是默認情況下采用行級鎖。
那么什么叫做表級鎖、行級鎖、頁面鎖呢?
-
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖粒度大。發生沖突幾率最大,並發度最低。
-
行級鎖:開銷大,加鎖慢;會出現死鎖,發生鎖沖突幾率最低,並發度最高。
-
頁面鎖:介於表級鎖和行級鎖之間。
那選用那種鎖最好呢?
從上述特點來說,無法確切的說出那種鎖更好,只能根據具體應用的特點來說那種最合適。
-
表級鎖:更適合於查詢為主的場景。
-
行級鎖:更適合於大量按索引條件 並發更新 少量不同的數據,同時又有 並發查詢 的應用。
1.1那么mysql何時使用MyISAM,什么時候使用InnoDB
INNODB和MyISAM是mysql數據庫提供的兩種存儲引擎。INNODB會支持一些關系數據庫的高級功能,如事務功能和行級鎖;MyISAM不支持,但是MyISAM的性能更優,占用的空間少。
如果應用程序一定要使用事務,那么要選擇INNODB引擎。但是INNODB的行級鎖是有條件的。在where條件沒有使用主鍵時,照樣會鎖住全表。比如delete from mytable
若是應用程序對查詢性能要求高,就要使用MyISAM。MyISAM索引和數據是分開的,而且索引是壓縮的,可以更好的利用內存。所以它的查詢性能明細優於Innodb。MyISAM擁有全文索引的功能,這可以極大的優化like查詢的效率。
InnoDB什么時候使用表鎖,什么時候使用行鎖?
- 事務需要更新大部分或者全部數據,表又比較大。如果使用默認的行鎖,那么不僅事務效率低,而且可能造成其他事務長時間鎖等待和鎖沖突,這種情況下可以考慮使用表鎖來提高事務的執行速度。
- 事務設計到多個表,比較復雜,很可能造成死鎖,造成大量事務回滾。也可以考慮一次性鎖定事務涉及到的表,從而避免死鎖,減少數據庫事務回滾的開銷。
當然應用中若是1,2比較多,那么就可以考慮使用MyISAM表了。
MyISAM和InnoDB的區別:
- InnoDB支持事務和外鍵以及行級鎖,MyISAM不支持。
- MyISAM讀性能優於InnoDB。
- MyISAM索引和數據是分開的,而且索引是壓縮的,而InnoDB索引和數據是緊密捆綁在一起的,無法壓縮,所以InnoDB的體積比MyISAM龐大。
- MyISAM引擎索引結構的葉子節點的數據域,存放的並不是實際的數據類型,而是數據記錄的地址。索引文件與數據文件分離,這樣的索引稱之:“非聚簇索引”。
- InnoDB引擎索引結構的葉子節點的數據域,存放是就是實際的數據記錄。這樣的索引被稱為“聚簇索引”,一個表只能有一個聚簇索引。
- InnoDB並不保存表的具體行數,也就是說,執行
select count(*) from table時,InnoDB要掃描整個表來計算有多少行,但是MyISAM只要簡單讀出保存好的行數即可。注意的是,當count(*)語句包含where條件時,兩個表的操作是一樣的。 - InnoDB表的行鎖也不是絕對的,假如在執行一個
SQL語句時MySQL不能確定掃描的范圍,InnoDB表同樣也會鎖全表。
在where條件沒有主鍵時,InnoDB照樣會鎖全表。
2. MyISAM和MEMORY引擎的表鎖
MySQL表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨占寫鎖(Table Write Lock)。
對於MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀操作。但會阻塞對同一個表的寫操作。
對於MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀操作和寫操作。
2.1 如何加表鎖
MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,再執行查詢操作。
(update、delete、insert)前,會自動給涉及到的表加寫鎖。用戶一般不需要顯示加鎖。
2.2 如何優化MyISAM表鎖
對於MyISAM存儲引擎,雖然使用表級鎖加鎖和解鎖的開銷最小,但是由於加鎖粒度大,所以會造成更多的資源爭用的情況。會較大程度上降低並發處理能力。
優化MyISAM存儲引擎關鍵是提高並發度。
2.2.1 查詢表級鎖爭用情況:
使用show status like 'table%'可以查詢系統內部鎖資源爭用的情況。
Table_locks_immediate:產生表級鎖定的次數;
Table_locks_waited:出現表級鎖定爭用而等待的次數。
兩個參數值都是系統啟動后開始記錄的,出現一次對應的事件,則數量加一。當Table_locks_waited狀態值比較高時,那么說明系統中表級鎖定爭用現象比較明顯。
2.2.2 縮短鎖定時間:
如何讓鎖定時間盡可能的短呢?唯一的辦法就是讓我們的Query執行的時間盡可能短。
- 盡量減少大的復雜的sql,將復雜的query分拆成為小的query分步進行。
- 盡可能建立高效索引,讓數據檢索更迅速。
- 盡量讓
MyISAM存儲引擎的表只存放必要信息,控制字段類型。 - 利用合適的機會優化MyISAM表的數據。
2.2.3 分離能並行的操作:
雖然MyISAM是讀寫相互阻塞的表鎖,但是MyISAM存儲引擎還有一個有用的特性,那就是ConcurrentInsert(並發插入)的特性。
- 當
concurrent_insert=0時,此時讀不能與insert寫共存。 - 當
concurrent_insert=1時,如果表中沒有空數據塊時,讀可以與insert寫共存,insert新增加的數據將插入到數據文件尾部。(默認設置)。 - 當
concurrent_insert=2時,不論有沒有空數據塊,insert新增加的數據都將插入到數據文件尾部。
注:在myisam里,如果沒有空的數據塊,新增加的數據都會附加到數據文件的尾部,但是如果經常做update和delete操作,數據文件就不再是連續的,出現了許多空的數據塊,此時再插入數據,按照缺省設置會先看看這些空的數據塊是否能夠容納新插入的數據,如果可以則直接存儲到這些空的數據塊里,如果不行再直接插入到數據文件的尾部。MyISAM的默認的這種處理方式是為了減少數據文件的大小和碎片,以免造成IO性能問題。
如果我們要設置concurrent_insert=2,后面插入的數據都會直接追加到數據文件的尾部,而系統如果有頻繁的delete和update操作,可能就會有過多的碎片造成過多的IO消耗,小魚建議如果需要設置concurrent_insert=2一定要定期對表進行(優化)optimizer,以免造成過多的碎片。
可以利用MyISAM存儲引擎的並發插入特性,來解決應用中對同一表的插入和查詢操作。可以將
concurrent_insert設置為2,總是允許並發插入;同時通過定期在系統空閑時段執行optimizer ['ɑ:ptɪmaɪzər] table語句來整理空間碎片,
2.2.4 合理利用讀寫優先級:
MyISAM存儲引擎的讀寫時相互阻塞的,但是,一個進程請求某個MyISAM表的讀鎖,同時另一個進程也請求同一個表的寫鎖,MySQL如何處理呢?
默認情況下:寫進程先獲得鎖,不僅如此,即便讀請求先到鎖等待隊列,寫請求后到,寫鎖也會插入到讀鎖之前。
這是由於Mysql的表級鎖定對於讀和寫是有不同優先級設定的,默認情況下寫的優先級要大於讀的優先級。
所以可以利用各自系統的差異覺得寫與讀的優先級:
通過執行命令:set low_priority_updates=1 [praɪˈɒrəti],使該連接的讀請求優先級比寫的優先級高。如果系統是一個以讀為主,可以設置改參數,否則,不需要設置。
通過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先級。
另外MySQL也提供了一種折中的方法調節讀寫沖突,即給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個值后,MySQL就暫時將寫請求的優先級降低,給讀進程一定的獲取鎖的機會。
還有一點:一些需要長時間運行的查詢操作,也會使得寫進程“餓死”,因此,應用中盡量避免出現長時間運行的查詢操作。
2.3 Innodb行鎖
總的來說,InnoDB的鎖定機制和Oracle數據庫有不少相似之處。InnoDB的行級鎖分為兩種類型:共享鎖和排他鎖。而在鎖定機制的實現過程中,為了讓行級鎖和表級鎖共存,InnoDB也同樣使用了意向鎖(表級鎖定)的概念,於是也就有了意向共享鎖和意向排他鎖兩種。
當一個事務需要給自己需要的資源加鎖時,如果遇到有一個共享鎖正鎖定自己需要的資源的時候,自己可以再加一個共享鎖,不過不能加排他鎖。但是,如果遇到自己需要的資源已經被排它鎖占用之后,則只能等待該排他鎖釋放資源之后自己才能獲取資源,並將其鎖定。
2.3.1 什么叫做意向鎖
可以說:Inoodb的鎖定模式實際上可以分為四種:共享鎖(S),排它鎖(X),意向共享鎖(IS),意向排他鎖(IX)
首先,意向鎖到底是做什么用的。
知乎的回答——InnoDB 的意向鎖有什么作用?
首先,申請意向鎖的動作是數據庫完成的,也就是說,事務A申請一行行鎖的時候,數據庫會自動先開始申請表的意向鎖。
那么到底意向鎖有什么用?
首先,意向鎖是——表級鎖。
(1)事務A鎖住了表中的一行,這一行只能讀,不能寫。【事務A加了共享鎖】
(2)事務B申請整個表的寫鎖。【請注意:是整個表!!!】
(3)如果事務B申請成功,那么理論上它就能修改表中的任意一行,這與A持有的共享鎖時沖突的。【事務A不允許修改】
(4)數據庫為避免這種沖突,怎么辦的?就是讓B的申請阻塞,直到A釋放行鎖。
解決方案:
step1:判斷表是否已被其他事務用表鎖鎖住。
step2:判斷表中每一行是否已經被行鎖鎖住。
請注意step2,需要遍歷整個表。效率實在不高。
改進版:
step1:不變
step2:發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,因此,事務B申請表的寫鎖會被阻塞。
假如一個表被加了意向排他鎖(IX),證明此時有事務在修改表中具體某行的數據,那么對應的某行可能加了x鎖,1.如果這時候其他事務要再加意向鎖,那么可以加成功(因為加了意向鎖之后,后續查詢或者修改的是某行的數據,這行和上面的x鎖未必沖突)所以意向鎖之間是兼容的。2.如果此時其他事務加的是全表共享鎖S,因為前面表中的數據正在被修改,所以S鎖是加不成功的。所以意向排他鎖和表共享鎖是沖突的。
(敲黑板,划重點)意向鎖的作用就是協調行鎖和表鎖之間的關系的,是將行鎖從另一個角度提高到了表鎖的等級(偽表鎖),與表鎖進行判斷。
注意:select語句不是加鎖!!!
意向鎖是InnDB自動加的,不需要用戶的干預。
對於update、delete和insert語句,Innodb會自動給涉及數據集加排他鎖(X);對於普通的select語句,Innodb不會加任何鎖!!!事務可以通過以下語句顯式的給記錄集加鎖:
//共享鎖 select * from table_name where ... lock in share mode; //排它鎖 select * from table_name where ... for update;
2.3.2 使用共享鎖注意事項
使用 select ... in share mode獲取共享鎖,主要用在數據依存關系時,確認某行記錄是否存在,並且確保沒有人對這個記錄進行update或者delete操作。
(敲黑板,划重點)但是如果當前事務也需要對該記錄進行更新操作,則很有可能造成死鎖,對於鎖定記錄后進行更新的應用,應該使用select...for update方式獲得排他鎖。
2.3.3 InnoDB行鎖實現方式
InnoDB行鎖是通過給索引項加鎖來實現的,只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。
在實際開發中,要特別注意InnoDB這一特性,不然,可能造成大量的鎖沖突,從而影響並發!!!
InnoDB使用索引的條件:
- 在不通過索引條件查詢的時候,InnoDB確實使用的是表鎖,而不是行鎖。
- mysql的行鎖是針對索引加的鎖。不是針對記錄加的鎖,雖然是訪問不同的行,但是若是相同的索引,會出現鎖鎖沖突的。
- 當表中含有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行。
- 即使在條件中使用了索引,但是是否使用索引來檢索數據是由MySQL通過判斷不同執行計划的代價決定的,如果MySQL認為全表掃描效率更高,比如很小的表,他也不會使用索引,此時InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突的時候,不要忘記檢查SQL的執行計划,以確定是否真正使用了索引。
關於InnoDB到底是使用行鎖還是表鎖,我們需要依據索引來決定的,本質上行鎖是針對索引加的鎖,而非記錄!!!雖然是訪問不同的行,但是若是含有相同的索引,還是會發生鎖沖突的!!!而且就算條件里面使用了索引,Mysql也不一定走索引,還是要看SQL的執行計划!!!
2.3 間隙鎖
當我們用范圍條件而不是相等條件檢索數據的時候,並請求共享或者排他鎖時,InnoDB會給符合條件的已有的數據記錄的索引項加鎖。
對於鍵值在條件范圍內但是不存在的記錄,叫做間隙(GAP)。InnoDB也會對這個“間隙”加鎖,這種鎖機制就是間隙鎖(Next-Key鎖)
InnoDB使用間隙鎖的目的:
- 防止幻讀,以滿足相關隔離級別的要求;
- 滿足恢復和復制的需要;
InnoDB的危害:
使用范圍條件檢索並鎖定記錄時,即使某些不存在的鍵值也會被無辜的鎖定。而造成在鎖定的時候無法插入鎖定鍵值范圍內的任何數據。在某些時刻可能會造成很大的危害。
在實際應用開發中,尤其是並發插入比較多的應用,盡量優化業務邏輯,盡量使用相等的條件來訪問更新數據,避免使用范圍條件。
需要特別說明的是:InnoDB除了通過范圍條件加鎖使用間隙鎖外,如果使用相等條件請求給一個不存在的記錄加鎖,InnoDB也會使用間隙鎖。
3. 死鎖
MyISAM表鎖總是一次性獲得所需的全部鎖,要么全部滿足,要不等待,因此不會出現死鎖。
InnoDB中,處理單個sql組成的事務外,鎖是逐步獲得的,當兩個事務都需要獲得對方持有的排他鎖才能完成事務時,這種循環等待就是典型的死鎖。
在InnoDB的事務管理和鎖機制中,有專門檢測死鎖的機制,會在系統產生死鎖之后的很短的時間內就檢測到死鎖的存在。當InnoDB檢測到系統產生了死鎖之后,InnoDB會通過相應的判斷來選出產生死鎖兩個事務中較小的事務回滾,而讓較大的事務成功完成。
但是需要注意,當產生死鎖的場景涉及到的不只是InnoDB存儲引擎的情況下,InnoDB是無法檢測到死鎖的,只能通過鎖的超時限制參數InnoDB_lock_wait_timeout來解決。
需要說明的是,這個參數並不是解決死鎖問題的,而事故在並發高的情況下,如果大量事務因無法立即獲得所需的鎖而掛起,會占用大量計算機資源,造成嚴重的性能問題。甚至拖垮數據庫。我們可以通過設置合適的鎖等待超時的閾值,避免這種情況的發送。
通常來說,死鎖都是應用設計的問題,我們可以通過:
- 若不同程序並發存取多個表,應約定以相同的順序來訪問表,這樣可以降低死鎖的機會。
- 在程序中以批量的方式處理數據時,如果事先對數據進行排序,保證每個線程按固定順序處理記錄,也可以大大降低死鎖可能。
- 在事務中,如果要更新記錄,應該申請足夠級別的鎖(排它鎖),而不是先申請共享鎖,更新時在申請排他鎖。當用戶申請排他鎖時,其他事務可能已經獲得了相同記錄的共享鎖,從而造成鎖等待,甚至死鎖
