Insert 문 실행 프로세스의 MySQL 전체 텍스트 인덱스 소스 코드 분석

이 기사는 Huawei Cloud Community " MySQL 전체 텍스트 인덱스 소스 코드 분석: 삽입 명령문 실행 프로세스 ", 작성자: GaussDB 데이터베이스 에서 공유되었습니다 .

0.PNG

1. 배경 소개

전체 텍스트 인덱싱은 정보 검색 분야에서 일반적으로 사용되는 기술적 수단입니다. 전체 텍스트 검색 문제, 즉 단어를 기준으로 해당 단어가 포함된 문서를 검색하는 경우에 사용됩니다. 브라우저에서는 검색 엔진이 관련 문서를 모두 찾아서 관련성에 따라 정렬해야 합니다.

전체 텍스트 인덱스의 기본 구현은 반전된 인덱스를 기반으로 합니다. 소위 역색인은 단어와 문서 간의 매핑 관계를 설명하며, (단어, (단어가 위치한 문서, 문서 내 단어의 오프셋)) 형식으로 표현됩니다. 전체 텍스트 색인은 다음과 같이 구성됩니다.

mysql> CREATE TABLE opening_lines (
           ID INT UNSIGNED AUTO_INCREMENT NOT NULL 기본 키,
           opening_line TEXT(500),
           작성자 VARCHAR(200),
           제목 VARCHAR(200),
           전체 텍스트 idx(opening_line)
           ) 엔진=InnoDB;    
mysql> opening_lines(opening_line,author,title) VALUES에 삽입
           ('이스마엘이라고 불러주세요.','허먼 멜빌','모비딕'),
           ('비명소리가 하늘을 가로질러 온다.','토마스 핀촌','중력의 무지개'),
           ('나는 투명인간이다.','랠프 엘리슨','투명인간'),
           ('지금은 어디? 지금은 누구? 지금은 언제?', '사무엘 베켓', '이름 붙일 수 없는');      
mysql> SET GLOBAL innodb_ft_aux_table='test/opening_lines';
mysql> information_schema.INNODB_FT_INDEX_TABLE에서 *를 선택합니다.
 +------------+---------------+-------------+------- ---+---------+----------+  
| 단어 | 첫 번째_DOC_ID | 마지막_DOC_ID | DOC_COUNT | DOC_ID | 위치 |  
+------------+---------------+-------------+------- ---+---------+----------+  
| 건너편 | 4 | 4 | 1 | 4 | 18 |  
| 전화 | 3 | 3 | 1 | 3 | 0 |  
| 온다 | 4 | 4 | 1 | 4 | 12 |  
| 보이지 않는 | 5 | 5 | 1 | 5 | 8 |  
| 이스마엘 | 3 | 3 | 1 | 3 | 8 |  
| 남자 | 5 | 5 | 1 | 5 | 18 |  
| 지금 | 6 | 6 | 1 | 6 | 6 |  
| 지금 | 6 | 6 | 1 | 6 | 9 |  
| 지금 | 6 | 6 | 1 | 6 | 10 |  
| 비명 | 4 | 4 | 1 | 4 | 2 |  
| 하늘 | 4 | 4 | 1 | 4 | 29 |  
+------------+---------------+-------------+------- ---+---------+----------+

위와 같이 테이블이 생성되고 opening_line 컬럼에 전체 텍스트 인덱스가 생성된다. 'Call me Ishmael'을 예로 들어 보겠습니다. 'Call me Ishmael'은 ID가 3인 문서입니다. 전체 텍스트 인덱스를 만들 때 문서는 'call', 'me' 3개 단어로 구분됩니다. , 'ishmael', 'me'는 ft_min_word_len(4)에 설정된 최소 단어 길이보다 작아서 폐기되기 때문에 결국 'call'과 'ishmael'만 전체 텍스트 인덱스에 기록됩니다. 'call'의 시작 위치는 문서의 0번째 문자이고, 오프셋은 0이며, 'ishmael'의 시작 위치는 문서의 12번째 문자이고, 오프셋은 12입니다.

