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.
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:
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:
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:
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;
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.
O principal fluxo de trabalho da fase de limpeza é mostrado na figura abaixo:
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:
-
Receba uma mensagem da fila fts_optimize_wq;
-
Determine o tipo de mensagem. Se for FTS_MSG_SYNC_TABLE, execute a liberação;
-
Liberar o conteúdo do cache para a tabela auxiliar no disco;
-
Limpe o cache e configure-o para seu estado inicial;
-
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