MyRocks DDL原理

来源:http://www.cnblogs.com/cchust/archive/2017/04/17/6716823.html
-Advertisement-
Play Games

最近一個日常實例在做DDL過程中,直接把資料庫給乾趴下了,問題還是比較嚴重的,於是趕緊排查問題,擼了下crash堆棧和alert日誌,發現是在去除唯一約束的場景下,MyRocks存在一個嚴重的bug,於是緊急向官方提了一個bug。其實問題比較隱蔽,因為直接一條DDL語句,資料庫是不會掛了,而是在特定 ...


     最近一個日常實例在做DDL過程中,直接把資料庫給乾趴下了,問題還是比較嚴重的,於是趕緊排查問題,擼了下crash堆棧和alert日誌,發現是在去除唯一約束的場景下,MyRocks存在一個嚴重的bug,於是緊急向官方提了一個bug。其實問題比較隱蔽,因為直接一條DDL語句,資料庫是不會掛了,而是在特定情況下,並且對同一個索引操作多次才會發生,因此排查問題也費了一些時間,具體bug排查和復現過程不在此展開,有興趣的童鞋可以直接看bug鏈接:https://github.com/facebook/mysql-5.6/issues/602。藉著排查問題的機會,我梳理了MyRocks DDL的工作流程,下文主要包括3方面內容:MyRocks數據字典,DDL操作除了修改數據本身,很重要的一個工作是維護數據字典,第二部分是MyRocks DDL的流程,主要圍繞增加/刪除索引的場景展開,最後一部分是分析DDL異常處理邏輯。

數據字典
    所謂數據字典,就是存儲引擎元數據的地方。數據字典可以從兩個維度來看,從用戶角度來看,數據字典就是information_schema表中的
RocksDB相關的表,主要包括ROCKSDB_DDL,ROCKSDB_INDEX_FILE_MAP等。而從RockDB內部實現角度來看,所有元數據都以KV對的方式存儲在system column family中。我們看到的information_schema中表的信息,其實都是通過system column family中的元數據構造出來的,同時在mysqld啟動時,也會構造一份元數據存儲在記憶體中,方便快速檢索查詢。下麵我會列出RocksDB數據字典的幾種類型,併列出每種類型KV對的形式。
// Data dictionary types

enum DATA_DICT_TYPE {
DDL_ENTRY_INDEX_START_NUMBER= 1, //表與索引映射關係
INDEX_INFO= 2, //索引
CF_DEFINITION= 3, //column family
BINLOG_INFO_INDEX_NUMBER= 4,  //binlog位點信息
DDL_DROP_INDEX_ONGOING= 5, //刪除索引字典任務
INDEX_STATISTICS= 6,  //索引統計信息
MAX_INDEX_ID= 7,  //當前最大index_id
DDL_CREATE_INDEX_ONGOING= 8, //添加索引字典任務
END_DICT_INDEX_ID= 255 
};

1). DDL_ENTRY_INDEX_START_NUMBER
表和索引之間的映射關係
key: Rdb_key_def::DDL_ENTRY_INDEX_START_NUMBER(0x1) + dbname.tablename
value: version + {global_index_id}*n_indexes_of_the_table

2). INDEX_INFO
索引id和索引屬性的關係
key: Rdb_key_def::INDEX_INFO(0x2) + global_index_id
value: version, index_type, key_value_format_version

index_type:主鍵/二級索引/隱式主鍵
key_value_format_version: 記錄存儲格式的版本

3). CF_DEFINITION
column family屬性
key: Rdb_key_def::CF_DEFINITION(0x3) + cf_id
value: version, {is_reverse_cf, is_auto_cf}

is_reverse_cf: 是否是reverse column family
is_auto_cf: column family名字是否是$per_index_cf,名字自動由table.indexname組成

4). BINLOG_INFO_INDEX_NUMBER
binlog位點及gtid信息,binlog_commit更新此信息
key: Rdb_key_def::BINLOG_INFO_INDEX_NUMBER (0x4)
value: version, {binlog_name,binlog_pos,binlog_gtid}

