成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

數(shù)據(jù)庫 MySQL
不久前用戶反饋部門的 MySQL 數(shù)據(jù)庫發(fā)生了數(shù)據(jù)更新丟失。為了解決這個(gè)問題,當(dāng)時(shí)對(duì)用戶使用的場景進(jìn)行了分析。發(fā)現(xiàn)可能是因?yàn)橛脩粼趦膳_(tái)互為主從的機(jī)器上都進(jìn)行了寫入導(dǎo)致的數(shù)據(jù)丟失。

 一、問題起源

[[337371]]

不久前用戶反饋部門的 MySQL 數(shù)據(jù)庫發(fā)生了數(shù)據(jù)更新丟失。為了解決這個(gè)問題,當(dāng)時(shí)對(duì)用戶使用的場景進(jìn)行了分析。發(fā)現(xiàn)可能是因?yàn)橛脩粼趦膳_(tái)互為主從的機(jī)器上都進(jìn)行了寫入導(dǎo)致的數(shù)據(jù)丟失。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

如圖所示,是正常和異常情況下應(yīng)用寫入數(shù)據(jù)庫的示例。隨后在更加深入調(diào)查問題的過程中,DBA 發(fā)現(xiàn)了故障引起數(shù)據(jù)丟失的原因:

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

如圖 1-2 所示為故障具體過程的還原。從圖中可以看出在第 3 步 DP 上的寫入操作,在恢復(fù) DA 到 DP 的同步之后,覆蓋了第 4 步 DA 上的寫入。因此導(dǎo)致了最終兩臺(tái)機(jī)器數(shù)據(jù)不一致,并且有一部分?jǐn)?shù)據(jù)更新丟失。

在這里相信讀者都會(huì)有一個(gè)疑問, 在第 4 步之后數(shù)據(jù)變成了(id : 1 ,name : name4),那么第 3 步操作的時(shí)候?qū)懭氲恼Z句是 update t20200709 set name = 'name3' where id =1 and name='name2',在第 5 步恢復(fù)同步的時(shí)候這條語句在 DA 上重放應(yīng)該不會(huì)被成功執(zhí)行,畢竟 Where 條件都不匹配了。而且在 DP 產(chǎn)生的 Binlog 中,確實(shí)也記錄了 SQL 語句的 Where 條件,無論從哪個(gè)角度上來看第 3 步的 SQL 語句都不應(yīng)該被重放成功。

 

  1. ### UPDATE `test`.`t20200709` 
  2. ### WHERE 
  3. ###   @1=1 /* INT meta=0 nullable=0 is_null=0 */ 
  4. ###   @2='name2' /* VARSTRING(255) meta=255 nullable=1 is_null=0 */ 
  5. ### SET 
  6. ###   @1=1 /* INT meta=0 nullable=0 is_null=0 */ 
  7. ###   @2='name3' /* VARSTRING(255) meta=255 nullable=1 is_null=0 */ 
  8. at 684315240 

那么這個(gè)問題難道是 MySQL 自身的 Bug,抑或是 MySQL 在某些特殊參數(shù)或者條件下的正常表現(xiàn)?對(duì)于這個(gè)問題,本文將可能的給出這個(gè)問題的詳細(xì)解釋和分析。

二、Row 格式下 RelayLog 的重放

2.1 BEFOR IMAGE && AFTER IMAGE && binlog_row_image 參數(shù)

在最后解釋本文最初提出的問題前,需要先來看下 RelayLog 是怎么被重放的。一般情況下,當(dāng)有 DML 語句變更數(shù)據(jù)庫中的數(shù)據(jù)的時(shí)候,Binlog 會(huì)記錄下事件描述信息、BEFORE IMAGE 和 AFTER IMAGE 等信息。在這里有一個(gè)概念 BEFORE IMAGE 和 AFTER IMAGE 需要先介紹下:

1. BEFORE IMAGE : 前鏡像,既數(shù)據(jù)修改前的樣子。

2. AFTER IMAGE : 后鏡像,既數(shù)據(jù)修改后的樣子。