전체 텍스트 인덱스의 기능에 대한 보다 자세한 소개는 MySQL 8.0 Reference Manual을 참조하시기 바랍니다. 이 글에서는 Insert 문의 실행 과정을 소스 코드 수준에서 간략하게 분석하겠습니다.

2. 전체 텍스트 인덱스 캐시

전체 텍스트 인덱스 테이블에 기록되는 내용은 {단어, {문서 ID, 발생 위치}}인데, 즉 문서를 삽입하려면 여러 개의 {단어, {문서 ID, 발생 위치}}로 분할해야 합니다. 매번 단어 분할 후 즉시 디스크를 플러시하면 성능이 매우 저하되는 구조입니다.

이 문제를 완화하기 위해 Innodb는 Change Buffer와 유사한 기능을 하는 전체 텍스트 인덱스 캐시를 도입했습니다. 문서가 삽입될 때마다 단어 분할 결과가 먼저 캐시에 캐시된 후 캐시가 가득 차면 일괄적으로 디스크에 플러시되므로 빈번한 디스크 플러시를 방지할 수 있습니다. Innodb는 다음 그림과 같이 캐시를 관리하기 위해 fts_cache_t 구조를 정의합니다.

1.png

각 테이블은 캐시를 유지 관리하고 전체 텍스트 인덱스가 생성된 각 테이블에 대해 fts_cache_t 개체가 메모리에 생성됩니다. fts_cache_t는 테이블 수준 캐시입니다. 테이블에 대해 여러 개의 전체 텍스트 인덱스가 생성되면 해당 fts_cache_t 개체가 메모리에 계속 남아 있습니다. fts_cache_t의 일부 중요한 멤버는 다음과 같습니다:

  • 최적화_잠금, 삭제_잠금, doc_id_lock: 동시 작업과 관련된 뮤텍스 잠금입니다.
  • 삭제된_doc_ids: 벡터 유형, 삭제된 doc_id를 저장합니다.
  • indexes: 벡터 유형, 각 요소는 전체 텍스트 인덱스를 나타냅니다. 전체 텍스트 인덱스가 생성될 때마다 요소가 배열에 추가됩니다. 각 인덱스의 단어 분할 결과는 빨간색-검은색 트리 구조에 저장됩니다. 키는 단어이고 값은 doc_id와 단어의 오프셋입니다.
  • total_size: 하위 구조에서 사용하는 메모리를 포함하여 캐시에 의해 할당된 모든 메모리입니다.

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도 행에 추가됩니다. records. 에서 이 값은 다음과 같이 fts_get_next_doc_id에서 가져옵니다.

그리고 fts_trx_add_op는 trx에 전체 텍스트 인덱스 작업을 추가하고 트랜잭션이 커밋될 때 추가로 처리됩니다.

3.2 거래 제출 단계

거래 제출 단계의 주요 작업 흐름은 아래 그림에 나와 있습니다.

3.png

이 단계는 전체 FTS 삽입 중 가장 중요한 단계로, 문서의 단어 분할, {단어, {문서 ID, 발생 위치}} 획득 및 캐시 삽입이 모두 완료되는 단계입니다. 해당 함수 호출 스택은 다음과 같습니다.

fts_commit_table
      ->fts_add
          ->fts_add_doc_by_id
              ->fts_cache_add_doc
                    // doc_id를 기반으로 문서를 가져오고 문서를 단어로 분할합니다.
                  ->fts_fetch_doc_from_rec
                    // 단어 분할 결과를 캐시에 추가합니다.
                  ->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) 문서에 대해 단어 분할을 수행하고, {단어(단어가 위치한 문서, 문서 내 단어의 오프셋)} 관련 쌍을 얻어 캐시에 추가합니다.
3) 캐시 여부를 결정합니다. total_size가 임계값에 도달하고 임계값에 도달하면 더티 스레드의 메시지 큐에 FTS_MSG_SYNC_TABLE 메시지를 추가하여 스레드에 플러시를 알립니다(fts_optimize_create_msg).