5). DDL_DROP_INDEX_ONGOING
刪除的索引任務
key: Rdb_key_def::DDL_DROP_INDEX_ONGOING(0x5) + global_index_id
value: version

6). INDEX_STATISTICS
索引統計信息
key: Rdb_key_def::INDEX_STATISTICS(0x6) + global_index_id
value: version, {materialized PropertiesCollector::IndexStats}

7). MAX_INDEX_ID
當前的index_id,每次創建索引index_id都從這個獲取和更新
key: Rdb_key_def::CURRENT_MAX_INDEX_ID(0x7)
value: version, current max index id

8). DDL_CREATE_INDEX_ONGOING
待創建的索引任務
key: Rdb_key_def::DDL_CREATE_INDEX_ONGOING(0x8) + global_index_id
value: version

DDL流程
    RocksDB引擎並沒有類似InnoDB引擎的增量row_log機制,因此MyRocks還不支持Online DDL,只是對部分DDL操作支持了inplace方式,從check_if_supported_inplace_alter介面實現可知,對於DROP_INDEX, DROP_UNIQUE_INDEX和ADD_INDEX這三個操作,可以通過inplace的方式完成DDL,inplace方式的優點就是不需要拷貝表,間接減少了鎖表時間,其它操作都只能通過重建表的方式來實現。下麵我以inplace的方式說明DDL的執行流程,copy方式相對會更簡單一些。總的入口函數是mysql_inplace_alter_table,主要包含4個階段。
1).檢查存儲引擎是否支持inplace的DDL操作
介面:ha_rocksdb::check_if_supported_inplace_alter
MyRocks支持inplace方式操作類型是HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE,意味著執行DDL過程中會堵塞寫。
2).準備階段
介面:ha_rocksdb::prepare_inplace_alter_table
對於 RocksDB引擎來說,支持inplace方式主要是添加和刪除索引,因此這個過程主要做的事情是搜集需要添加、刪除的索引信息。涉及到數據字典操作具體入口函數是create_key_defs,最終調用create_key_def介面,每個索引對應一個Rdb_key_def對象。這裡涉及到一個主要操作是為索引產生全局有序的index_id(ddl_manager.get_and_update_next_number)。

3).執行階段
介面:ha_rocksdb::inplace_alter_table
這裡主要是添加二級索引操作,具體實現在inplace_populate_sk介面。主要包括兩部分內容,更新數據字典和創建索引。
a.更新數據字典
數據字典維護通過最終通過介面start_ongoing_index_operation完成,為新建索引構造KV對,寫入system column family。
,所有添加的索引的KV對會作為一個事務commit,表示一批待創建索引的任務。

begin
put-KV:(DDL_CREATE_INDEX_ONGOING,cf_id,index_id)->(DDL_CREATE_INDEX_ONGOING_VERSION)
commit

b.創建索引
接下來就是真正創建索引的操作,通過遍歷PK索引,構造出新增二級索引的格式記錄,然後寫入索引,主要實現介面在update_sk里。由於RockDB行鎖實現中,每個key對應一把鎖,並且鎖對象不能復用,因此鎖消耗的總記憶體與key大小和key數量相關,為了保證系統運行中記憶體可控,一般開啟rocksdb_commit_in_the_middle避免大事務。因此這個這個過程也會觸發是否提前提交事務的檢查,主要實現介面在do_bulk_commit裡面。

4).提交或回滾階段
介面:commit_inplace_alter_table
a.處理待刪除的索引,最終通過介面start_ongoing_index_operation(drop)完成。
b.對於新增索引,寫入索引字典信息
c.寫入表和索引的映射關係
對錶進行alter操作後,會增一些索引,並刪除一些索引,因此表對應的索引關係需要重建,主要實現介面在Rdb_tbl_def::put_dict裡面。
第1),2),3)涉及的字典操作整個作為一個事務提交。

