MYSQL的日志、事務、鎖

日志(可用來恢復數據)

注:MySQL 整體來看,一共有兩塊:一塊是 Server 層,它主要做的是 MySQL 功能層面的事情;還有一塊是引擎層,負責存儲相關的具體事宜。它們各有自己的日志系統。 redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)。

日志的作用:因為每一次的更新操作都需要寫進磁盤,然后磁盤也要找到對應的那條記錄,然后再更新,整個過程 IO 成本、查找成本都很高。為了解決這個問題,MySQL 的設計者就用了類似酒店掌柜粉板的思路來提升更新效率。

redo?Log(重做日志)(屬于InnoDB引擎)(粉板)

Mysql采用WAL(write-Ahead Logging)技術,它的關鍵點在于先寫日志再寫磁盤。這個日志就是Redo Log。當一條記錄需要更新的時候,InnoDB引擎會把記錄先寫到Redo Log中,并更新內存,等到合適的時間,再將記錄更新到磁盤中。這個更新往往在系統比較空閑的時候做,但是當Redo Log的空間用完的時候,就需要先將記錄更新,以便接下來繼續操作。

InnoDB的Redo Log是固定大小的,比如可以配置為一組 4 個文件,每個文件的大小是 1GB,那么這塊“粉板”總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。

Redo Log操作示意圖

write pos 是當前記錄的位置,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭。checkpoint 是當前要擦除的位置,也是往后推移并且循環的,擦除記錄前要把記錄更新到數據文件。

write pos 和 checkpoint 之間的是“粉板”上還空著的部分,可以用來記錄新的操作。如果 write pos 追上 checkpoint,表示“粉板”滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把 checkpoint 推進一下。

有了 redo log,InnoDB 就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為 crash-safe。


Binlog(歸檔日志)(屬于Service層)

有兩份日志的原因:①mysql自帶引擎MyISAM沒有crash-safe能力,而binglog也只能用來歸檔。

Redo Log和Binlog的不同點:

redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。

redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。

redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,并不會覆蓋以前的日志。


兩階段提交

更新一個數據的過程:

1、執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則,需要先從磁盤讀入內存,然后再返回。

2、執行器拿到引擎給的行數據,把這個值加上 1,比如原來是 N,現在就是 N+1,得到新的一行數據,再調用引擎接口寫入這行新數據。

3、引擎將這行新數據更新到內存中,同時將這個更新操作記錄到 redo log 里面,此時 redo log 處于 prepare 狀態。然后告知執行器執行完成了,隨時可以提交事務。

4、執行器生成這個操作的 binlog,并把 binlog 寫入磁盤。

5、執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。

update 語句的執行流程圖如下,圖中淺色框表示是在 InnoDB 內部執行的,深色框表示是在執行器中執行的。

update執行流程

圖中將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。兩階段提交的主要是為了讓兩份日志之間的邏輯一致。

先寫 redo log 后寫 binlog。假設在 redo log 寫完,binlog 還沒有寫完的時候,MySQL 進程異常重啟。由于我們前面說過的,redo log 寫完之后,系統即使崩潰,仍然能夠把數據恢復回來,所以恢復后這一行 c 的值是 1。但是由于 binlog 沒寫完就 crash 了,這時候 binlog 里面就沒有記錄這個語句。因此,之后備份日志的時候,存起來的 binlog 里面就沒有這條語句。然后你會發現,如果需要用這個 binlog 來恢復臨時庫的話,由于這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復出來的這一行 c 的值就是 0,與原庫的值不同。

先寫 binlog 后寫 redo log。如果在 binlog 寫完之后 crash,由于 redo log 還沒寫,崩潰恢復以后這個事務無效,所以這一行 c 的值是 0。但是 binlog 里面已經記錄了“把 c 從 0 改成 1”這個日志。所以,在之后用 binlog 來恢復的時候就多了一個事務出來,恢復出來的這一行 c 的值就是 1,與原庫的值不同。


事務(事務間隔離)

事務就是要保證一組數據庫操作,要么全部成功,要么全部失敗。在MySQL中,事務支持是在引擎層實現的。(MySQL 原生的 MyISAM 引擎就不支持事務,這也是 MyISAM 被 InnoDB 取代的重要原因之一。)

隔離性與隔離級別

當數據庫上有多個事務同時執行的時候,就可能出現臟讀(dirty read)、不可重復讀(non-repeatable read)、幻讀(phantom read)的問題,為了解決這些問題,就有了“隔離級別”的概念。