為了方便理解,這里貼一個(gè) Binlog 的例子。假設(shè)當(dāng)前有表 t20200709,然后表中數(shù)據(jù)如下:

 

  1. mysql> select * from t20200709 ; 
  2. +----+-------+ 
  3. | id | name  | 
  4. +----+-------+ 
  5. |  1 | name4 | 
  6. +----+-------+ 
  7. rows in set (0.00 sec) 

之后執(zhí)行 SQL 語句 update t20200709 set name =1 where id = 1;

 

  1. mysql> update t20200709 set name =1 where id = 1; 
  2. Query OK, 1 row affected (0.00 sec) 
  3. Rows matched: 1  Changed: 1  Warnings: 0 

然后來看下 Binlog 中的記錄:

 

  1. #200715 17:28:28 server id 15218  end_log_pos 400 CRC32 0xe4dedec0     Update_rows: table id 4034114356 flags: STMT_END_F 
  2. ### UPDATE `test`.`t20200709` 
  3. ### WHERE 
  4. ###   @1=1 /* INT meta=0 nullable=0 is_null=0 */ 
  5. ###   @2='name4' /* VARSTRING(255) meta=255 nullable=1 is_null=0 */ 
  6. ### SET 
  7. ###   @1=1 /* INT meta=0 nullable=0 is_null=0 */ 
  8. ###   @2='1' /* VARSTRING(255) meta=255 nullable=1 is_null=0 */ 
  9. at 400 

可以見得,在修改之前 name 字段的值是 name4,在 Binlog 中用 Where 條件 @2='name4' 來指明,而修改后的 name 的值是 '1',在 Binlog 中就是 @2='1' 來指明。因此 BEFORE IMAGE 就是 Binlog 中 WHERE 到 SET 的部分。而 AFTER IMAGE 就是 SET 之后的部分。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

那么 DELETE,UPDATE 和 INSERT 語句被記錄在 Binlog 中的時(shí)候,是否都有 BEFORE IMAGE 和 AFTER IMAGE?其實(shí)不是所有的 DML 事件類型都擁有兩個(gè) IMAGE 的,參見圖 2-2 可知只有 UPDATE 語句,會(huì)同時(shí)擁有 BEFORE IMAGE 和 AFTER IMAGE。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

BEFOR IMAGE 和 AFTER IMAGE 默認(rèn)會(huì)記錄所有的列的變更,因此會(huì)導(dǎo)致 Binlog 的內(nèi)容變得很大。那么有沒有參數(shù)可以控制 IMAGE(對(duì)于 BEFOR IMAGE 和 AFTER IMAGE 以下合并簡稱為 IMAGE)的行為?MySQL 5.7 之后引入了一個(gè)新的參數(shù) binlog_row_image 。

參數(shù)說明:

binlog_row_image:https://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html#sysvar_binlog_row_image

用于控制 IMAGE 的行為。binlog_row_image 參數(shù)的值有三個(gè):

1. full:Log all columns in both the before image and the after image. 既所有的列的值的變更,都會(huì)在 IMAGE 中記錄。系統(tǒng)默認(rèn)是 full。

2. minimal:Log only those columns in the before image that are required to identify the row to be changed; log only those columns in the after image where a value was specified by the SQL statement, or generated by auto-increment. BEFOR IMAGE 只記錄哪些能夠唯一標(biāo)識(shí)數(shù)據(jù)的列,比如主鍵,唯一鍵等。AFTER IMAGE 只記錄了變更的列。可以看出,minimal 會(huì)有效的減少 Binlog 的大小。

3. noblob:Log all columns (same as full), except for BLOB and TEXT columns that are not required to identify rows, or that have not changed. 對(duì)于其他列的行為都和 full 參數(shù)一樣。但是對(duì)于 BLOB 和 TEXT,在不是可以標(biāo)識(shí)數(shù)據(jù)行或者有變更的情況下不做記錄。

參數(shù)說明:

BLOB:https://dev.mysql.com/doc/refman/5.7/en/blob.html

TEXT:https://dev.mysql.com/doc/refman/5.7/en/blob.html

可以看出 binlog_row_image 可以有效控制Binlog的大小,但是如果要保證數(shù)據(jù)的一致性,最好的值就是設(shè)置為 full。

2.2 slave_rows_search_algorithms 參數(shù)

