MySQL全文索引源碼剖析之Insert語句執行過程

来源:https://www.cnblogs.com/huaweiyun/p/18201367
-Advertisement-
Play Games

本文分享自華為雲社區《MySQL全文索引源碼剖析之Insert語句執行過程》 ,作者:GaussDB 資料庫。 1. 背景介紹 全文索引是信息檢索領域的一種常用的技術手段,用於全文搜索問題,即根據單詞,搜索包含該單詞的文檔,比如在瀏覽器中輸入一個關鍵詞,搜索引擎需要找到所有相關的文檔,並且按相關性排 ...


本文分享自華為雲社區《MySQL全文索引源碼剖析之Insert語句執行過程》 ,作者:GaussDB 資料庫。

0.PNG

1. 背景介紹

全文索引是信息檢索領域的一種常用的技術手段,用於全文搜索問題,即根據單詞,搜索包含該單詞的文檔,比如在瀏覽器中輸入一個關鍵詞,搜索引擎需要找到所有相關的文檔,並且按相關性排好序。

全文索引的底層實現是基於倒排索引。所謂倒排索引,描述的是單詞和文檔的映射關係,表現形式為(單詞,(單詞所在的文檔,單詞在文檔中的偏移)),下文的示例將會展示全文索引的組織方式:

mysql> CREATE TABLE opening_lines (
           id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
           opening_line TEXT(500),
           author VARCHAR(200),
           title VARCHAR(200),
           FULLTEXT idx (opening_line)
           ) ENGINE=InnoDB;    
mysql> INSERT INTO opening_lines(opening_line,author,title) VALUES
           ('Call me Ishmael.','Herman Melville','Moby-Dick'),
           ('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'), 
           ('I am an invisible man.','Ralph Ellison','Invisible Man'),
           ('Where now? Who now? When now?','Samuel Beckett','The Unnamable');      
mysql> SET GLOBAL innodb_ft_aux_table='test/opening_lines';
mysql> select * from information_schema.INNODB_FT_INDEX_TABLE; 
 +-----------+--------------+-------------+-----------+--------+----------+  
| WORD      | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |  
+-----------+--------------+-------------+-----------+--------+----------+  
| across    |            4 |           4 |         1 |      4 |       18 |  
| call      |            3 |           3 |         1 |      3 |        0 |  
| comes     |            4 |           4 |         1 |      4 |       12 |  
| invisible |            5 |           5 |         1 |      5 |        8 |  
| ishmael   |            3 |           3 |         1 |      3 |        8 |  
| man       |            5 |           5 |         1 |      5 |       18 |  
| now       |            6 |           6 |         1 |      6 |        6 |  
| now       |            6 |           6 |         1 |      6 |        9 |  
| now       |            6 |           6 |         1 |      6 |       10 |  
| screaming |            4 |           4 |         1 |      4 |        2 |  
| sky       |            4 |           4 |         1 |      4 |       29 |  
+-----------+--------------+-------------+-----------+--------+----------+

如上,創建了一個表,併在opening_line列上建立了全文索引。以插入'Call me Ishmael.'為例,'Call me Ishmael.'也即文檔,其ID為3,在構建全文索引時,該文檔會被分成3個單詞'call', 'me', 'ishmael',由於'me'小於設定的ft_min_word_len(4)最小單詞長度被丟棄,最後全文索引中只會記錄'call'和'ishmael',其中'call'起始位置在文檔中的第0個字元處,偏移為0,'ishmael'起始位置在文檔中的第12個字元處,偏移即為12。

關於全文索引更詳細的功能介紹可以參考MySQL 8.0 Reference Manual,本文將從源碼層面,簡要剖析Insert語句的執行過程。

2. 全文索引Cache

全文索引表中記錄的是{單詞,{文檔ID,出現的位置}},即插入一個文檔需要將其分詞成多個{單詞,{文檔ID,出現的位置}}這樣的結構,如果每次分詞完就馬上刷磁碟,其性能會非常差。

為了緩解該問題,Innodb引入了全文索引cache,其作用與Change Buffer類似。每次插入一個文檔時,先將分詞結果緩存到cache,等到cache滿了再批量刷到磁碟,從而避免頻繁地刷盤。Innodb定義了fts_cache_t的結構來管理cache,如下圖所示:

1.png

每張表維護一個cache,對於每個創建了全文索引的表都會在記憶體中創建一個fts_cache_t的對象。註意,fts_cache_t是表級別的cache, 若一個表創建了多個全文索引,記憶體中依舊是對應一個fts_cache_t對象。fts_cache_t的一些重要成員如下:

  • optimize_lock、deleted_lock、doc_id_lock:互斥鎖,與併發操作相關。
  • deleted_doc_ids:vector類型,存儲已刪除的doc_id。
  • indexes:vector類型,每個元素表示一個全文索引,每次創建全文索引時,都會往該數組中添加一個元素,每個索引的分詞結果以紅黑樹結構存儲,key為word, value就是doc_id及單詞的偏移。
  • total_size:cache已分配的全部記憶體,包含其子結構使用的記憶體。

3. Insert語句執行過程

以MySQL 8.0.22源碼為例,Insert語句的執行主要分為三個階段,分別為寫入行記錄階段、事務提交階段和刷臟階段。

3.1 寫入行記錄階段

寫入行記錄的主要工作流如下圖所示:

2.png

如上圖所示,這一階段最主要是生成doc_id,並寫入到Innodb的行記錄中,並且將doc_id緩存,以供事務提交階段根據doc_id獲取文本內容,其函數調用棧如下:

  ha_innobase::write_row
        ->row_insert_for_mysql
            ->row_insert_for_mysql_using_ins_graph
                ->row_mysql_convert_row_to_innobase
                    ->fts_create_doc_id
                        ->fts_get_next_doc_id
                ->fts_trx_add_op
                    ->fts_trx_table_add_op

fts_get_next_doc_id與fts_trx_table_add_op是比較重要的兩個函數,fts_get_next_doc_id是為了獲取doc_id,innodb行記錄中包含了一些隱藏列,比如row_id、trx_id等,若創建了全文索引,其行記錄中也會增加一個隱藏欄位FTS_DOC_ID,這個值在fts_get_next_doc_id中獲取的,如下:

而fts_trx_add_op則是將對全文索引的操作添加到trx中,待事務提交時進一步處理。

3.2 事務提交階段

事務提交階段的主要工作流如下圖所示:

3.png

這一階段是整個FTS 插入的最重要的一步,對文檔進行分詞,獲取{單詞,{文檔ID,出現的位置}},插入到cache,這些都是在這一階段完成的。其函數調用棧如下:

fts_commit_table
      ->fts_add
          ->fts_add_doc_by_id
              ->fts_cache_add_doc
                    // 根據doc_id獲取文檔,對文檔分詞
                  ->fts_fetch_doc_from_rec
                    // 將分詞結果添加到cache中
                  ->fts_cache_add_doc
              ->fts_optimize_request_sync_table
                    // 創建FTS_MSG_SYNC_TABLE消息,通知刷臟線程刷臟
                  ->fts_optimize_create_msg(FTS_MSG_SYNC_TABLE)

其中,fts_add_doc_by_id是比較關鍵的一個函數,該函數主要完成了以下幾件事:

1)根據doc_id找到行記錄, 獲取對應的文檔;

2)對文檔執行分詞,獲取{單詞,(單詞所在的文檔,單詞在文檔中的偏移)}關聯對,並添加到cache中;
3)判斷cache->total_size是否達到閾值時,若達到閾值,則往刷臟線程的消息隊列添加一個FTS_MSG_SYNC_TABLE消息, 通知該線程刷臟(fts_optimize_create_msg),具體代碼如下:

為方便理解,我把代碼的異常處理部分以及一些查找記錄的通用部分省略了,並給出了簡要註釋:

   static ulint fts_add_doc_by_id(fts_trx_table_t *ftt, doc_id_t doc_id)
    {
            /* 1. 根據docid在fts_doc_id_index索引中的查找記錄 */
          /* btr_pcur_open_with_no_init函數中會調用btr_cur_search_to_nth_level,btr_cur_search_to_nth_level
            會執行b+樹搜索記錄的過程,先從根節點找到docid記錄所在的葉子節點,再通過二分查找找到docid記錄。*/
        btr_pcur_open_with_no_init(fts_id_index, tuple, PAGE_CUR_LE,
                                    BTR_SEARCH_LEAF, &pcur, 0, &mtr);
        if (btr_pcur_get_low_match(&pcur) == 1) { /* 如果找到了docid記錄 */
            if (is_id_cluster) {
                 /** 1.1 如果fts_doc_id_index是聚集索引,則意味著已經找到行記錄數據, 直接保存行記錄 **/
                doc_pcur = &pcur;
              } else {
                /** 1.2 如果fts_doc_id_index是輔助索引,則需要根據1.1找到的主鍵id在聚集索引上進一步搜 索行記錄,找到後保存行記錄**/                btr_pcur_open_with_no_init(clust_index, clust_ref, PAGE_CUR_LE,
                                           BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr); 
               doc_pcur = &clust_pcur;
             }        // 遍歷cache->get_docs
            for (ulint i = 0; i < num_idx; ++i) {
                /***** 2. 對文檔執行分詞,獲取{單詞,(單詞所在的文檔,單詞在文檔中的偏移)}關聯對,並添加到cache中 *****/
                fts_doc_t doc;
                fts_doc_init(&doc);
        /** 2.1 根據doc_id獲取行記錄中該全文索引對應列的內容文檔,解析文檔,主要是為了構建               fts_doc_t結構體的tokens,tokens為一個紅黑樹結構,每個元素是一個               {單詞,[該單詞在文檔中出現的位置]}的結構,解析結果存於doc中 **/
                fts_fetch_doc_from_rec(ftt->fts_trx->trx, get_doc, clust_index,doc_pcur, offsets, &doc);
                /** 2.2 將2.1步驟獲得的{單詞,[該單詞在文檔中出現的位置]}添加到index_cache中 **/
                fts_cache_add_doc(table->fts->cache, get_doc->index_cache, doc_id, doc.tokens);
               /***** 3. 判斷cache->total_size是否達到閾值時。  若達到閾值,則往刷臟線程的消息隊列添加一個FTS_MSG_SYNC_TABLE消息, 通知該線程刷臟 *****/
                bool need_sync = false;
                if ((cache->total_size - cache->total_size_before_sync >
                     fts_max_cache_size / 10 || fts_need_sync) &&!cache->sync->in_progress) {
                  /** 3.1 判斷是達到閾值 **/
                  need_sync = true;
                  cache->total_size_before_sync = cache->total_size;
                }
                    if (need_sync) {
                    /** 3.2 打包FTS_MSG_SYNC_TABLE消息掛載至fts_optimize_wq隊列,                   通知fts_optimize_thread線程刷臟,消息的內容為table id **/                  fts_optimize_request_sync_table(table);
                }
            }
        }
    }  

瞭解了上述過程,就可以解釋官網所述的全文索引事務提交的特殊現象了,參考MySQL 8.0 Reference Manual 的InnoDB Full-Text Index Transaction Handling一節,若對全文索引表插入一些行記錄,如果當前事務未提交,我們在當前事務中通過全文索引是查不到已插入的行記錄。其原因在於,全文索引的更新是在事務提交的時完成的,事務未提交時,fts_add_doc_by_id尚未執行,因此,不能通過全文索引查找該記錄。但是,通過3.1小節可以知道,此時Innodb的行記錄是已經插入了的,如果通過全文索引查詢,直接執行SELECT COUNT(*) FROM opening_lines是可以查到該記錄的。

3.3 刷臟階段

刷臟階段的主要工作流如下圖所示:

4.png

InnoDB啟動時,會創建一個後臺線程,線程函數為fts_optimize_thread,工作隊列為fts_optimize_wq。3.2節事務提交階段,當cache滿時fts_optimize_request_sync_table函數會往fts_optimize_wq隊列添加一個FTS_MSG_SYNC_TABLE消息,後臺線程取下該消息後將cache刷新到磁碟。其函數調用棧如下:

  fts_optimize_thread
        ->ib_wqueue_timedwait
            ->fts_optimize_sync_table
                ->fts_sync_table
                    ->fts_sync
                        ->fts_sync_commit
                            ->fts_cache_clear

該線程主要執行的操作如下:

  1. 從fts_optimize_wq隊列取一個消息;
  2. 判斷消息的類型,若為FTS_MSG_SYNC_TABLE,則執行刷臟;
  3. 將cache中的內容刷新到磁碟上的輔助表;
  4. 清空cache, 置cache為初始狀態;
  5. 返回至步驟1,取下一個消息;