begin
put-KV: (DDL_DROP_INDEX_ONGOING,cf_id,index_id)->(DDL_DROP_INDEX_ONGOING_VERSION)
put-KV: (INDEX_INFO+cf_id+index_id)->INDEX_INFO_VERSION_VERIFY_KV_FORMAT+index_type+kv_version
put-KV: (DDL_ENTRY_INDEX_START_NUMBER,dbname_tablename)->version + {key_entry, key_entry, key_entry, ... } ,key_entry --> (cf_id, index_nr)
commit

d.維護數據字典在記憶體中對象m_ddl_hash。
主要工作是從hash表中摘掉老的tbl對象,寫入新的tbl對象,主要實現介面在Rdb_ddl_manager::put裡面。

e.清理DDL_CREATE_INDEX_ONGOING標記。
正常執行到這裡,表示新建的索引已經成功執行,需要清理DDL_CREATE_INDEX_ONGOING標記。主要實現介面在finish_indexes_operation裡面,最終調用end_ongoing_index_operation將之前加入的KV對進行刪除動作。
(DDL_CREATE_INDEX_ONGOING,cf_id,index_id)->(DDL_CREATE_INDEX_ONGOING_VERSION),並將整個操作作為一個事務commit。我們可以看到,整個過程已經執行完畢,但並沒有看到哪裡將刪除的索引真正清理掉,RocksDB裡面刪除索引實質是一個非同步的過程,真正刪除索引的動作通過後臺線程Rdb_drop_index_thread完成。所以,到這裡會主動觸發一次喚醒rdb_drop_idx_thread的動作,告知線程有活幹了。

Rdb_drop_index_thread工作流程
1).獲取待刪除索引列表key=(DDL_DROP_INDEX_ONGOING)
2).逐一遍歷每個需要刪除的索引,按照(index_id,index_id+1)key範圍來刪除記錄
3).並調用CompactRange觸發合併
4).通過index_id來查找key,若不存在index-id相同的key,則認為index已經被清理
5).最後調用finish_indexes_operation(DDL_DROP_INDEX_ONGOING)清理待刪除索引標記,並將索引字典信息從數據字典中刪除,具體實現參考delete_index_info。

begin
delete-key: (DDL_DROP_INDEX_ONGOING,cf_id,index_id)
delete-key: (INDEX_INFO+cf_id+index_id)
batch-commit