前文提到了 IMAGE 與 binlog_row_image 相關(guān)的內(nèi)容。本節(jié)開始將主要介紹 Relay Log 的重放的時(shí)候,對(duì)于被重放的記錄的查找邏輯。對(duì)于 DELETE 和 UPDATE 操作,需要先對(duì)數(shù)據(jù)庫中的記錄進(jìn)行檢索以確定需要執(zhí)行 Binlog 重放的數(shù)據(jù)。如果從庫的表上沒有主鍵或唯一鍵時(shí),則需要根據(jù)每一個(gè)行記錄 BEFOR IMAGE 在所有數(shù)據(jù)中進(jìn)行一次全表掃描。在大多數(shù)情況下這種開銷非常巨大,會(huì)導(dǎo)致從庫和主庫的巨大延遲。從 MySQL 5.6 開始提供了參數(shù) slave_rows_search_algorithms

參數(shù)說明:slave_rows_search_algorithms:https://dev.mysql.com/doc/refman/5.7/en/replication-options-replica.html#sysvar_slave_rows_search_algorithms

用于控制在 Relay Log 執(zhí)行重放的時(shí)候?qū)τ谟涗浀臋z索行為。其基本的思路是收集每條記錄的 BEFOR IMAGE 信息,然后根據(jù) BEFOR IMAGE 的信息在被重放的表中檢索對(duì)應(yīng)的記錄。根據(jù) MySQL 的文檔,檢索數(shù)據(jù)的方式有如下的幾種:

1. INDEX_SCAN

2. TABLE_SCAN

3. HASH_SCAN

如上三個(gè)方式可以兩兩組合并賦值給 slave_rows_search_algorithms 參數(shù)。MySQL 文檔也給出了如下的說明:

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

  • The default value is INDEX_SCAN,TABLE_SCAN, which means that all searches that can use indexes do use them, and searches without any indexes use table scans.
  • To use hashing for any searches that do not use a primary or unique key, set INDEX_SCAN,HASH_SCAN. Specifying INDEX_SCAN,HASH_SCAN has the same effect as specifying INDEX_SCAN,TABLE_SCAN,HASH_SCAN, which is allowed.
  • Do not use the combination TABLE_SCAN,HASH_SCAN. This setting forces hashing for all searches. It has no advantage over INDEX_SCAN,HASH_SCAN, and it can lead to “record not found” errors or duplicate key errors in the case of a single event containing multiple updates to the same row, or updates that are order-dependent.

1. INDEX_SCAN,TABLE_SCAN: 可以看出在默認(rèn)的情況下,既 INDEX_SCAN,TABLE_SCAN 如果有主鍵或者唯一鍵,則通過主鍵或者唯一鍵來查詢數(shù)據(jù)并重放 AFTER IMAGE。如果沒有主鍵或者唯一鍵,則通過二級(jí)索引完成這個(gè)工作。如果什么都沒有,則使用全表掃描的方式。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

2. INDEX_SCAN,HASH_SCAN : 在表有主鍵或者唯一鍵的情況下, INDEX_SCAN,HASH_SCAN 配置也是使用的主鍵或者唯一鍵去定位數(shù)據(jù)。在表有二級(jí)索引或者完全沒有索引的情況下會(huì)使用 HASH_SCAN 的方法。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

可以見得 Slave 檢索需要重放的數(shù)據(jù)的時(shí)候,三個(gè)檢索方式的優(yōu)先級(jí)是 Index Scan > Hash Scan > Table Scan

相信讀者在這里會(huì)有 2 個(gè)疑問:

1. Hash Scan 的原理是什么?它和 Table Scan 以及 Index Scan 有什么區(qū)別?文檔中還提到了 Hash scan over index 這個(gè)和 Index 有什么關(guān)系?

2. 前文提到在表沒有主鍵或者唯一鍵的時(shí)候,會(huì)通過二級(jí)索引來定位數(shù)據(jù)。假設(shè)表中有 N 個(gè)二級(jí)索引(包括單列索引和聯(lián)合索引),哪個(gè)二級(jí)索引會(huì)被選中?

2.3 Hash Scan && Table Scan && Index Scan 實(shí)現(xiàn)