이해를 돕기 위해 코드의 예외 처리 부분과 레코드 찾기의 일부 공통 부분을 생략하고 간략한 설명을 제공했습니다.

정적 ulint fts_add_doc_by_id(fts_trx_table_t *ftt, doc_id_t doc_id)
    {
            /* 1. docid를 기준으로 fts_doc_id_index 인덱스에서 레코드 검색*/
          /* btr_cur_search_to_nth_level, btr_cur_search_to_nth_level은 btr_pcur_open_with_no_init 함수에서 호출됩니다.
            b+ 트리 검색 레코드 프로세스는 먼저 루트 노드에서 docid 레코드가 위치한 리프 노드를 찾은 다음 이진 검색을 통해 docid 레코드를 찾는 과정을 수행합니다. */
        btr_pcur_open_with_no_init(fts_id_index, 튜플, 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;
              } 또 다른 {
                /** 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;
             } // 캐시 탐색->get_docs
            for (ulint i = 0; i < num_idx; ++i) {
                /***** 2. 문서에서 단어 분할을 수행하고, {단어, (단어가 위치한 문서, 문서에서 단어의 오프셋)} 관련 쌍을 얻어 캐시에 추가합니다** ***/
                fts_doc_t 문서;
                fts_doc_init(&doc);
        /** 2.1 doc_id에 따라 행 레코드의 전체 텍스트 인덱스에 해당하는 열의 콘텐츠 문서를 얻고 문서를 구문 분석합니다. 주로 fts_doc_t 구조의 토큰을 구축합니다. 토큰은 레드-블랙 트리입니다. 각 요소는 {단어, [문서에서 해당 단어가 나타나는 위치의 구조]}이며, 파싱 결과는 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. 캐시->total_size가 임계값에 도달했는지 확인합니다. 임계값에 도달하면 더티 스레드의 메시지 큐에 FTS_MSG_SYNC_TABLE 메시지를 추가하여 스레드에 정리를 알립니다 *****/
                bool need_sync = false;
                if ((cache->total_size - 캐시->total_size_before_sync >
                     fts_max_cache_size / 10 || fts_need_sync) &&!cache->sync->in_progress) {
                  /** 3.1 임계값에 도달했는지 확인**/
                  need_sync = 사실;
                  캐시->total_size_before_sync = 캐시->total_size;
                }
                    if (need_sync) {
                    /** 3.2 FTS_MSG_SYNC_TABLE 메시지를 패키징하여 fts_optimize_wq 대기열에 마운트하고 fts_optimize_thread 스레드에 이를 알리어 더티를 정리합니다. **/ fts_optimize_request_sync_table(table);
                }
            }
        }
    }

위의 과정을 이해한 후, 공식 홈페이지에 설명된 전체 텍스트 인덱스 트랜잭션 제출의 특별한 현상을 설명할 수 있습니다. MySQL 8.0 참조 설명서의 InnoDB 전체 텍스트 인덱스 트랜잭션 처리 섹션을 참조하세요. 전체 텍스트 인덱스 테이블에서 현재 트랜잭션이 커밋되지 않은 경우 현재 트랜잭션의 전체 텍스트 인덱스를 통해 삽입된 행 레코드를 찾을 수 없습니다. 그 이유는 트랜잭션이 커밋되면 전체 텍스트 인덱스의 업데이트가 완료되기 때문입니다. 트랜잭션이 커밋되지 않은 경우에는 fts_add_doc_by_id가 아직 실행되지 않았으므로 전체 텍스트 인덱스를 통해 해당 레코드를 찾을 수 없습니다. 그런데 3.1절을 보면 이때 Innodb row 레코드가 삽입된 것을 알 수 있다. full-text index를 통해 쿼리하면 SELECT COUNT(*) FROM opening_lines를 직접 실행하여 해당 레코드를 찾을 수 있다.

3.3 브러싱 단계

청소 단계의 주요 작업 흐름은 아래 그림에 나와 있습니다.

4.png

