Análise do código-fonte do índice de texto completo do MySQL do processo de execução da instrução Insert

Este artigo foi compartilhado pela Huawei Cloud Community " Análise de código-fonte do índice de texto completo MySQL: processo de execução de instrução de inserção ", autor: banco de dados GaussDB.

0.PNG

1. Introdução ao contexto

A indexação de texto completo é um meio técnico comumente utilizado na área de recuperação de informações. É utilizada para problemas de pesquisa de texto completo, ou seja, busca de documentos que contenham a palavra com base na palavra. no navegador, o mecanismo de busca precisa encontrar todos os documentos relacionados e classificados por relevância.

A implementação subjacente do índice de texto completo é baseada no índice invertido. O chamado índice invertido descreve a relação de mapeamento entre palavras e documentos, expressa na forma de (palavra, (o documento onde a palavra está localizada, o deslocamento da palavra no documento)). o índice de texto completo é organizado:

mysql> CREATE TABLE linhas_de abertura (
           id INT UNSIGNED AUTO_INCREMENT NÃO NULO CHAVE PRIMÁRIA,
           linha_de abertura TEXTO(500),
           autor VARCHAR(200),
           título VARCHAR(200),
           TEXTO COMPLETO idx (opening_line)
           ) MOTOR=InnoDB;    
mysql> INSERT INTO opening_lines(opening_line,author,title) VALUES
           ('Me chame de Ismael.','Herman Melville','Moby-Dick'),
           ('Um grito atravessa o céu.','Thomas Pynchon','Gravity\'s Rainbow'),
           ('Eu sou um homem invisível.', 'Ralph Ellison', 'Homem Invisível'),
           ('Onde agora? Quem agora? Quando agora?', 'Samuel Beckett', 'O Inominável');      
mysql> SET GLOBAL innodb_ft_aux_table='test/opening_lines';
mysql> selecione * em information_schema.INNODB_FT_INDEX_TABLE;
 +-----------+--------------+------------+-------- ---+--------+----------+  
| PALAVRA | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSIÇÃO |  
+-----------+--------------+------------+-------- ---+--------+----------+  
| através | 4 | 4 | 1 | 4 | 18 |  
| ligar | 3 | 3 | 1 | 3 | 0 |  
| vem | 4 | 4 | 1 | 4 | 12 |  
| invisível | 5 | 5 | 1 | 5 | 8 |  
| ismael | 3 | 3 | 1 | 3 | 8 |  
| homem | 5 | 5 | 1 | 5 | 18 |  
| agora | 6 | 6 | 1 | 6 | 6 |  
| agora | 6 | 6 | 1 | 6 | 9 |  
| agora | 6 | 6 | 1 | 6 | 10 |  
| gritando | 4 | 4 | 1 | 4 | 2 |  
| céu | 4 | 4 | 1 | 4 | 29 |  
+-----------+--------------+------------+-------- ---+--------+----------+

Como acima, uma tabela é criada e um índice de texto completo é estabelecido na coluna opening_line. Tomemos como exemplo a inserção de 'Chame-me Ismael'. 'Chame-me Ismael.' , 'ishmael ', porque 'me' é menor que o comprimento mínimo de palavra definido em ft_min_word_len(4) e é descartado no final, apenas 'call' e 'ishmael' serão registrados no índice de texto completo, onde o índice de texto completo. a posição inicial de 'call' é o 0º caractere no documento, o deslocamento é 0, a posição inicial de 'ishmael' é o 12º caractere no documento e o deslocamento é 12.

Para uma introdução mais detalhada às funções do índice de texto completo, consulte o Manual de Referência do MySQL 8.0. Este artigo analisará brevemente o processo de execução da instrução Insert no nível do código-fonte.

2. Cache de índice de texto completo

O que está registrado na tabela de índice de texto completo é {palavra, {ID do documento, posição da ocorrência}}, ou seja, para inserir um documento, ele precisa ser segmentado em múltiplas {palavras, {ID do documento, posição da ocorrência}} tais uma estrutura, se toda vez Se o disco for descarregado imediatamente após a segmentação de palavras, o desempenho será muito ruim.