分析 MySQL 源碼可知最后決定使用哪個(gè)檢索方式是在函數(shù) Rows_log_event::decide_row_lookup_algorithm_and_key 里面實(shí)現(xiàn)的。

 

  1. 9745 void 
  2. 9746 Rows_log_event::decide_row_lookup_algorithm_and_key() 
  3. 9747 { 
  4. 9748 
  5. ... ... 
  6.  
  7. 9781   /* PK or UK => use LOOKUP_INDEX_SCAN */ 
  8. 9782   this->m_key_index= search_key_in_table(table, cols, (PRI_KEY_FLAG | UNIQUE_KEY_FLAG)); 
  9. 9783   if (this->m_key_index != MAX_KEY) 
  10. 9784   { 
  11. 9785     DBUG_PRINT("info", ("decide_row_lookup_algorithm_and_key: decided - INDEX_SCAN")); 
  12. 9786     this->m_rows_lookup_algorithm= ROW_LOOKUP_INDEX_SCAN; 
  13. 9787     goto end
  14. 9788   } 
  15. ... ... 
  16.  
  17. 9790 TABLE_OR_INDEX_HASH_SCAN: 
  18. ... ... 
  19.  
  20. 9808 TABLE_OR_INDEX_FULL_SCAN: 
  21. ... ... 
  22.  
  23. 9827 end
  24. ... ... 

在 9782 行會(huì)先檢索表中是否有主鍵和唯一鍵。之后在 TABLE_OR_INDEX_HASH_SCAN 和 TABLE_OR_INDEX_FULL_SCAN 決定最后使用哪種檢索方式。在 do_apply_event 函數(shù)中,會(huì)根據(jù) decide_row_lookup_algorithm_and_key 的結(jié)果去調(diào)用函數(shù):

 

  1. 11286     switch (m_rows_lookup_algorithm) 
  2. 11287     { 
  3. 11288       case ROW_LOOKUP_HASH_SCAN: 
  4. 11289         do_apply_row_ptr= &Rows_log_event::do_hash_scan_and_update; 
  5. 11290         break; 
  6. 11291 
  7. 11292       case ROW_LOOKUP_INDEX_SCAN: 
  8. 11293         do_apply_row_ptr= &Rows_log_event::do_index_scan_and_update; 
  9. 11294         break; 
  10. 11295 
  11. 11296       case ROW_LOOKUP_TABLE_SCAN: 
  12. 11297         do_apply_row_ptr= &Rows_log_event::do_table_scan_and_update; 
  13. 11298         break; 
  14. 11299 
  15. 11300       case ROW_LOOKUP_NOT_NEEDED: 
  16. 11301         DBUG_ASSERT(get_general_type_code() == binary_log::WRITE_ROWS_EVENT); 
  17. 11302 
  18. 11303         /* No need to scan for rows, just apply it */ 
  19. 11304         do_apply_row_ptr= &Rows_log_event::do_apply_row; 
  20. 11305         break; 
  21. 11306 
  22. 11307       default
  23. 11308         DBUG_ASSERT(0); 
  24. 11309         error= 1; 
  25. 11310         goto AFTER_MAIN_EXEC_ROW_LOOP; 
  26. 11311         break; 
  27. 11312     } 

可以見得:

1. do_hash_scan_and_update: 對(duì)應(yīng) hash_scan 方式。

2. do_index_scan_and_update: 對(duì)應(yīng) index_scan 方式。

3. do_table_scan_and_update:對(duì)應(yīng) table_scan 方式。

接下來分別介紹下這三個(gè)函數(shù)所完成的內(nèi)容。

2.3.1 do_hash_scan_and_update

do_hash_scan_and_update 函數(shù)主要實(shí)現(xiàn)了 Hash Scan 檢索數(shù)據(jù)的功能。在實(shí)現(xiàn)方式上又可以分為 H --> Hash Scan 和 Hi --> Hash over Index 兩種方式。首先來看下 Hash Scan 的實(shí)現(xiàn)方法,圖 2-5 給出 Hash Scan 的實(shí)現(xiàn)邏輯。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

可以見得 Binlog 中的 BI 在 Slave 上會(huì)被處理到一個(gè) Hash 表中。因?yàn)闆]有合適的索引可以使用,所以使用全表掃描的方式每獲取一條記錄就根據(jù)記錄的值計(jì)算一個(gè) hash 值,然后在 BI 的 Hash 表中匹配。如果匹配到了 BI,則重放并刪除 Hash 表中的記錄。

