悲劇啊!Mysql的上古BUG!!!

導讀 這是MySQL8.0修復的上古bug之一,在2003年由Percona的CEO(當時應該還沒Percona吧)提出的bug#199,光看這bug號就撲面而來一股上古時代的滄桑氣息。

u6024910391627713456fm21gp0
問題的本質在於InnoDB初始化AUTO_INCREMENT的方式,在每次重啓時,老是算出表上最大的自增值做爲最大值,下一次分配從該值開始。這意味着若是在btree右側葉節點大量刪除記錄,重啓後,自增值可能被重用。這在不少場景下可能致使問題,包括但不限於:主備切換、歷史數據遷移等場景。在bug#199下面一大堆的回覆裏,能夠看到大量的同行抱怨。html

官方的修復就比較優雅了,不改變任何現有的存儲,而是經過redo log來進行恢復。該補丁基於WL#7816的框架實現的,要想搞懂這個補丁,得先看看WL#7816作了哪些改動,爲了解決這個問題,InnoDB使用一個引擎私有的系統表+特殊redo log的方式,在引擎內部本身解決corruption標記持久化的問題。其大概思路爲:mysql

  1. 當發現索引損壞時,寫入一條redo log,但不更新數據詞典
  2. 引入一個innodb引擎私有的系統表,稱爲DD Buffer Table,每次checkpoint以前會將索引corruption bit存入其中。
  3. 在崩潰恢復時,同時從redo log和DD Buffer Table中讀取索引 corruption bit, 合併結果,並標記內存中的表和索引對象。
初始化Persister

目前Persister的類型僅有兩種,一個用於corruption bit的持久化,一個用於自增列的持久化,對應的類爲:linux

Persister:
    |-- CorruptedIndexPersister
    |-- AutoIncPersister

Persister對應全局對象dict_persist_t::persisters,能夠經過類型persistent_type_t來找到對應的Persister,目前僅有PM_INDEX_CORRUPTED及PM_TABLE_AUTO_INC,但從註釋來看,將來確定會作更多的擴展,Persister在啓動時調用函數dict_persist_init進行初始化。sql

新的系統表

新的系統表名爲SYS_TABLE_INFO_BUFFER,對應管理類爲DDTableBuffer,指針存儲在dict_persist->table_buffer中。app

系統表包含兩個列:TABLE_ID及BLOB類型的METADATA(ref DDTableBuffer::init),METADATA列包含了全部須要持久化的元數據。框架

回寫DDTableBuffer

有幾種狀況會將內存修改回寫到DDTableBuffer中:函數

  1. 在作checkpoint(log_checkpoint)以前,全部在dirty_dict_tables鏈表上的表對象,對應persist metadata都須要回寫到DDTableBuffer中(dict_persist_to_dd_table_buffer)
  2. 從內存中驅逐一個表對象時(dict_table_remove_from_cache_low),若是須要的話也會去嘗試回寫。
  3. 在對包含自增列的表作DDL後,須要持久化counter,在以下函數中,會調用dict_table_set_and_persist_autoinc:
ha_innobase::commit_inplace_alter_table
create_table_info_t::initialize_autoinc()
// for example: alter table..auto_increment = ??
row_rename_table_for_mysql;
// rename from temporary table to normal table

回寫的過程也比較簡單(dict_table_persist_to_dd_table_buffer_low):spa

  1. 經過表對象初始化須要回寫的Metadata數據: corrupt index及autoinc值(dict_init_dynamic_metadata)
  2. 構建記錄值,插入DDTableBuffer系統表(DDTableBuffer::replace(), 若是記錄存在的話,則進行悲觀更新操做
  3. 表對象的diry_status修改爲 METADATA_BUFFERED,表示有buffer的元數據
Recovery and Startup

在崩潰恢復時,當解析到日誌MLOG_TABLE_DYNAMIC_META時(MetadataRecover::parseMetadataLog),會進行解析並將解析獲得的數據存儲到集合中(MetadataRecover::m_tables),若是存在相同table-id的項,就進行替換,確保老是最新的。指針

在完成recovery後,蒐集到的meta信息暫時存儲到srv_dict_metadata中, 隨後進行apply(srv_dict_recover_on_restart), apply的過程也比較簡單,載入表對象,而後對錶對象進行更新(MetadataRecover::apply),例如對於autoinc列,就老是選擇更大的那個值。rest

最後

這個bug已經掛了至關長的時間,不排除把這個bug看成InnoDB的「特性」的同窗,必定要注意到這個改動...

本文地址: http://www.linuxprobe.com/mysql-bug.html

相關文章
相關標籤/搜索