Para aliviar esse problema, o Innodb introduziu um cache de índice de texto completo, que funciona de forma semelhante ao Change Buffer. Cada vez que um documento é inserido, os resultados da segmentação de palavras são primeiro armazenados em cache e, em seguida, liberados para o disco em lotes quando o cache está cheio, evitando assim a limpeza frequente do disco. Innodb define a estrutura fts_cache_t para gerenciar o cache, conforme mostrado na figura a seguir:

1.png

Cada tabela mantém um cache e, para cada tabela para a qual um índice de texto completo é criado, um objeto fts_cache_t é criado na memória. Observe que fts_cache_t é um cache em nível de tabela. Se vários índices de texto completo forem criados para uma tabela, ainda haverá um objeto fts_cache_t correspondente na memória. Alguns membros importantes do fts_cache_t são os seguintes:

  • Optimize_lock, Deleted_lock, doc_id_lock: bloqueios mutex, relacionados a operações simultâneas.
  • Deleted_doc_ids: tipo de vetor, armazena doc_ids excluídos.
  • índices: tipo vetorial, cada elemento representa um índice de texto completo. Cada vez que um índice de texto completo é criado, um elemento será adicionado à matriz. Os resultados da segmentação de palavras de cada índice são armazenados em uma estrutura de árvore vermelha e preta. A chave é palavra e o valor é doc_id e o deslocamento da palavra.
  • total_size: Toda a memória alocada pelo cache, incluindo a memória utilizada por suas subestruturas.

3. Inserir processo de execução de instrução

Tomando o código-fonte do MySQL 8.0.22 como exemplo, a execução da instrução Insert é dividida principalmente em três estágios, ou seja, o estágio de escrita do registro de linha, o estágio de envio da transação e o estágio de limpeza suja.

3.1 Fase de registro de linha de escrita

O fluxo de trabalho principal para gravar registros de linha é mostrado na figura abaixo:

2.png

Conforme mostrado na figura acima, o mais importante neste estágio é gerar o doc_id, gravá-lo no registro de linha do Innodb e armazenar em cache o doc_id para que o conteúdo do texto possa ser obtido com base no doc_id durante a fase de envio da transação. pilha de chamadas de função é a seguinte:

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 e fts_trx_table_add_op são duas funções importantes para obter doc_id os registros de linha do Innodb contêm algumas colunas ocultas, como row_id, trx_id, etc. records , esse valor é obtido em fts_get_next_doc_id, como segue:

E fts_trx_add_op adiciona a operação de índice de texto completo ao trx e será processado posteriormente quando a transação for confirmada.

3.2 Fase de envio da transação

O principal fluxo de trabalho da fase de envio da transação é mostrado na figura abaixo:

3.png

Esta etapa é a etapa mais importante em toda a inserção FTS do documento, a obtenção de {palavra, {ID do documento, posição da ocorrência}} e a inserção no cache são concluídas nesta fase. Sua pilha de chamadas de função é a seguinte:

fts_commit_table
      ->fts_add
          ->fts_add_doc_by_id
              ->fts_cache_add_doc
                    // Obtém o documento com base em doc_id e segmenta o documento em palavras
                  ->fts_fetch_doc_from_rec
                    //Adiciona os resultados da segmentação de palavras ao cache
                  ->fts_cache_add_doc
              ->fts_optimize_request_sync_table
                    //Cria mensagem FTS_MSG_SYNC_TABLE para notificar o thread sujo para limpar o thread sujo
                  ->fts_optimize_create_msg(FTS_MSG_SYNC_TABLE)

Entre eles, fts_add_doc_by_id é uma função chave. Esta função realiza principalmente o seguinte:

1) Encontre o registro da linha com base em doc_id e obtenha o documento correspondente;

2) Realize a segmentação de palavras no documento, obtenha o par associado {palavra, (o documento onde a palavra está localizada, o deslocamento da palavra no documento)} e adicione-o ao cache
3) Determine se cache-> ; total_size atinge o limite, se atingir o limite, adicione uma mensagem FTS_MSG_SYNC_TABLE à fila de mensagens do thread sujo para notificar o thread para liberar (fts_optimize_create_msg).

Para facilitar o entendimento, omiti a parte do código de tratamento de exceções e algumas partes comuns de localização de registros, e fiz breves comentários:

ulint estático fts_add_doc_by_id(fts_trx_table_t *ftt, doc_id_t doc_id)
    {
            /* 1. Pesquisa registros no índice fts_doc_id_index com base em docid*/
          /* btr_cur_search_to_nth_level, btr_cur_search_to_nth_level será chamado na função btr_pcur_open_with_no_init
            O processo de registro de pesquisa de árvore b+ será executado primeiro, o nó folha onde o registro docid está localizado é encontrado no nó raiz e, em seguida, o registro docid é encontrado por meio de pesquisa binária. */
        btr_pcur_open_with_no_init(fts_id_index, tupla, PAGE_CUR_LE,
                                    BTR_SEARCH_LEAF, &pcur, 0, &mtr);
        if (btr_pcur_get_low_match(&pcur) == 1) { /* Se o registro docid for encontrado*/
            if (is_id_cluster) {
                 /** 1.1 Se fts_doc_id_index for um índice clusterizado, significa que os dados do registro de linha foram encontrados e o registro de linha é salvo diretamente**/
                doc_pcur = &pcur;
              } outro {
                /** 1.2 Se fts_doc_id_index for um índice secundário, você precisará pesquisar mais o registro de linha no índice clusterizado com base no ID da chave primária encontrado em 1.1 e salvar o registro de linha após encontrá-lo **/ btr_pcur_open_with_no_init(clust_index, clust_ref, PAGE_CUR_LE,
                                           BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr);
               doc_pcur = &clust_pcur;
             } // Percorrer cache->get_docs
            for (ulint i = 0; i < num_idx; ++i) {
                /***** 2. Execute a segmentação de palavras no documento, obtenha o par associado {palavra, (o documento onde a palavra está localizada, o deslocamento da palavra no documento)} e adicione-o ao cache** ***/
                documento fts_doc_t;
                fts_doc_init(&doc);
        /** 2.1 Obtenha o documento de conteúdo da coluna correspondente do índice de texto completo no registro de linha de acordo com o doc_id e analise o documento, principalmente para construir os tokens da estrutura fts_doc_t Os tokens são uma árvore vermelho-preta. estrutura Cada elemento é uma {palavra, [a estrutura da posição onde a palavra aparece no documento]}, e os resultados da análise são armazenados no documento**/.
                fts_fetch_doc_from_rec(ftt->fts_trx->trx, get_doc, clust_index,doc_pcur, deslocamentos, &doc);
                /** 2.2 Adicione a {palavra, [a posição onde a palavra aparece no documento]} obtida no passo 2.1 ao index_cache**/
                fts_cache_add_doc(tabela->fts->cache, get_doc->index_cache, doc_id, doc.tokens);
               /***** 3. Determine se cache->total_size atinge o limite. Se o limite for atingido, adicione uma mensagem FTS_MSG_SYNC_TABLE à fila de mensagens do thread sujo para notificar o thread para limpar *****/
                bool need_sync = falso;
                if ((cache->total_size - cache->total_size_before_sync >
                     fts_max_cache_size/10 || fts_need_sync) &&!cache->sync->in_progress) {
                  /** 3.1 Determine se o limite foi atingido**/
                  need_sync = verdadeiro;
                  cache->total_size_before_sync = cache->total_size;
                }
                    if (precisa de sincronização) {
                    /** 3.2 Empacote a mensagem FTS_MSG_SYNC_TABLE e monte-a na fila fts_optimize_wq e notifique o thread fts_optimize_thread para limpá-la. O conteúdo da mensagem é id da tabela **/ fts_optimize_request_sync_table(table);
                }
            }
        }
    }

Depois de compreender o processo acima, você pode explicar o fenômeno especial de envio de transações de índice de texto completo descrito no site oficial. Consulte a seção Manipulação de transações de índice de texto completo do InnoDB do Manual de referência do MySQL 8.0. tabela de índice de texto completo, se a transação atual não for confirmada, não poderemos encontrar o registro de linha inserido por meio do índice de texto completo na transação atual. A razão é que a atualização do índice de texto completo é concluída quando a transação é confirmada. Quando a transação não é confirmada, fts_add_doc_by_id ainda não foi executado, o registro não pode ser encontrado por meio do índice de texto completo. No entanto, na Seção 3.1, podemos saber que o registro de linha do Innodb foi inserido neste momento. Se você consultar o índice de texto completo, poderá encontrar o registro executando diretamente SELECT COUNT(*) FROM opening_lines.

