日志(可用來恢復數據)
注: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 的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。
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 內部執行的,深色框表示是在執行器中執行的。
圖中將 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講》