如果 test06 表中 id 列上有索引,那么在 Slave 重放的使用會(huì)使用 Hi --> Hash over index 的方式。如圖 2-6 所示給出了 Hash over Index 方式(以下均簡稱 Hi)的實(shí)現(xiàn)邏輯。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

可以見得如果通過 Hi 方式進(jìn)行重放,則會(huì)對(duì)使用的二級(jí)索引生成一個(gè) m_distinct_keys 結(jié)構(gòu),這個(gè)結(jié)構(gòu)存放著這個(gè) BI 中這個(gè)索引所有的去重值。然后對(duì)于 Slave 上的 test06 表通過 m_distinct_keys 中的每一個(gè)值在二級(jí)索引上進(jìn)行遍歷,遍歷獲取的記錄與 m_hash 中的結(jié)果對(duì)比并執(zhí)行重放邏輯。

ps : 對(duì)于 Hash Scan 方式還要一個(gè)比較迷惑的特性,讀者可以參考下這篇文章[技術(shù)分享 | HASH_SCAN BUG 之迷惑行為大賞]

2.3.2 do_index_scan_and_update

Index Scan 方式會(huì)通過索引檢索 Slave 上需要重放的數(shù)據(jù)。通過索引檢索數(shù)據(jù)的方式又可以分為:

1. 通過主鍵/唯一鍵索引檢索數(shù)據(jù)。

2. 通過二級(jí)索引檢索數(shù)據(jù)。

在通過主鍵或者唯一鍵索引檢索數(shù)據(jù)的時(shí)候會(huì)調(diào)用 do_index_scan_and_update 函數(shù),在函數(shù)邏輯中直接通過主鍵/唯一鍵索引返回了記錄然后重放 Binlog。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

而在通過二級(jí)索引檢索數(shù)據(jù)的時(shí)候,會(huì)對(duì)二級(jí)索引返回的數(shù)據(jù)與 BI 中每一條記錄做比較,如果一致就會(huì)重放 Binlog。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

至此可以發(fā)現(xiàn) Index Scan 下對(duì)于主鍵/唯一鍵和二級(jí)索引的實(shí)現(xiàn)邏輯有一些不同。對(duì)于主鍵/唯一鍵,對(duì)于索引到的記錄并不會(huì)和 BI 中的每一個(gè)列做比較,而二級(jí)索引獲取到的數(shù)據(jù)會(huì)與 BI 中每一個(gè)列做比較,如果不一致而不會(huì)重放并報(bào)錯(cuò)。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

2.3.3 do_table_scan_and_update

Table Scan 的實(shí)現(xiàn)相對(duì)簡單,如果沒有任何的索引可以使用,只能通過全表掃描的方式獲取每一行數(shù)據(jù)并和BI中的每一行做比較。因此如果 Slave 上的數(shù)據(jù)和 Master 上的數(shù)據(jù)不一致,也會(huì)如圖 2-9 中所示一樣報(bào)錯(cuò)。關(guān)于 Table Scan 更加具體的實(shí)現(xiàn)方式,讀者可以參考 MySQL 源碼 sql/log_event.cc 文件中的 do_table_scan_and_update 函數(shù),在這里就不過多的展開。

2.3.4 小結(jié)

至此可以回答本文之前提出的這個(gè)問題了:

Hash scan 方法的原理是什么?它和 Table scan 以及 Index scan 有什么區(qū)別?文檔中還提到了 Hash scan over index 這個(gè)和 Index 又有什么關(guān)系?

