做者:高鵬 文章末尾有他著做的《深刻理解MySQL主從原理 32講》,深刻透徹理解MySQL主從,GTID相關技術知識。mysql
本文節選自《深刻理解MySQL主從原理》第24節 注意:本文分爲正文和附件兩部分,都是圖片格式,若是正文有圖片不清晰能夠將附件的圖片保存到本地查看。算法
本節包含一個筆記以下: https://www.jianshu.com/p/5183fe0f00d8sql
咱們前面已經知道了對於 DML 語句來說其數據的更改將被放到對應的 Event 中。好比‘Delete’語句會將全部刪除數據的 before_image放到DELETE_ROWS_EVENT 中,從庫只要讀取這些 before_image 進行數據查找,而後調用相應的‘Delete’的操做就能夠完成數據的刪除了。下面咱們來討論一下從庫是如何進行數據查找的。app
本節咱們假定參數 binlog_row_image 設置爲‘FULL’也就是默認值,關於 binlog_row_image 參數的影響在第11節已經描述過了。ide
在開始以前咱們先假定參數‘slave_rows_search_algorithms’爲默認值,即:函數
由於這個參數會直接影響到對索引的利用方式。性能
咱們仍是以‘Delete’操做爲例,實際上對於索引的選擇‘Update’操做也是同樣的,由於都是經過 before_image 去查找數據。我測試的表結構、數據和操做以下:測試
mysql> show create table tkkk \G *************************** 1. row *************************** Table: tkkk Create Table: CREATE TABLE `tkkk` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, KEY `a` (`a`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql> select * from tkkk; +------+------+------+ | a | b | c | +------+------+------+ | 1 | 1 | 1 | | 2 | 2 | 2 | | 3 | 3 | 3 | | 4 | 4 | 4 | | 5 | 5 | 5 | | 6 | 6 | 6 | | 7 | 7 | 7 | | 8 | 8 | 8 | | 9 | 9 | 9 | | 10 | 10 | 10 | | 11 | 11 | 11 | | 12 | 12 | 12 | | 13 | 13 | 13 | | 15 | 15 | 15 | | 15 | 16 | 16 | | 15 | 17 | 17 | +------+------+------+ 16 rows in set (2.21 sec) mysql> delete from tkkk where a=15; Query OK, 3 rows affected (6.24 sec) 由於我作了debug索引這裏時間看起來很長
對於這樣一個‘Delete’語句來說主庫會利用到索引 KEY a
,刪除的三條數據咱們實際上只須要一次索引的定位(參考btr_cur_search_to_nth_level函數),而後順序掃描接下來的數據進行刪除就能夠了。大概的流程以下圖:debug
這條數據刪除的三條數據的 before_image 將會記錄到一個 DELETE_ROWS_EVENT 中。從庫應用的時候會從新評估應該使用哪一個索引,優先使用主鍵和惟一鍵。對於 Event 中的每條數據都須要進行索引定位操做,而且對於非惟一索引來說第一次返回的第一行數據可能並非刪除的數據,還須要須要繼續掃描下一行,在函數 Rows_log_event::do_index_scan_and_update 中有以下代碼:3d
while (record_compare(m_table, &m_cols))//比較每個字段 若是不相等 掃描下一行 { while((error= next_record_scan(false)))//掃描下一行 { /* We just skip records that has already been deleted */ if (error == HA_ERR_RECORD_DELETED) continue; DBUG_PRINT("info",("no record matching the given row found")); goto end; } }
這些代價是比主庫更大的。在這個列子中沒有主鍵和惟一鍵,所以依舊使用的是索引KEY a
,大概流程以下圖:
可是若是咱們在從庫增長一個主鍵,那麼在從庫進行應用的時候流程以下:
咱們從上面的流程來看,主庫‘Delete’操做和從庫‘Delete’操做主要的區別在於:
對於主庫來說通常只須要一次數據定位查找便可,接下來訪問下一條數據就行了。其實對於真正的刪除操做來說並無太多的區別。若是合理的使用了主鍵和惟一鍵能夠將上面提到的兩點影響下降。在形成從庫延遲的狀況中,沒有合理的使用主鍵和惟一鍵是一個比較重要的緣由。
最後若是表上一個索引都沒有的話,那麼狀況變得更加嚴重,簡單的圖以下:
咱們能夠看到每一行數據的更改都須要進行全表掃描,這種問題就很是嚴重了。這種狀況使用參數‘slave_rows_search_algorithms’的HASH_SCAN選項也許能夠提升性能,下面咱們就來進行討論。
前面的例子中咱們接觸了參數‘slave_rows_search_algorithms’,這個參數主要用於確認如何查找數據。其取值能夠是下面幾個組合(來自官方文檔),源碼中體現爲一個位圖:
在源碼中有以下的說明,固然官方文檔也有相似的說明:
/* Decision table: - I --> Index scan / search - T --> Table scan - Hi --> Hash over index - Ht --> Hash over the entire table |--------------+-----------+------+------+------| | Index\Option | I , T , H | I, T | I, H | T, H | |--------------+-----------+------+------+------| | PK / UK | I | I | I | Hi | | K | Hi | I | Hi | Hi | | No Index | Ht | T | Ht | Ht | |--------------+-----------+------+------+------| */
實際上源碼中會有三種數據查找的方式,分別是:
對應函數接口:Rows_log_event::do_index_scan_and_update
對應函數接口:Rows_log_event::do_hash_scan_and_update 它又包含: (1) Hi --> Hash over index (2) Ht --> Hash over the entire table 後面討論
對應函數接口:Rows_log_event::do_table_scan_and_update
在源碼中以下:
switch (m_rows_lookup_algorithm)//根據不一樣的算法決定使用哪一個方法 { case ROW_LOOKUP_HASH_SCAN: do_apply_row_ptr= &Rows_log_event::do_hash_scan_and_update; break; case ROW_LOOKUP_INDEX_SCAN: do_apply_row_ptr= &Rows_log_event::do_index_scan_and_update; break; case ROW_LOOKUP_TABLE_SCAN: do_apply_row_ptr= &Rows_log_event::do_table_scan_and_update; break;
決定如何查找數據以及經過哪一個索引查找正是經過參數‘slave_rows_search_algorithms’的設置和表中是否有合適的索引共同決定的,並非徹底由‘slave_rows_search_algorithms’參數決定。
下面這個圖就是決定的過程,能夠參考函數decide_row_lookup_algorithm_and_key(圖24-1,高清原圖包含在文末原圖中)。
總的來說這種方式和 ROW_LOOKUP_INDEX_SCAN和ROW_LOOKUP_TABLE_SCAN 都不一樣,它是經過表中的數據和 Event 中的數據進行比對,而不是經過 Event 中的數據和表中的數據進行比對,下面咱們將詳細描述這種方法。
假設咱們將參數‘slave_rows_search_algorithms’設置爲 INDEX_SCAN,HASH_SCAN ,且表上沒有主鍵和惟一鍵的話,那麼上圖的流程將會把數據查找的方式設置爲 ROW_LOOKUP_HASH_SCAN。
在 ROW_LOOKUP_HASH_SCAN 又包含兩種數據查找的方式:
對於 ROW_LOOKUP_HASH_SCAN 來說,其首先會將 Event 中的每一行數據讀取出來存入到 HASH 結構中,若是可以使用到 Hi 那麼還會額外維護一個集合(set),將索引鍵值存入集合,做爲索引掃描的依據。若是沒有索引這個集合(set)將不會維護直接使用全表掃描,即Ht。 Ht --> Hash over the entire table 會全表掃描,其中每行都會查詢 hash 結構來比對數據。Hi --> Hash over index 則會經過前面咱們說的集合(set)來進行索引定位掃描,每行數據也會去查詢 hash 結構來比對數據。
須要注意一點這個過程的單位是 Event,咱們前面說過一個 DELETE_ROWS_EVENT 可能包含了多行數據,Event 最大爲 8K 左右。所以使用 Ht --> Hash over the entire table 的方式,將會從原來的每行數據進行一次全表掃描變爲每一個 Event 才進行一次全表掃描。
可是對於 Hi --> Hash over index 來說效果就沒有那麼明顯了,由於若是刪除的數據重複值不多的狀況下,依然須要足夠多的索引定位查找才行,可是若是刪除的數據重複值較多那麼構造的集合(set)元素將會大大減小,也就減小了索引查找定位的開銷。
考慮另一種狀況,若是個人每條 delete 語句一次只刪除一行數據而不是 delete 一條語句刪除大量的數據,那這種狀況每一個 DELETE_ROWS_EVENT 只有一條數據存在,那麼使用 ROW_LOOKUP_HASH_SCAN 方式並不會提升性能,由於這條數據仍是須要進行一次全表掃描或者索引定位才能查找到數據,和默認的方式沒什麼區別。
整個過程參考以下接口:
下面咱們仍是用最開始的列子,咱們刪除了三條數據,所以 DELETE_ROW_EVENT 中包含了三條數據。假設咱們參數‘slave_rows_search_algorithms’設置爲 INDEX_SCAN,HASH_SCAN。由於個人表中沒有主鍵和惟一鍵,所以會最終使用 ROW_LOOKUP_HASH_SCAN 進行數據查找。可是由於咱們有一個索引 key a,所以會使用到 Hi --> Hash over index。爲了更好的描述 Hi 和 Ht 兩種方式,咱們也假定另外一種狀況是表上一個索引都沒有,我將兩種方式放到一個圖中方便你們發現不一樣點,以下圖(圖24-2,高清原圖包含在文末原圖中):
我記得之前有位朋友問我主庫沒有主鍵若是我在從庫創建一個主鍵能下降延遲嗎?這裏咱們就清楚了答案是確定的,由於從庫會根據 Event 中的行數據進行使用索引的選擇。那麼總結一下:
slave_rows_search_algorithms 參數設置了 HASH_SCAN 並不必定會提升性能,只有知足以下兩個條件纔會提升性能: (1)(表中沒有任何索引)或者(有索引且本條 update/delete 的數據關鍵字重複值較多)。 (2) 一個 update/delete 語句刪除了大量的數據,造成了不少個 8K 左右的 UPDATE_ROW_EVENT/DELETE_ROW_EVENT。update/delete 語句只修改少許的數據(好比每一個語句修改一行數據)並不能提升性能。
從庫索引的利用是自行判斷的,順序爲主鍵->惟一鍵->普通索引。
若是 slave_rows_search_algorithms 參數沒有設置 HASH_SCAN ,而且沒有主鍵/惟一鍵那麼性能將會急劇降低形成延遲。若是連索引都沒有那麼這個狀況更加嚴重,由於更改的每一行數據都會引起一次全表掃描。
所以咱們發如今 MySQL 中強制設置主鍵又多了一個理由。
第24節結束