InnoDB가 시작되면 백그라운드 스레드가 생성되고 스레드 함수는 fts_optimize_thread 이며 작업 대기열은 fts_optimize_wq입니다. 섹션 3.2의 트랜잭션 제출 단계에서 캐시가 가득 차면 fts_optimize_request_sync_table 함수는 FTS_MSG_SYNC_TABLE 메시지를 fts_optimize_wq 대기열에 추가합니다. 백그라운드 스레드는 메시지를 제거하고 캐시를 디스크에 플러시합니다. 해당 함수 호출 스택은 다음과 같습니다.

 

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. 캐시의 내용을 디스크의 보조 테이블로 플러시합니다.
  4. 캐시를 지우고 캐시를 초기 상태로 설정합니다.
  5. 1단계로 돌아가서 다음 메시지를 받습니다.

섹션 3.2에서 트랜잭션이 제출될 때 fts 캐시의 total_size가 설정된 메모리 크기 임계값보다 큰 경우 FTS_MSG_SYNC_TABLE이 기록되어 fts_optimize_wq 대기열에 삽입됩니다. 더티 스레드는 메시지를 처리하고 데이터를 플러시합니다. fts 캐시를 디스크에 저장한 다음 캐시를 지웁니다.

fts 캐시의 total_size가 설정된 메모리 크기 임계값보다 크면 단 하나의 메시지만 fts_optimize_wq 대기열에 기록됩니다. 이때 fts 캐시는 데이터와 메모리를 처리하기 전에 계속 쓸 수 있습니다. 이는 전체 텍스트 인덱스의 동시 삽입을 유발하는 OOM 문제의 근본 원인이기도 합니다. 문제에 대한 수정 사항은 패치 버그 #32831765 SERVER HITS OOM CONDITION WHEN LOADING TWO INNODB 입니다 . 스스로 확인할 수 있어요. 

OOM 확인 링크: https://bugs.mysql.com/bug.php?id=103523

더티 스레드가 특정 테이블의 fts 캐시를 아직 더티화하지 않은 경우 MySQL 프로세스가 중단되고 캐시의 데이터가 손실됩니다. 다시 시작한 후 테이블에서 처음 insert 또는 select가 실행되면 충돌이 발생하기 전 캐시에 있던 데이터가 fts_init_index 함수에서 복원됩니다. 이때 디스크에 삭제된 synced_doc_id는 config에서 읽혀집니다. 테이블의 synced_doc_id는 synced_doc_id보다 큽니다. 레코드를 읽고 단어를 분할하여 캐시에 복원하려면 fts_doc_fetch_by_doc_id 및 fts_init_recover_doc 함수를 참조하세요.

화웨이 클라우드의 신기술에 대해 빨리 알아보고 팔로우하려면 클릭하세요~

 

"Celebrateing More Than Years 2"의 불법 복제 리소스가 npm에 업로드되어 npmmirror가 unpkg 서비스 를 중단해야 했습니다. Microsoft의 중국 AI 팀은 수백 명의 사람들을 모아 미국으로 떠났습니다. 프론트엔드 시각화 라이브러리와 Baidu의 유명한 오픈 소스 프로젝트 ECharts - Fish 사기꾼을 지원하기 위한 "going to the sea"는 TeamViewer를 사용하여 398만 개를 전송했습니다! 원격 데스크톱 공급업체는 무엇을 해야 합니까? Zhou Hongyi: Google은 시간이 얼마 남지 않았습니다. 모든 제품을 오픈소스로 만드는 것이 좋습니다. 한 유명 오픈소스 회사의 전직 직원이 소식을 전했습니다. 부하 직원의 도전을 받은 후 기술 리더는 분노했습니다. Google은 Android 가상 머신에서 ChromeOS를 실행하는 방법을 보여주었습니다. 여기서 time.sleep(6)은 어떤 역할을 합니까? 마이크로소프트, 중국 AI 팀이 "미국을 위해 준비 중"이라는 루머에 대응 사무용 소프트웨어의 마트료시카 같은 충전에 대한 인민일보 온라인 논평: "세트"를 적극적으로 해결해야만 미래를 가질 수 있다
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/4526289/blog/11179935