一、事務的特性(ACID),原子性,一致性,隔離性,持久性;
二、Mysql的事務隔離級別
1、讀未已提交(READ UNCOMMITTED):
一個事務可以讀取到未提交的數據(比如只進行了更新操作),產生髒讀,幻度。
2、讀已提交(READ COMMITTED):
事務讀取到已經提交的數據,多次select,查詢結果不一致,產生幻度。
3、可重複讀(REPEATABLE READ) :
一個事務中多次讀取數據相同,可能產生幻讀,但是mysql的可重複讀解決了幻讀的問題,事務讀取可以分爲:快照讀使用與select 語句,比如:select id from user where id = 1 (MVCC:READVIEW 和 版本鏈解決)、當前讀,比如:select id from user where id = 1 for update (一個事務內讀取數據,間隙鎖),可以參考一下鏈接加深理解:https://blog.csdn.net/qq_40918324/article/details/104617714。
4、串行化(SERIALIZABLE):
三、1、 什麼是版本鏈:
在innoDB存儲引擎的表,有三個隱藏列分別是:row_id,trx_id,roll_pointer,其中row_id不是必要的,如果有主鍵索引,則列號不一定存在。
trx_id:記錄當前事務的事務號。
roll_pointer:指向前一個事務號,(通過指針找到修改前的記錄)。
2、ReadView:
① 對於讀已提交和可重複讀,需要使用上述的版本鏈,核心問題:找到哪個事務是正確的,可見的。那麼怎麼判斷哪個事務版本是可用的呢,就需要ReadView了,我發現其實很多人都在說mvcc但是卻不知道readView,真的是很奇怪。
② ReadView 主要由四部分組成:
1、m_ids:記錄當前活躍的所有事務,相當於一個數組,[1,2,3,4]
2、min_trx_id: 記錄當前活躍事務中最小的事務號。
3、max_trx_id:記錄下一個應該開啓的事務的事務號,事務號是遞增的,其實就是時間戳。
4、creator_trx_id:表示生成該ReadView的事務號。
注意max_trx_id並不是m_ids中的最大值,事務id是遞增分配的。比方說現在有id爲1,2,3這三個事務,之
後id爲3的事務提交了。那麼一個新的讀事務在生成ReadView時,m_ids就包括1和2,min_trx_id的值就是1,
max_trx_id的值就是4。
③ 有了readView之後,再查詢語句的時候就可以判斷哪些事務的數據是可以被查到的了:
1、比如查詢的數據的事務id < min_trx_id 表示數據在查詢前沒有操作過,可以操作。
2、如果數據的事務id == creator_trx_id 則表示當前事務在查詢數據,可以操作。
3、如果數據的事務id > max_trx_id 則表示查詢時,數據又被其他的事務操作過,不能查詢數據,需要根據版本鏈查詢。
4、如果min_trx_id < 數據的事務id <max_trx_id, 需要判斷是否在m_id,並且不在m_id 列表中則代表,查詢時已經被提交過,可以查詢。
④ 對於讀已提交和可重複讀,在生成ReadView的規則不同,導致了他們讀取數據時候的區別:
1、讀已提交:每次執行select的時候都會生成一個readView,記錄當前活躍的事務id。所以他可以讀取到最新的別提交過的數據。
2、可重讀度:他只會在第一次執行select 語句的時候生成ReadView,所以多次執行ReadView相同所以查詢數據相同。
四、mysql innoDB的行鎖與表鎖
一、行鎖:
1、行鎖分爲: ① LOCK_REC_NOT_GAP:單個行記錄上的鎖。
② LOCK_GAP:間隙鎖,只會鎖住查詢數據之間的間隙。
③ NEXT_KEY_LOCK:結合單行鎖和間隙鎖的鎖。
2、對於讀已提交,不存在間隙鎖,只會對錶中查詢出來的數據加鎖。
3、對於可重讀度,對於主鍵索引和唯一索引不會加間隙鎖。對於普通索引和沒有索引的列,會對查詢出來的數據的間隙加鎖(可能會造成死鎖)。
4、這裏的鎖對於二級索引,首先會對索引字段加鎖,再下來同時會對,索引頁中的主鍵id進行加鎖,爲什麼會加兩次鎖?
① 當查詢數據有二級索引列時,直接判斷加鎖快速返回。
② 當前查詢沒有二級索引列,但是時主鍵索引對應的數據時,保證數據被加鎖。
五、mysql的redolog和undolog
數據是通過什麼方式保存到數據庫中呢?數據存儲過程中是如何保證髒頁落盤呢?如何實現數據回滾?如何實現數據恢復呢?
其實也就是mysql如何保證持久性,原子性和一致性的。
① mysql更新操作會先把數據從磁盤加載到內存,進行數據修改,然後再從內存(用戶態)到內存空間(內核態)最後再到mysql的磁盤空間保證了數據持久化。
1>buffer pool,數據會從磁盤加載到buffer pool進行數據修改。
2> redolog 分成兩部分: 1、redolog buffer 2、redolog file
1、數據在寫入redolog file之前會先寫入redolog buffer,因爲寫入緩存的效率更快,當發生commit或者到達固定時間後(主要由innodb_flush_log_at_trx_commit 和 innodb_flush_log_at_timeout 兩個值決定) 纔會寫入redolog file,每次mysql重啓是都會判斷根據 redolog恢復數據(保證持久化),這裏還有一個lns的概念後面說,具體的寫入規則 由 innodb_flush_log_at_trx_commit 0|1|2 控制,如下圖:
0: 事務提交時,每次都會寫入logbuffer ,但是隻會定時(每秒)寫入(調用fsync)osbuffer ,在寫入到磁盤文件,可能會丟失1 秒內的數據。
1: mysql默認規則,事務提交時每次都寫入 logbuffer、osbuffer、刷新到磁盤。數據完整但是性能低下。
2: 事務提交時每次都寫入osbuffer,但是每秒執行一次寫入磁盤操作。
3>日誌刷盤規則:主要由innodb_flush_log_at_trx_commit 和 innodb_flush_log_at_timeout 兩個值決定。
1.發出commit動作時。已經說明過,commit發出後是否刷日誌由變量 innodb_flush_log_at_trx_commit 0|1|2 控制。
2.每秒刷一次。這個刷日誌的頻率由變量 innodb_flush_log_at_timeout 值決定,默認是1秒。要注意,這個刷日誌頻率和commit動作無關。
3.當log buffer中已經使用的內存超過一半時。
4.當有checkpoint時,checkpoint在一定程度上代表了刷到磁盤時日誌所處的LSN位置。
② 日誌存儲格式
innodb存儲引擎中,redo log以塊爲單位進行存儲的,每個塊佔512字節,這稱爲redo log block。所以不管是log buffer中還是os buffer中以及redo log file on disk中,都是這樣以512字節的塊存儲的。
每個redo log block由3部分組成:日誌塊頭、日誌塊尾和日誌主體。其中日誌塊頭佔用12字節,日誌塊尾佔用8字節,所以每個redo log block的日誌主體部分只有512-12-8=492字節。
因爲redo log記錄的是數據頁的變化,當一個數據頁產生的變化需要使用超過492字節的redo log來記錄,那麼就會使用多個redo log block來記錄該數據頁的變化。
日誌塊頭包含4部分:
- log_block_hdr_no:(4字節)該日誌塊在redo log buffer中的位置ID。
- log_block_hdr_data_len:(2字節)該log block中已記錄的log大小。寫滿該log block時爲0x200,表示512字節。
- log_block_first_rec_group:(2字節)該log block中第一個log的開始偏移位置。
- lock_block_checkpoint_no:(4字節)寫入檢查點信息的位置。
關於log block塊頭的第三部分 log_block_first_rec_group ,因爲有時候一個數據頁產生的日誌量超出了一個日誌塊,這是需要用多個日誌塊來記錄該頁的相關日誌。例如,某一數據頁產生了552字節的日誌量,那麼需要佔用兩個日誌塊,第一個日誌塊佔用492字節,第二個日誌塊需要佔用60個字節,那麼對於第二個日誌塊來說,它的第一個log的開始位置就是73字節(60+12)。如果該部分的值和 log_block_hdr_data_len 相等,則說明該log block中沒有新開始的日誌塊,即表示該日誌塊用來延續前一個日誌塊。
日誌尾只有一個部分: log_block_trl_no ,該值和塊頭的 log_block_hdr_no 相等。
上面所說的是一個日誌塊的內容,在redo log buffer或者redo log file on disk中,由很多log block組成。如下圖:
1.4 log group和redo log file
log group表示的是redo log group,一個組內由多個大小完全相同的redo log file組成。組內redo log file的數量由變量 innodb_log_files_group 決定,默認值爲2,即兩個redo log file。這個組是一個邏輯的概念,並沒有真正的文件來表示這是一個組,但是可以通過變量 innodb_log_group_home_dir 來定義組的目錄,redo log file都放在這個目錄下,默認是在datadir下。
mysql> show global variables like "innodb_log%";
+-----------------------------+----------+
| Variable_name | Value |
+-----------------------------+----------+
| innodb_log_buffer_size | 8388608 |
| innodb_log_compressed_pages | ON |
| innodb_log_file_size | 50331648 |
| innodb_log_files_in_group | 2 |
| innodb_log_group_home_dir | ./ |
+-----------------------------+----------+
[root@xuexi data]# ll /mydata/data/ib*
-rw-rw---- 1 mysql mysql 79691776 Mar 30 23:12 /mydata/data/ibdata1
-rw-rw---- 1 mysql mysql 50331648 Mar 30 23:12 /mydata/data/ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Mar 30 23:12 /mydata/data/ib_logfile1
可以看到在默認的數據目錄下,有兩個ib_logfile開頭的文件,它們就是log group中的redo log file,而且它們的大小完全一致且等於變量 innodb_log_file_size 定義的值。第一個文件ibdata1是在沒有開啓 innodb_file_per_table 時的共享表空間文件,對應於開啓 innodb_file_per_table 時的.ibd文件。
在innodb將log buffer中的redo log block刷到這些log file中時,會以追加寫入的方式循環輪訓寫入。即先在第一個log file(即ib_logfile0)的尾部追加寫,直到滿了之後向第二個log file(即ib_logfile1)寫。當第二個log file滿了會清空一部分第一個log file繼續寫入。
由於是將log buffer中的日誌刷到log file,所以在log file中記錄日誌的方式也是log block的方式。
在每個組的第一個redo log file中,前2KB記錄4個特定的部分,從2KB之後纔開始記錄log block。除了第一個redo log file中會記錄,log group中的其他log file不會記錄這2KB,但是卻會騰出這2KB的空間。如下:
redo log file的大小對innodb的性能影響非常大,設置的太大,恢復的時候就會時間較長,設置的太小,就會導致在寫redo log的時候循環切換redo log file。
1.5 redo log的格式
因爲innodb存儲引擎存儲數據的單元是頁(和SQL Server中一樣),所以redo log也是基於頁的格式來記錄的。默認情況下,innodb的頁大小是16KB(由 innodb_page_size 變量控制),一個頁內可以存放非常多的log block(每個512字節),而log block中記錄的又是數據頁的變化。
其中log block中492字節的部分是log body,該log body的格式分爲4部分:
- redo_log_type:佔用1個字節,表示redo log的日誌類型。
- space:表示表空間的ID,採用壓縮的方式後,佔用的空間可能小於4字節。
- page_no:表示頁的偏移量,同樣是壓縮過的。
- redo_log_body表示每個重做日誌的數據部分,恢復時會調用相應的函數進行解析。例如insert語句和delete語句寫入redo log的內容是不一樣的。
如下圖,分別是insert和delete大致的記錄方式。
③ mysql的髒頁落盤(持久化):
1> 髒頁: buffer pool中的數據與磁盤中數據不一致的數據(mysql加載數據以頁爲單位的)成爲髒頁。
2> 檢查點:到達一個檢查點是纔會觸發髒頁落盤。在innodb中,數據刷盤的規則只有一個:checkpoint, Innodb存儲引擎中checkpoint分爲兩種:
- sharp checkpoint:在重用redo log文件(例如切換日誌文件)的時候,將所有已記錄到redo log中對應的髒數據刷到磁盤。
- fuzzy checkpoint:一次只刷一小部分的日誌到磁盤,而非將所有髒日誌刷盤。有以下幾種情況會觸發該檢查點:
- master thread checkpoint:由master線程控制,每秒或每10秒刷入一定比例的髒頁到磁盤。
- flush_lru_list checkpoint:從MySQL5.6開始可通過 innodb_page_cleaners 變量指定專門負責髒頁刷盤的page cleaner線程的個數,該線程的目的是爲了保證lru列表有可用的空閒頁。
- async/sync flush checkpoint:同步刷盤還是異步刷盤。例如還有非常多的髒頁沒刷到磁盤(非常多是多少,有比例控制),這時候會選擇同步刷到磁盤,但這很少出現;如果髒頁不是很多,可以選擇異步刷到磁盤,如果髒頁很少,可以暫時不刷髒頁到磁盤
- dirty page too much checkpoint:髒頁太多時強制觸發檢查點,目的是爲了保證緩存有足夠的空閒空間。too much的比例由變量 innodb_max_dirty_pages_pct 控制,MySQL 5.6默認的值爲75,即當髒頁佔緩衝池的百分之75後,就強制刷一部分髒頁到磁盤。
由於刷髒頁需要一定的時間來完成,所以記錄檢查點的位置是在每次刷盤結束之後纔在redo log中標記的。
③ double write: 因爲mysql一頁數據爲16k,而磁盤最小爲4k,所以一頁數據需要寫多次,期間如果出現失敗就需要doblewrite來保證持久化成功,大概也是先寫入緩存再寫入磁盤。
④ lsn分析
1>LSN稱爲日誌的邏輯序列號(log sequence number),在innodb存儲引擎中,lsn佔用8個字節。LSN的值會隨着日誌的寫入而逐漸增大。
2>lsn存在於 數據頁中,可以通過比較redolog 中lsn的值來決定是否需要redolog執行日誌數據恢復,(數據頁中的lsn小於redolog中的lsn,則刷新他們之間的差值建的數據)。
innodb從執行修改語句開始:
(1).首先修改內存中的數據頁,並在數據頁中記錄LSN,暫且稱之爲data_in_buffer_lsn;
(2).並且在修改數據頁的同時(幾乎是同時)向redo log in buffer中寫入redo log,並記錄下對應的LSN,暫且稱之爲redo_log_in_buffer_lsn;
(3).寫完buffer中的日誌後,當觸發了日誌刷盤的幾種規則時,會向redo log file on disk刷入重做日誌,並在該文件中記下對應的LSN,暫且稱之爲redo_log_on_disk_lsn;
(4).數據頁不可能永遠只停留在內存中,在某些情況下,會觸發checkpoint來將內存中的髒頁(數據髒頁和日誌髒頁)刷到磁盤,所以會在本次checkpoint髒頁刷盤結束時,在redo log中記錄checkpoint的LSN位置,暫且稱之爲checkpoint_lsn。
(5).要記錄checkpoint所在位置很快,只需簡單的設置一個標誌即可,但是刷數據頁並不一定很快,例如這一次checkpoint要刷入的數據頁非常多。也就是說要刷入所有的數據頁需要一定的時間來完成,中途刷入的每個數據頁都會記下當前頁所在的LSN,暫且稱之爲data_page_on_disk_lsn。
詳細說明如下圖:
參考鏈接:https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html