可以見得,Hash Scan 的原理是將 BI 每一行的內(nèi)容都放入一個(gè) Hash 表中。如果可以使用二級(jí)索引(既 Hash scan over index 這個(gè)方式),則額外的對(duì) BI 中二級(jí)索引的值生成一個(gè) Hash 結(jié)構(gòu),并且將 BI 中二級(jí)索引列的去重值放入這個(gè) Hash 結(jié)構(gòu)中。之后不管是通過全表掃描還是索引的方式獲取數(shù)據(jù),都會(huì)通過 Hash 結(jié)構(gòu)去定位 BI 中的數(shù)據(jù)。對(duì)于 Table Scan 和 Index Scan 在獲取表中的每一行之后,都需要去和 BI 中的記錄做一次查找和比較(有主鍵或者唯一鍵的時(shí)候不做比較),而 BI 的每一行并沒有生成類似于 Hash 的結(jié)構(gòu),因此從算法的時(shí)間復(fù)雜度效率上來說是屬于 O(n^2) 的。而 Hash Scan 在獲取一條記錄之后也需要根據(jù) BI 生成的 Hash 結(jié)構(gòu)中查找記錄,但是對(duì)于 Hash 結(jié)構(gòu)的查找來說效率是 O(1),因此可以忽略不計(jì)。由此可以看出,在沒有主鍵的情況下 Hi 和 Ht 方式的效率是會(huì)比 Table Scan 和 Index Scan 來的高一些。

同時(shí)到這里,也可以回答本文開頭的問題,為什么當(dāng)前表中的記錄有一列值已經(jīng)和 BI 中的記錄不一致了,Binlog 中的操作還會(huì)重放。原因就是因?yàn)樵谀J(rèn)的 INDEX_SCAN,TABLE_SCAN 方式下,對(duì)于有主鍵/唯一鍵的表不會(huì)去比較 BI 中的記錄是否和檢索到的數(shù)據(jù)一致。

2.4 Hash Scan Over Index && Index Scan 中二級(jí)索引的選擇

前文提到了在有二級(jí)索引的情況下,Hash Scan 和 Index Scan 都會(huì)選擇二級(jí)索引進(jìn)行掃描。如果表中存在多個(gè)二級(jí)索引,MySQL 會(huì)選擇哪個(gè)?通過源碼分析,最后驚訝的發(fā)現(xiàn),在 binlog_row_image 參數(shù)是 Full 的情況下,如果表中存在多個(gè)二級(jí)索引,MySQL 會(huì)默認(rèn)選擇使用第一個(gè)索引進(jìn)行重放。在 decide_row_lookup_algorithm_and_key 函數(shù)中,除了決定了使用哪種方式檢索數(shù)據(jù)以外(例如使用 Hash Scan 還是 Table Scan),也決定了后續(xù)使用哪個(gè)索引。

 

故障分析 | 記一次 MySQL 主從雙寫導(dǎo)致的數(shù)據(jù)丟失問題

 

