本節咱們假定參數binlog_row_image設置爲‘FULL’也就是默認值。mysql
1、從一個列子出發算法
在開始以前咱們先假定參數‘slave_rows_search_algorithms’爲默認值,即:sql
TABLE_SCAN,INDEX_SCANapp
由於這個參數會直接影響到對索引的利用方式。ide
咱們仍是以‘Delete’操做爲例,實際上對於索引的選擇‘Update’操做也是同樣的,由於都是經過before_image去查找數據。我測試的表結構、數據和操做以下:函數
mysql> show create table tkkk \G性能
*************************** 1. row ***************************測試
Table: tkkkspa
Create Table: CREATE TABLE `tkkk` (debug
`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函數),而後順序掃描接下來的數據進行刪除就能夠了。大概的流程以下圖:
這條數據刪除的三條數據的before_image將會記錄到一個DELETE_ROWS_EVENT中。從庫應用的時候會從新評估應該使用哪一個索引,優先使用主鍵和惟一鍵。對於Event中的每條數據都須要進行索引定位操做,而且對於非惟一索引來說第一次返回的第一行數據可能並非刪除的數據,還須要須要繼續掃描下一行,在函數Rows_log_event::do_index_scan_and_update中有以下代碼:
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選項也許能夠提升性能,下面咱們就來進行討論。
2、確認查找數據的方式
前面的例子中咱們接觸了參數‘slave_rows_search_algorithms’,這個參數主要用於確認如何查找數據。其取值能夠是下面幾個組合(來自官方文檔),源碼中體現爲一個位圖:無錫婦科醫院排行 http://www.0510bhyy.com/
TABLE_SCAN,INDEX_SCAN(默認值)
INDEX_SCAN,HASH_SCAN
TABLE_SCAN,HASH_SCAN
TABLE_SCAN,INDEX_SCAN,HASH_SCAN
在源碼中有以下的說明,固然官方文檔也有相似的說明:
/*
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 |
|--------------+-----------+------+------+------|
*/
實際上源碼中會有三種數據查找的方式,分別是:
ROW_LOOKUP_INDEX_SCAN
對應函數接口:Rows_log_event::do_index_scan_and_update
ROW_LOOKUP_HASH_SCAN
對應函數接口:Rows_log_event::do_hash_scan_and_update
ROW_LOOKUP_TABLE_SCAN
對應函數接口: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(以下圖)。
3、ROW_LOOKUP_HASH_SCAN方式的數據查找
總的來說這種方式和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又包含兩種數據查找的方式:
Hi --> Hash over index
Ht --> Hash over the entire table
對於ROW_LOOKUP_HASH_SCAN來說,其首先會將Event中的每一行數據讀取出來存入到HASH結構中,若是可以使用到Hi那麼還會額外維護一個集合(set),將索引鍵值存入集合,做爲索引掃描的依據。若是沒有索引這個集合(set)將不會維護直接使用全表掃描,即Ht。
須要注意一點這個過程的單位是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方式並不會提升性能,由於這條數據仍是須要進行一次全表掃描或者索引定位才能查找到數據,和默認的方式沒什麼區別。
整個過程參考以下接口:
Rows_log_event::do_hash_scan_and_update:總接口,調用下面兩個接口。
Rows_log_event::do_hash_row:將數據加入到hash結構,若是有索引還須要維護集合(set)。
Rows_log_event::do_scan_and_update:查找而且進行刪除操做,會調用Rows_log_event::next_record_scan進行數據查找。
Rows_log_event::next_record_scan:具體的查找方式實現了Hi --> Hash over index和Ht --> Hash over the entire table的查找方式
下面咱們仍是用最開始的列子,咱們刪除了三條數據,所以DELETE_ROW_EVENT中包含了三條數據。假設咱們參數‘slave_rows_search_algorithms’設置爲INDEX_SCAN,HASH_SCAN。由於個人表中沒有主鍵和惟一鍵,所以會最終使用ROW_LOOKUP_HASH_SCAN進行數據查找。可是由於咱們有一個索引key a,所以會使用到Hi --> Hash over index。爲了更好的描述Hi和Ht兩種方式,咱們也假定另外一種狀況是表上一個索引都沒有,我將兩種方式放到一個圖中方便你們發現不一樣點,以下圖:
4、總結
我記得之前有位朋友問我主庫沒有主鍵若是我在從庫創建一個主鍵能下降延遲嗎?這裏咱們就清楚了答案是確定的,由於從庫會根據Event中的行數據進行使用索引的選擇。那麼總結一下:
slave_rows_search_algorithms參數設置了HASH_SCAN並不必定會提升性能,只有知足以下兩個條件纔會提升性能:
從庫索引的利用是自行判斷的,順序爲主鍵->惟一鍵->普通索引。
若是slave_rows_search_algorithms參數沒有設置HASH_SCAN,而且沒有主鍵/惟一鍵那麼性能將會急劇降低形成延遲。若是連索引都沒有那麼這個狀況更加嚴重,由於更改的每一行數據都會引起一次全表掃描。
所以咱們發如今MySQL中強制設置主鍵又多了一個理由。