在3.2節中,當事務提交時,若fts cache的total_size大於了設定的記憶體大小閾值,則會寫入一條FTS_MSG_SYNC_TABLE插入到fts_optimize_wq隊列,刷臟線程會處理該消息,將fts cache中的數據刷到磁碟,隨後清空cache。

值得一提的是,當fts cache的total_size大於設定的記憶體大小閾值時,只會寫條消息到fts_optimize_wq隊列,此時fts cache在未被後臺刷臟線程處理之前,依然可以寫入數據,記憶體會繼續增加,這也是導致了全文索引併發插入的OOM問題的根因,問題的修複patch Bug #32831765 SERVER HITS OOM CONDITION WHEN LOADING TWO INNODB,感興趣的讀者可以自行查閱。

OOM查閱鏈接:https://bugs.mysql.com/bug.php?id=103523

若刷臟線程還未對某個表的fts cache刷臟,此時MySQL進程crash了,cache中的數據丟失。重啟之後,第一次對該表執行insert或者select時,在fts_init_index函數中會對crash之前cache中的數據進行恢復,此時會從config表中讀取已落盤的synced_doc_id, 將表中大於synced_doc_id的記錄讀取並分詞恢復到cache中,具體實現參考fts_doc_fetch_by_doc_id, fts_init_recover_doc函數。

點擊關註,第一時間瞭解華為雲新鮮技術~

 


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

-Advertisement-
Play Games
更多相關文章
  • 目錄電腦系統的層次結構操作系統的定義操作系統的功能和目標作為系統資源的管理者向上層提供方便易用的服務作為最接近硬體的層次操作系統的四個特征併發共用虛擬非同步操作系統的發展與分類操作系統的運行機制中斷和異常中斷的作用中斷類型中斷機制的基本原理系統調用系統調用的分類系統調用過程操作系統的內核巨集內核和微內 ...
  • 一.問題:列舉Linux高級命令,至少6個(百度) netstat //網路狀態監控 top //系統運行狀態 lsblk //查看硬碟分區 find ps -aux //查看運行進程 chkconfig //查看服務啟動狀態 systemctl //管理系統伺服器 二.問題:Linux查看記憶體、i ...
  • docker-compose logs -f ##查看該容器的啟動的日誌列印(日誌從頭列印 docker logs -f container_id ##查看某一容器的啟動的日誌列印(日誌從頭列印) docker logs -f --tail(-t) 數量詞 container_id ##查看某一容器 ...
  • 基本概念 後臺啟動AsterixDB cd ~/asterixdb/asterixdb/asterix-server/target/asterix-server-0.9.10-SNAPSHOT-binary-assembly/apache-asterixdb-0.9.10-SNAPSHOT/opt/ ...
  • 在業務實現的過程中,時常會出現且或關係邏輯的拼接。邏輯運算的組合使用,是實現複雜業務規則和決策支持系統的關鍵技術。 目前袋鼠雲的指標管理平臺、客戶數據洞察平臺、數據資產平臺都有在使用。並且,且或組件已經在 RC 5.0 中添加到組件庫,企業現在可以更加靈活地構建和實施複雜的業務規則。 本文將從前期分 ...
  • 正常上下文在複製一個一模一樣的上下文 appsettings.json添加兩個資料庫連接字元串 Program.cs裡邊一樣添加兩個 控制台遷移命令 必須加上-Context 後邊跟的是我們上下文的名稱 Add-Migration MyMigration -Context MYDBContext22 ...
  • 引言 近期我們註意到很多學生朋友通過郵件嚮導師申請報名,請註意!!!​這是無效的,請必須通過“開源之夏”官方後臺申請報名,請仔細參考這篇【報名攻略】 所以,我們特此舉辦這次宣講會,目的是向所有感興趣的學生詳細介紹Apache DolphinScheduler社區在開源之夏中提供的項目,並且解答學生朋 ...
  • Percona Toolkit 神器全攻略 Percona Toolkit 神器全攻略系列共八篇分為 文章名 文章名 Percona Toolkit 神器全攻略 Percona Toolkit 神器全攻略(實用類) Percona Toolkit 神器全攻略(配置類) Percona Toolkit ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...