如圖 2-10 給出了選擇二級(jí)索引的時(shí)候的邏輯。可以發(fā)現(xiàn)如果在遍歷的過程中,找到了第一個(gè)所有的列都在 BI 中 key,則會(huì)使用這個(gè) key。給出一個(gè)例子,test06 的表結(jié)構(gòu)和表中數(shù)據(jù)如下:

 

  1. *************************** 1. row *************************** 
  2.        Table: test06 
  3. Create TableCREATE TABLE `test06` ( 
  4.   `id` int(11) NOT NULL
  5.   `namevarchar(255) DEFAULT NULL
  6.   `c1` int(11) DEFAULT NULL
  7.   KEY `k1` (`id`), 
  8.   KEY `k2` (`id`,`name`), 
  9.   KEY `k3` (`c1`,`name`) 
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 
  11. 1 row in set (0.13 sec) 
  12.  
  13. mysql> select * from test06 ; 
  14. +------+-------+------+ 
  15. | id   | name  | c1   | 
  16. +------+-------+------+ 
  17. | 2582 | name3 |    1 | 
  18. | 2582 | name4 |    1 | 
  19. |    1 | name1 |    0 | 
  20. |    1 | name2 |    0 | 
  21. |    1 | name3 |    0 | 
  22. +------+-------+------+ 
  23. rows in set (0.00 sec) 

在 Master 上執(zhí)行 SQL,同時(shí) Master 上的執(zhí)行計(jì)劃如下:

 

  1. delete from test06 where id = 1 and name ='name3' and c1=0; 
  2. mysql> explain delete from test06 where id = 1 and name ='name3' and c1=0; 
  3. +----+-------------+--------+------------+-------+---------------+------+---------+-------------+------+----------+-------------+ 
  4. | id | select_type | table  | partitions | type  | possible_keys | key  | key_len | ref         | rows | filtered | Extra       | 
  5. +----+-------------+--------+------------+-------+---------------+------+---------+-------------+------+----------+-------------+ 
  6. |  1 | DELETE      | test06 | NULL       | range | k1,k2,k3      | k2   | 772     | const,const |    1 |   100.00 | Using where | 
  7. +----+-------------+--------+------------+-------+---------------+------+---------+-------------+------+----------+-------------+ 
  8. 1 row in set (0.00 sec) 

可以見得,在 Master 上優(yōu)化器選擇了 k2 這個(gè)聯(lián)合索引。通過 GDB 跟蹤 Slave 的進(jìn)程,在 log_event.cc 第 9733 行打斷點(diǎn):

 

  1. 9714   if (key_type & MULTIPLE_KEY_FLAG && table->s->keys) 
  2. 9715   { 
  3. 9716     DBUG_PRINT("debug", ("Searching for K.")); 
  4. 9717     for (key=0,keyinfo= table->key_info ; 
  5. 9718          (key < table->s->keys) && (res == MAX_KEY); 
  6. 9719          key++,keyinfo++) 
  7. 9720     { 
  8. 9721       /* 
  9. 9722         - Skip innactive keys 
  10. 9723         - Skip unique keys without nullable parts 
  11. 9724         - Skip indices that do not support ha_index_next() e.g. full-text 
  12. 9725         - Skip primary keys 
  13. 9726       */ 
  14. 9727       if (!(table->s->keys_in_use.is_set(key)) || 
  15. 9728           ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME) || 
  16. 9729           !(table->file->index_flags(key, 0, true) & HA_READ_NEXT) || 
  17. 9730           (key == table->s->primary_key)) 
  18. 9731         continue
  19. 9732 
  20. 9733       res= are_all_columns_signaled_for_key(keyinfo, bi_cols) ? 
  21. 9734            key : MAX_KEY; 
  22. 9735 
  23. 9736       if (res < MAX_KEY) 
  24. 9737         DBUG_RETURN(res); 
  25. 9738     } 
  26. 9739     DBUG_PRINT("debug", ("Not all columns signaled for K.")); 
  27. 9740   } 

可以觀察到這時(shí)候 m_key_index 的值是 0,并且觀察 keyinfo 變量的值為:

 

  1. (gdb) print *keyinfo 
  2. $4 = {key_length = 4, flags = 0, actual_flags = 0, user_defined_key_parts = 1, actual_key_parts = 1, unused_key_parts = 0, usable_key_parts = 1, block_size = 0, algorithm = HA_KEY_ALG_UNDEF, { 
  3.     parser = 0x0, parser_name = 0x0}, key_part = 0x7f2f4c015a00, name = 0x7f2f4c012bb1 "k1", rec_per_key = 0x7f2f4c012bc0, m_in_memory_estimate = -1, rec_per_key_float = 0x7f2f4c012bf8, handler = { 
  4.     bdb_return_if_eq = 0}, table = 0x7f2f4c92d1a0, comment = {str = 0x0, length = 0}} 

接下來,刪除 k1 這個(gè)索引,再來觀察下 m_key_index 和 keyinfo 的值。

 

  1. (gdb) print *keyinfo 
  2. $7 = {key_length = 772, flags = 64, actual_flags = 64, user_defined_key_parts = 2, actual_key_parts = 2, unused_key_parts = 0, usable_key_parts = 2, block_size = 0, algorithm = HA_KEY_ALG_UNDEF, { 
  3.     parser = 0x0, parser_name = 0x0}, key_part = 0x7f2f4c92b680, name = 0x7f2f4c92e7d1 "k2", rec_per_key = 0x7f2f4c92e7d8, m_in_memory_estimate = -1, rec_per_key_float = 0x7f2f4c92e808, handler = { 
  4.     bdb_return_if_eq = 0}, table = 0x7f2f4ca9fd90, comment = {str = 0x0, length = 0}} 

可以發(fā)現(xiàn)刪除了 k1 之后,Slave 上就選擇 k2 這個(gè)索引,和 Master上的執(zhí)行計(jì)劃選擇的索引一致了。通過前面的源碼分析和調(diào)試跟蹤可以發(fā)現(xiàn),MySQL 在 Slave 重放數(shù)據(jù)的時(shí)候(沒有主鍵和唯一鍵的情況),選擇的索引是第一個(gè)所有的列都在 BI 中存在的索引。因此可能存在 Slave 上選擇的索引不是最優(yōu)的導(dǎo)致 Slave 和 Master 有巨大延遲。

三、總結(jié)

至此前文提出的幾個(gè)問題都基本清楚了,可以總結(jié)出如下的幾點(diǎn)內(nèi)容:

1. 在有主鍵或者唯一鍵的情況下,Slave 重放 Binlog 并不會(huì)去比較檢索到的記錄的每一列是否和BI相同,因此如果 Slave 和 Master 存在數(shù)據(jù)不一致,會(huì)直接覆蓋 Slave 的數(shù)據(jù)而不會(huì)報(bào)錯(cuò)。

2. 在沒有主鍵或者唯一鍵的情況下,Hash Scan / Hash Scan Over Index 的執(zhí)行效率 在理論上分析高于 Table Scan 和Index Scan 。

3. 在沒有主鍵或者唯一鍵的情況下,Slave 選擇的二級(jí)索引是第一個(gè)所有的列都在 BI 中存在的索引,不一定是 Master 執(zhí)行計(jì)劃所選擇的索引。

最后本文所有分析的源碼都是基于 mysql-5.7.28 版本。限于作者的水平有限,如果文章中有錯(cuò)誤之處,望大家不吝指正。

責(zé)任編輯:華軒 來源: 今日頭條
相關(guān)推薦

2023-10-11 22:24:00

DubboRedis服務(wù)器

2020-11-16 07:19:17

線上函數(shù)性能

2019-09-11 08:22:57

MySQL數(shù)據(jù)庫遠(yuǎn)程登錄

2018-02-23 13:41:05

數(shù)據(jù)庫MySQL數(shù)據(jù)恢復(fù)

2021-05-13 08:51:20

GC問題排查

2023-04-06 07:53:56

Redis連接問題K8s

2019-03-15 16:20:45

MySQL死鎖排查命令

2022-01-10 09:31:17

Jetty異步處理seriesbaid

2017-09-22 10:16:16

MySQL數(shù)據(jù)庫用戶數(shù)據(jù)

2021-10-14 10:53:20

數(shù)據(jù)庫查詢超時(shí)

2021-11-23 21:21:07

線上排查服務(wù)

2023-04-13 12:00:00

MySQLSQL線程

2021-03-29 12:35:04

Kubernetes環(huán)境TCP

2023-01-05 11:44:43

性能HTTPS

2017-12-19 14:00:16

數(shù)據(jù)庫MySQL死鎖排查

2018-07-11 10:24:33

數(shù)據(jù)恢復(fù)數(shù)據(jù)刪除

2021-11-11 16:14:04

Kubernetes

2011-08-12 09:30:02

MongoDB

2022-06-06 11:31:31

MySQL數(shù)據(jù)查詢

2024-04-10 08:48:31

MySQLSQL語句
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 黄色大片在线 | 欧美一级在线视频 | 欧美一区免费在线观看 | 欧美 日本 国产 | 国产91九色 | 69av网 | 日韩国产欧美视频 | 欧美一级大片免费观看 | 欧美成人激情 | 九九伦理电影 | 欧美一二三 | 久久视频精品 | h视频亚洲| 精品国产成人 | 欧洲成人免费视频 | 日韩中文字幕av | 久久久久久成人 | 欧美精品综合 | 91av视频在线播放 | 中文亚洲视频 | 久久久久网站 | 一区二区三区在线观看免费视频 | 中文字幕日韩一区 | 亚洲精美视频 | 国产精品欧美一区二区 | 国产精品亚洲一区 | 国产欧美一区二区三区另类精品 | 成人午夜在线 | 国产一区二区三区亚洲 | 91精品久久久久久久久久入口 | 成人av片在线观看 | 欧美激情视频一区二区三区免费 | 美国a级毛片免费视频 | 欧洲色综合 | 亚洲精品一区二区三区免 | 欧美激情一区二区三级高清视频 | 精品国产一区二区三区久久久蜜月 | 久久久久久久久久久久亚洲 | 欧美日一区二区 | 中文字幕高清免费日韩视频在线 | 少妇精品久久久久久久久久 |