mysql——事務以及鎖

一、事務的特性(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章