隔離的越嚴實,效率就會越低。

SQL 標準的事務隔離級別包括:讀未提交(read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(serializable )。

讀未提交是指,一個事務還沒提交時,它做的變更就能被別的事務看到。

讀提交是指,一個事務提交之后,它做的變更才會被其他事務看到。

可重復讀是指,一個事務執行過程中看到的數據,總是跟這個事務在啟動時看到的數據是一致的。當然在可重復讀隔離級別下,未提交變更對其他事務也是不可見的。

串行化,顧名思義是對于同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖沖突的時候,后訪問的事務必須等前一個事務執行完成,才能繼續執行。

如下圖A、B事務同時執行,不同隔離級別看到的不同結果:

多個事務執行情況

若隔離級別是“讀未提交”, 則 V1 的值就是 2。這時候事務 B 雖然還沒有提交,但是結果已經被 A 看到了。因此,V2、V3 也都是 2。

若隔離級別是“讀提交”,則 V1 是 1,V2 的值是 2。事務 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。

若隔離級別是“可重復讀”,則 V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就是這個要求:事務在執行期間看到的數據前后必須是一致的。

若隔離級別是“串行化”,則在事務 B 執行“將 1 改成 2”的時候,會被鎖住。直到事務 A 提交后,事務 B 才可以繼續執行。所以從 A 的角度看, V1、V2 值是 1,V3 的值是 2。

在實現上,數據庫里面會創建一個視圖,訪問的時候以視圖的邏輯結果為準。在“可重復讀”隔離級別下,這個視圖是在事務啟動時創建的,整個事務存在期間都用這個視圖。在“讀提交”隔離級別下,這個視圖是在每個 SQL 語句開始執行的時候創建的。這里需要注意的是,“讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念;而“串行化”隔離級別下直接用加鎖的方式來避免并行訪問。


事務隔離的實現

可重復讀事務實現:

在 MySQL 中,實際上每條記錄在更新的時候都會同時記錄一條回滾操作。記錄上的最新值,通過回滾操作,都可以得到前一個狀態的值。(不同時刻啟動事務,會有不同的讀視圖(read-view)產生)

假設一個值從 1 被按順序改成了 2、3、4,在回滾日志里面就會有類似下面的記錄。

事務記錄

當前值是 4,但是在查詢這條記錄的時候,不同時刻啟動的事務會有不同的 read-view。如圖中看到的,在視圖 A、B、C 里面,這一個記錄的值分別是 1、2、4,同一條記錄在系統中可以存在多個版本,就是數據庫的多版本并發控制(MVCC)。對于 read-view A,要得到 1,就必須將當前值依次執行圖中所有的回滾操作得到。即使現在有另外一個事務正在將 4 改成 5,這個事務跟 read-view A、B、C 對應的事務是不會沖突的。

盡量不要使用長事務。長事務意味著系統里面會存在很老的事務視圖。由于這些事務隨時可能訪問數據庫里面的任何數據,所以這個事務提交之前,數據庫里面它可能用到的回滾記錄都必須保留,這就會導致大量占用存儲空間。


事務的啟動方式

MySQL 的事務啟動方式有以下幾種:

1、顯式啟動事務語句, begin 或 start transaction。配套的提交語句是 commit,回滾語句是 rollback。

2、set autocommit=0,這個命令會將這個線程的自動提交關掉。意味著如果你只執行一個 select 語句,這個事務就啟動了,而且并不會自動提交。這個事務持續存在直到你主動執行 commit 或 rollback 語句,或者斷開連接。

有些客戶端連接框架會默認連接成功后先執行一個 set autocommit=0 的命令。這就導致接下來的查詢都在事務中,如果是長連接,就導致了意外的長事務。因此,最好總是使用 set autocommit=1, 通過顯式語句的方式來啟動事務。




全局鎖和表鎖 增刪改查數據(DML),修改表字段(DDL)

全局鎖

使用場景:做全庫邏輯備份。

①MYSQL加全局鎖的命令:Flush table with read lock(FTWRL) (相比較②目前優選,對引擎沒有要求)

②官方邏輯備份工具MysqlDump: -single-transaction (缺點:需要引擎支持這個隔離級別,single-transaction 方法只適用于所有的表使用事務引擎的庫。)

③不推薦使用:set global readonly=true(相較于①,readonly在有些系統可能用來做其他邏輯,還有就是發生異常之后不會釋放,而①會釋放)


表鎖:表鎖、元數據鎖

表鎖。語法:lock tables ... read/write (例線程A: lock tables t1 read, t2 write;則其他線程寫 t1、讀寫 t2 的語句都會被阻塞)? ? ? 解鎖:unlock tables(或者客戶端斷開的時候自動釋放)(而對于 InnoDB 這種支持行鎖的引擎,一般不使用 lock tables 命令來控制并發,畢竟鎖住整個表的影響面還是太大。)

(共享讀鎖,獨占寫鎖,加上讀鎖,不會限制別的線程讀,但會限制別的線程寫。加上寫鎖,會限制別的線程讀寫。線程 A 在執行 unlock tables 之前,也只能執行讀 t1、讀寫 t2 的操作。連寫 t1 都不允許,自然也不能訪問其他表。)


另一類表級的鎖是MDL(metadata lock)

mdl需不要顯示調用,在訪問一個表的時候會被自動加上。mdl的作用是保證讀書的正確性。

在 MySQL 5.5 版本中引入了 MDL,當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對表做結構變更操作的時候,加 MDL 寫鎖。

(讀鎖之間不互斥,因此你可以有多個線程同時對一張表增刪改查。

讀寫鎖之間、寫鎖之間是互斥的)

給一個表加字段,或者修改字段,或者加索引,需要掃描全表的數據

事務中的 MDL 鎖,在語句執行開始時申請,但是語句結束后并不會馬上釋放,而會等到整個事務提交后再釋放。

如何安全的給小表加字段:①考慮在長事務中不進行DDL,或者kill掉這個事務。? ②先嘗試拿MDL的寫鎖。

備注

全局鎖主要用在邏輯備份過程中,對于引擎全部是InnoDB的庫來說,用-Single-transation參數,對應用更友好。

表鎖一般是在數據庫引擎不支持行鎖的時候才會用到。

如果你發現你的應用程序里有 lock tables 這樣的語句,你需要追查一下,比較可能的情況是:

要么是你的系統現在還在用 MyISAM 這類不支持事務的引擎,那要安排升級換引擎;

要么是你的引擎升級了,但是代碼還沒升級。我見過這樣的情況,最后業務開發就是把 lock tables 和 unlock tables 改成 begin 和 commit,問題就解決了。

MDL 會直到事務提交才釋放,在做表結構變更的時候,你一定要小心不要導致鎖住線上查詢和更新。


行鎖

Mysql的行鎖是引擎自己實現的,這個InnoDB替代MylSAM的原因之一。

行鎖:行鎖就是針對數據表中行記錄的鎖。比如事務 A 更新了一行,而這時候事務 B 也要更新同一行,則必須等事務 A 的操作完成后才能進行更新。(例如:update t set k = k + 1 where id = 1 會鎖update t set k = k + 2where id = 1? 而不會鎖update t set k = k + 1 where id = 2 )

兩階段鎖(見下圖)

兩階段鎖

事務 B 的 update 語句會被阻塞,直到事務 A 執行 commit 之后,

事務 B 才能繼續執行。

事務 A 持有的兩個記錄的行鎖

事務 A 持有的兩個記錄的行鎖,都是在 commit 的時候才釋放的。

在 InnoDB 事務中,行鎖是在需要的時候才加上的,但并不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議

因為兩階段鎖的存在,如果事務中需要鎖多行,要把最可能造成鎖沖突、影響并發度的鎖往后放。

死鎖和死鎖檢測(見下圖)

死鎖,都在等待對方行鎖釋放

避免死鎖的方式。 一、超時機制(不推薦,時間長影響使用,時間容易誤會鎖的正常等待。) 二、死鎖檢測。發現死鎖后主動回滾死鎖鏈中的某一事物。過程如下:每當一個事務被鎖的時候,就要看看它所依賴的線程有沒有被別人鎖住,如此循環,最后判斷是否出現了循環等待,也就是死鎖。缺點:每個新來的被堵住的線程,都要判斷會不會由于自己的加入導致了死鎖,這是一個時間復雜度是 O(n) 的操作。假設有 1000 個并發線程要同時更新同一行,那么死鎖檢測操作就是 100 萬這個量級的。

解決死鎖方案

臨時把死鎖檢測關掉。

控制并發度。

減少死鎖的主要方向,就是控制訪問相同資源的并發事務量。




注:筆記主要來自于極客時間的《Mysql實戰45講》



最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374