DDL異常處理
     從上述的實現來看,我們執行一個DDL操作,除了本身索引操作的事務,涉及數據字典的操作的事務也有好幾個,所以整個DDL操作並不是一個原子操作。比如在執行階段的第1步,字典相關的操作提交後,實例crash了,那麼這些字典操作內容就殘留在system Column family中了,但從業務角度來看,並不影響。上面介紹的mysql_inplace_alter_table包含了DDL的主要執行過程,實際上,在此之前還會通過mysql_prepare_alter_table創建臨時表定義frm文件,(文件名一般以#sql開頭),該文件包含了目標表的schema定義;併在DDL結束的時候,通過mysql_rename_table更新為目標表名.frm。如果在rename之前,實例crash了,就會導致frm文件的內容仍然是老版本,但RocksDB引擎字典已經更新。從表現形式來看,就會發現show create table xxx,顯示的索引內容與information_schema.ROCKSDB_DDL的數據字典不一致。前面討論的兩種情況都是inplace方式帶來的問題,對於copy方式,由於需要重建表,會將臨時表#sqlxxx的信息寫入數據字典,如果這個動作完成後,實例crash,會導致數據字典中殘留有臨時表的信息。mysqld重啟時,會根據字典的信息檢查表是否存在,主要通過介面validate_schemas實現,具體而言,通過數據字典中的表名查找對應的frm文件,並且查找過程中會忽略#開頭的臨時frm文件,因此會導致只要數據字典中包含了臨時表的字典信息,則會導致mysqld啟動失敗,並報如下錯誤。

error:
[Warning] RocksDB: Schema mismatch - Table test.#sql-b54_1 is registered in RocksDB but does not have a .frm file
[ERROR] RocksDB: Problems validating data dictionary against .frm files, exiting
[ERROR] RocksDB: Failed to initialize DDL manager.

如果想正常啟動,可以臨時通過參數rocksdb_validate_tables=2設置忽略這個錯誤,畢竟臨時表的數據字典不影響業務表的使用。從我這裡分析來看,目前DDL在異常處理這塊還處理的不夠好,根本原因還在於DDL不是一個原子操作,server層和引擎層的修改在某些情況下無法保持一致,導致問題出現。

相關實現文件和介面
storage/rocksdb/rdb_datadic.cc //數據字典相關代碼
storage/rocksdb/rdb_i_s.cc //information_schema相關代碼
myrocks::ha_rocksdb::inplace_populate_sk //更新二級索引
Rdb_dict_manager::get_max_index_id //獲取最大index_id
ha_rocksdb::check_if_supported_inplace_alter //檢查是否支持inplace
myrocks::ha_rocksdb::create //copy方式建表介面
myrocks::ha_rocksdb::create_key_def //建立key對象
myrocks::Rdb_ddl_manager::get_and_update_next_number //獲取下一個index_id
Rdb_dict_manager::start_ongoing_index_operation //添加一個建立/刪除索引的任務

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 簡介: 存儲過程(stored procedure)是一組為了完成特定功能的SQL語句集合,經編譯後存儲在伺服器端的資料庫中,利用存儲過程可以加速SQL語句的執行。 自定義存儲過程,由用戶創建並能完成某一特定功能的存儲過程,存儲過程既可以有參數又有返回值,但是它與函數不同,存儲過程的返回值只是指明執 ...
  • 往Oracle 中導入數據時,有一個列導入的數據應該時‘2017-04-17’ 的格式,結果導入的數據為 ‘2017/04/17’格式的,1000多條記錄要一條條改基本不可能。 於是想到了replace這個函數,具體用法如下: update 表1 t set t.列1=replace((select ...
  • 在ORACLE資料庫中,定義外鍵約束時,ORACLE是不會自動創建對應索引的,必須手動在外鍵約束相關的列上創建索引。那麼外鍵欄位上是否有必要創建索引呢?如果有必要的話,巡檢時,如何找出外鍵欄位上沒有創建索引的相關表,並生成對應的索引的腳本呢? 外鍵缺失索引影響 外鍵列上缺少索引會帶來三個問題,限制並... ...
  • 隨著需求的變化越來越快,線上修改表結構變得越來越需要。 在mysql5.6以前,mysql的修改表結構操作會鎖表,這樣就會造成開發人員或者DBA修改表結構必須要等到凌晨流量谷值或者停服修改。這樣必定會流失一部分用戶,在當下的互聯網需求里是不太能容忍的。 在mysql5.6之後,雖然mysql支持線上 ...
  • 在安裝資料庫的時候出現瞭如下錯誤: 解決辦法如下: 1.在bin目錄下 輸入:kill -s 9 9907 再輸入:ps -ef|grep mysql 顯示如下: 2.回到lampp目錄下,重啟資料庫,伺服器: 1. ./lampp restart ...
  • 有這麼一個需求:寫一條sql語句,功能是當B表中的欄位2值=X的時候,修改A表中的一個欄位1=Y,SQL語句如下: ...
  • 業務場景:公司電纜被挖斷,突然斷電導致的宕機。 [oracle@slave1 ~]$ sqlplus / as sysdba SQL> startup mount --載入到mount狀態 SQL> alter database open; [oracle@slave1 trace]$ cd /u0 ...
  • 創建資料庫 如果資料庫不存在則創建,否則切換到指定資料庫 查看當前資料庫名 查看所有資料庫 查看集合 刪除資料庫 刪除當前資料庫 刪除集合,collection為集合名,例 插入文檔 實例 <! more 我們也可以把數據定義為變數 插入文檔也可以使用 命令,如果不指定 欄位save()方法類似 於 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...