這一篇文章是本人數據庫的第二篇,也是對數據庫學習的階段性總結。對於數據庫鎖的瞭解,是區分程序員,尤爲是Java程序員,中高級的一個重要標誌。也是平常,咱們開發中,常常碰到坑的地方。每每,咱們無腦的CURD過程當中,其實已經出現問題了,鎖問題,可是咱們並無發現,你那是沒有被大訪問量衝擊。一旦一朝咱們衝擊到了,那損失和鍋,是要本身承擔下來的。很少說,咱們接下來就來一步步看看Mysql的鎖機制。程序員
總體上,Mysql鎖的類型,從全局的到細節的,包含以下幾個:sql
大概上,咱們平常生產學習中,所能接觸到的就這幾大種類了(我的的腦容量也就能掌握這麼多了,(⊙﹏⊙)b),接下來,咱們一個個的說說。數據庫
顧名思義,全局鎖就是對整個數據庫實例加鎖。Mysql提供了一個加鎖的語句:Flush tables with read lock (FTWRL)。它能使整個實例上面,只讀,全部的寫和更新,都會被阻塞。全局鎖的使用經典的使用場景是作全局的數據備份使用,具體的操做,可能平時咱們碰到很少,不過要了解幾點:安全
咱們首先要知道的是,每次進行select操做或者DML的時候,對錶加的都是MDL的讀鎖,而進行DDL的時候,對錶加的是MDL的寫鎖,讓咱們首先來個印象。接下來來看看普通的表鎖與MDL(元數據鎖meta data lock)的區別。session
普通的表鎖也是分讀鎖與寫鎖,數據庫提供語句操做:lock tables … read/write,使用unlock tables進行釋放鎖。具體注意的點是:加了普通的表鎖以後,對當前加鎖線程接下來的數據庫操做,都是有影響的。併發
舉個例子:若是A線程使用語句lock tables t1 read, t2 write; 這個語句,那麼,其餘線程寫t1和讀寫t2都會被阻塞;同時線程A再進行unlock以前,也只能讀t1和讀寫t2,連寫t1都是不被容許的。天然也不能訪問其餘的表高併發
在沒有出現行鎖以前,都是經過表鎖進行併發控制的,上面例子可見,影響面仍是太大,限制太嚴格了。學習
MDL不須要主動加鎖,每當咱們訪問一個數據表的時候,會自動被加上,做用是防止在咱們進行表的操做的時候,進行了表結構的變動。再5.5這個版本中被引入了Mysql中:線程
讀寫鎖的MDL之間的互斥關係是:orm
具體有個經典的例子:常常發生的是,咱們給一個表加了個一個字段或者幾個字段,很當心了,可是加的過程當中,直接整個表掛了,接下來的操做都失敗了或者不返回。接下來咱們就看看具體的操做過程:
sessionA | sessionB | sessionC | sessionD |
---|---|---|---|
begin; | |||
select * from t limit1 | |||
select * from t limit1 | |||
alter table t add f int (block) | |||
select * from t limit1 (block) |
可見,咱們sessionC操做以後,因爲sessionA是沒有結束事務的,咱們MDL會隨着事務的開啓而加鎖,事務的結束而釋放鎖,因此,sessionA這時候保持住了MDL的讀鎖。而後sessionC想要獲取MDL的寫的時候,因爲讀寫互斥,sessionC就被阻塞了。接下來的語句,也都執行不了了,由於接下倆的語句要申請MDL的讀鎖,而有寫鎖已經在阻塞狀態,讀鎖又要排隊等這個寫鎖執行釋放,那接下來的現象可想而知。
咱們如何安全的對一個表進行加字段的操做呢:
這個過程比較複雜,首先,咱們來看看,Mysql加行鎖,是使用兩階段加鎖策略的,咱們看看什麼叫作兩階段加鎖:
兩階段鎖協議,整個事務分爲兩個階段,前一個階段爲加鎖,後一個階段爲解鎖。在加鎖階段,事務只能加鎖,也能夠操做數據,但不能解鎖,直到事務釋放第一個鎖,就進入解鎖階段,此過程當中事務只能解鎖,也能夠操做數據,不能再加鎖。兩階段鎖協議使得事務具備較高的併發度,由於解鎖沒必要發生在事務結尾。它的不足是沒有解決死鎖的問題,由於它在加鎖階段沒有順序要求。如兩個事務分別申請了A, B鎖,接着又申請對方的鎖,此時進入死鎖狀態。
正常,咱們select語句時候,是不會添加行鎖的,只會加上MDL的讀鎖,即便這條語句是全表掃描,也不會加行鎖,只不過全表掃描,查詢較慢罷了,並不會由於鎖的問題而對其餘操做進行阻塞。下面是我總結的一些加行鎖的場景:
p.s.:固然上面全部所列取的操做,都是首先加了MDL的讀鎖的
行鎖,之因此存在,就是提升併發度的。取代之前,咱們要整表進行加鎖,而引發同一時刻,只能有一個線程對數據表進行增刪改的操做,下面咱們看一個具體的數據庫操做:
事務A | 事務B |
---|---|
begin; | |
update t set k = k+1 where id = 1; | |
update t set k = k+2 where id = 2; | |
begin; | |
update t set k = k+3 where id = 1; | |
commit; |
因此,按照這種邏輯,越是併發度高的數據表,越要靠事務的後面寫,由於持有行鎖時間短,影響併發度的時間越短。
首先咱們看接下來的這個模擬操做:
事務A | 事務B |
---|---|
begin; | |
update t set k = k+1 where id = 1; | begin; |
update t set k = k+3 where id = 2; | |
update t set k = k+2 where id = 2; | |
update t set k = k+3 where id = 1; |
這就是一個經典的死鎖場景,咱們來分析下:
事務A的update t set k = k+1 where id = 1;獲取了id爲1這一行的行鎖(排它鎖)
事務B的update t set k = k+3 where id = 2;獲取了id爲2這一行的行鎖
事務A的update t set k = k+2 where id = 2;要獲取id爲2的行鎖,而獲取不到,阻塞
事務B的update t set k = k+3 where id = 1;要獲取id爲1的行鎖,獲取不到,阻塞
對於這種,Mysql有兩種機制進行處理:
第一種狀況雖然能控制,死鎖,可是時間很差設置,例如咱們設置一個10s,若是一個線程被鎖住,要等待10s才能進行回滾,併發度天然不高,若是我設置低了,1s,那麼一個正常等待的,並不是死鎖,也會被回滾。如此一來得不償失。下面重點說說主動死鎖檢測
每當吧innodb_deadlock_detect設置成on,MySQL會主動檢測死鎖:
看起來很好,而後會有代價:每次對比是否當前線程堵住了其餘線程這一步,會對比全部系統正在執行的線程,時間複雜度是O(n)。當前執行的線程數少不成問題,若是是1000個正在執行的線程,那麼這就是100w次的對比,這個過程極度消耗CPU資源。結果可能檢測出沒有死鎖,而後會發現:最終CPU飈的老高,然而執行的條數沒幾個!解決辦法有下面幾個:
下面一篇文章,會重點講間隙鎖,這個內容最爲複雜,涉及到了所謂數據庫解決幻讀的機制問題,特別單獨抽出一章來說解。