3.3 Etapa de escovação

O principal fluxo de trabalho da fase de limpeza é mostrado na figura abaixo:

4.png

Quando o InnoDB inicia, um thread de segundo plano será criado, a função do thread é fts_optimize_thread e a fila de trabalho é fts_optimize_wq. Na fase de envio da transação da Seção 3.2, quando o cache estiver cheio, a função fts_optimize_request_sync_table adicionará uma mensagem FTS_MSG_SYNC_TABLE à fila fts_optimize_wq. O thread de segundo plano removerá a mensagem e liberará o cache para o disco. Sua pilha de chamadas de função é a seguinte:

 

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

As principais operações realizadas por este thread são as seguintes:

  1. Receba uma mensagem da fila fts_optimize_wq;
  2. Determine o tipo de mensagem. Se for FTS_MSG_SYNC_TABLE, execute a liberação;
  3. Liberar o conteúdo do cache para a tabela auxiliar no disco;
  4. Limpe o cache e configure-o para seu estado inicial;
  5. Volte ao passo 1 e receba a próxima mensagem;

Na Seção 3.2, quando a transação for enviada, se o total_size do cache fts for maior que o limite de tamanho de memória definido, um FTS_MSG_SYNC_TABLE será escrito e inserido na fila fts_optimize_wq. O thread sujo processará a mensagem e liberará os dados. o cache fts para o disco e, em seguida, limpe o cache.

Vale ressaltar que quando o total_size do cache fts for maior que o limite de tamanho de memória definido, apenas uma mensagem será gravada na fila fts_optimize_wq. Neste momento, o cache fts ainda pode gravar dados e memória antes de ser processado pelo. thread de liberação em segundo plano continuará a aumentar, o que também é a causa raiz do problema OOM que causa a inserção simultânea de índices de texto completo. A correção para o problema é o patch Bug #32831765 SERVER HITS OOM CONDITION WHEN LOADING TWO INNODB . podem verificar por si mesmos. 

Link de verificação do OOM: https://bugs.mysql.com/bug.php?id=103523

Se o thread sujo ainda não tiver sujo o cache fts de uma determinada tabela, o processo MySQL irá travar e os dados no cache serão perdidos. Após a reinicialização, na primeira vez que insert ou select for executado na tabela, os dados no cache antes da falha serão restaurados na função fts_init_index. Neste momento, o synced_doc_id que foi descartado no disco será lido a partir da configuração. tabela, e osynced_doc_id na tabela será maior quesynced_doc_id Os registros são lidos e segmentados por palavra e restaurados no cache. Para implementação específica, consulte as funções fts_doc_fetch_by_doc_id e fts_init_recover_doc.

Clique para seguir e conhecer as novas tecnologias da Huawei Cloud o mais rápido possível~

 

Os recursos piratas de "Celebrating More Than Years 2" foram carregados no npm, fazendo com que o npmmirror tivesse que suspender o serviço unpkg da Microsoft na China fez as malas coletivamente e foi para os Estados Unidos, envolvendo centenas de pessoas . biblioteca de visualização front-end e o conhecido projeto de código aberto ECharts do Baidu - "indo para o mar" para apoiar os golpistas de peixes usaram o TeamViewer para transferir 3,98 milhões! O que os fornecedores de desktop remoto devem fazer? Zhou Hongyi: Não resta muito tempo para o Google. Recomenda-se que todos os produtos sejam de código aberto. Um ex-funcionário de uma conhecida empresa de código aberto deu a notícia: Após ser desafiado por seus subordinados, o líder técnico ficou furioso e. demitiu a funcionária grávida. O Google mostrou como executar o ChromeOS em uma máquina virtual Android. Por favor, me dê alguns conselhos: qual é o papel do time.sleep(6) aqui? A Microsoft responde aos rumores de que a equipe de IA da China está "fazendo as malas para os Estados Unidos" Comentários do People's Daily Online sobre a cobrança semelhante a matryoshka do software de escritório: Somente resolvendo ativamente "conjuntos" podemos ter um futuro
{{o.nome}}
{{m.nome}}

Acho que você gosta

Origin my.oschina.net/u/4526289/blog/11179935
Recomendado
Clasificación