Este artículo es compartido por Huawei Cloud Community " Análisis del código fuente del índice de texto completo de MySQL: proceso de ejecución de declaraciones de inserción ", autor: base de datos GaussDB.
1. Introducción a los antecedentes
La indexación de texto completo es un medio técnico comúnmente utilizado en el campo de la recuperación de información. Se utiliza para problemas de búsqueda de texto completo, es decir, buscar documentos que contengan la palabra en función de la palabra. el navegador, el motor de búsqueda necesita encontrar todos los documentos relacionados y ordenarlos por relevancia.
La implementación subyacente del índice de texto completo se basa en un índice invertido. El llamado índice invertido describe la relación de mapeo entre palabras y documentos, expresada en forma de (palabra, (el documento donde se encuentra la palabra, el desplazamiento de la palabra en el documento)). El índice de texto completo está organizado:
mysql> CREAR TABLA líneas_apertura ( id INT SIN FIRMAR AUTO_INCREMENT NO CLAVE PRIMARIA NULA, TEXTO de línea_apertura(500), autor VARCHAR(200), título VARCHAR(200), TEXTO COMPLETO idx (opening_line) ) MOTOR=InnoDB; mysql> INSERTAR EN líneas_apertura(línea_apertura,autor,título) VALORES ('Llámame Ismael.','Herman Melville','Moby-Dick'), ('Un grito cruza el cielo', 'Thomas Pynchon', 'Gravity's Rainbow'), ('Soy un hombre invisible.','Ralph Ellison','Hombre invisible'), ('¿Dónde ahora? ¿Quién ahora? ¿Cuándo ahora?', 'Samuel Beckett', 'El Innombrable'); mysql> SET GLOBAL innodb_ft_aux_table='prueba/líneas_de_apertura'; mysql> seleccione * de information_schema.INNODB_FT_INDEX_TABLE; +-----------+--------------+-------------+-------- ---+--------+----------+ | PALABRA | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSICIÓN | +-----------+--------------+-------------+-------- ---+--------+----------+ | a través | 4 | 4 | 1 | 4 | 18 | | llamar | 3 | 3 | 1 | 3 | 0 | | viene | 4 | 4 | 1 | 4 | 12 | | invisibles | 5 | 5 | 1 | 5 | 8 | | ismael | 3 | 3 | 1 | 3 | 8 | | hombre | 5 | 5 | 1 | 5 | 18 | | ahora | 6 | 6 | 1 | 6 | 6 | | ahora | 6 | 6 | 1 | 6 | 9 | | ahora | 6 | 6 | 1 | 6 | 10 | | gritando | 4 | 4 | 1 | 4 | 2 | | cielo | 4 | 4 | 1 | 4 | 29 | +-----------+--------------+-------------+-------- ---+--------+----------+
Como se indicó anteriormente, se crea una tabla y se establece un índice de texto completo en la columna open_line. Tomemos como ejemplo la inserción de "Llámame Ishmael." , 'ishmael ', porque 'me' es más pequeño que la longitud mínima de palabra establecida en ft_min_word_len(4) y se descarta. Al final, solo 'call' e 'ishmael' se registrarán en el índice de texto completo, donde La posición inicial de 'llamar' es el carácter 0 del documento, el desplazamiento es 0, la posición inicial de 'ishmael' es el carácter 12 del documento y el desplazamiento es 12.
Para obtener una introducción más detallada a las funciones del índice de texto completo, consulte el Manual de referencia de MySQL 8.0. Este artículo analizará brevemente el proceso de ejecución de la declaración Insertar desde el nivel del código fuente.
2. Caché de índice de texto completo
Lo que se registra en la tabla de índice de texto completo es {palabra, {ID del documento, posición de aparición}}, es decir, para insertar un documento, es necesario segmentarlo en varias {palabras, {ID del documento, posición de aparición}}, como una estructura, si cada vez que se vacía el disco inmediatamente después de la segmentación de palabras, el rendimiento será muy pobre.
Para aliviar este problema, Innodb introdujo un caché de índice de texto completo, que funciona de manera similar a Change Buffer. Cada vez que se inserta un documento, los resultados de la segmentación de palabras primero se almacenan en caché y luego se vacían en el disco en lotes cuando el caché está lleno, evitando así el vaciado frecuente del disco. Innodb define la estructura fts_cache_t para administrar el caché, como se muestra en la siguiente figura:
Cada tabla mantiene un caché y para cada tabla para la que se crea un índice de texto completo, se crea un objeto fts_cache_t en la memoria. Tenga en cuenta que fts_cache_t es un caché a nivel de tabla. Si se crean varios índices de texto completo para una tabla, todavía habrá un objeto fts_cache_t correspondiente en la memoria. Algunos miembros importantes de fts_cache_t son los siguientes:
-
optimizar_lock, eliminado_lock, doc_id_lock: bloqueos mutex, relacionados con operaciones concurrentes.
-
eliminado_doc_ids: tipo de vector, almacena doc_ids eliminados.
-
índices: tipo de vector, cada elemento representa un índice de texto completo. Cada vez que se crea un índice de texto completo, se agregará un elemento a la matriz. Los resultados de la segmentación de palabras de cada índice se almacenan en una estructura de árbol rojo-negro. La clave es palabra y el valor es doc_id y el desplazamiento de la palabra.
-
tamaño_total: toda la memoria asignada por el caché, incluida la memoria utilizada por sus subestructuras.
3. Insertar proceso de ejecución de declaraciones.
Tomando el código fuente de MySQL 8.0.22 como ejemplo, la ejecución de la instrucción Insert se divide principalmente en tres etapas, a saber, la etapa de escritura de registros de filas, la etapa de envío de transacciones y la etapa de limpieza sucia.
3.1 Fase de registro de fila de escritura
El flujo de trabajo principal para escribir registros de filas se muestra en la siguiente figura:
Como se muestra en la figura anterior, lo más importante en esta etapa es generar doc_id, escribirlo en el registro de fila de Innodb y almacenar en caché el doc_id para que el contenido del texto se pueda obtener en función del doc_id durante la fase de envío de la transacción. La pila de llamadas a funciones es la siguiente:
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 y fts_trx_table_add_op son dos funciones importantes: fts_get_next_doc_id es obtener doc_id. Los registros de fila de Innodb contienen algunas columnas ocultas, como row_id, trx_id, etc. Si se crea un índice de texto completo, también se agregará un campo oculto FTS_DOC_ID a la fila. records, este valor se obtiene en fts_get_next_doc_id, de la siguiente manera:
Y fts_trx_add_op agrega la operación de índice de texto completo a trx y se procesará aún más cuando se confirme la transacción.
3.2 Fase de envío de transacciones
El flujo de trabajo principal de la fase de envío de transacciones se muestra en la siguiente figura:
Esta etapa es el paso más importante en toda la inserción FTS. La segmentación de Word del documento, la obtención de {palabra, {ID del documento, posición de aparición}} y la inserción en el caché se completan en esta etapa. Su pila de llamadas de funciones es la siguiente:
fts_commit_table ->fts_add ->fts_add_doc_by_id ->fts_cache_add_doc // Obtener el documento basado en doc_id y segmentar el documento en palabras ->fts_fetch_doc_from_rec // Agrega los resultados de la segmentación de palabras al caché ->fts_cache_add_doc ->fts_optimize_request_sync_table // Crea un mensaje FTS_MSG_SYNC_TABLE para notificar al hilo sucio que limpie el hilo sucio ->fts_optimize_create_msg(FTS_MSG_SYNC_TABLE)
Entre ellos, fts_add_doc_by_id es una función clave. Esta función logra principalmente las siguientes cosas:
1) Busque el registro de fila según doc_id y obtenga el documento correspondiente;
3) Determine si caché->; total_size alcanza el umbral, si alcanza el umbral, agregue un mensaje FTS_MSG_SYNC_TABLE a la cola de mensajes del hilo sucio para notificar al hilo que se vacíe (fts_optimize_create_msg).
Para facilitar la comprensión, omití la parte del código sobre manejo de excepciones y algunas partes comunes de búsqueda de registros, y brindé breves comentarios:
ulint estático fts_add_doc_by_id(fts_trx_table_t *ftt, doc_id_t doc_id) { /* 1. Buscar registros en el índice fts_doc_id_index basado en docid*/ /* btr_cur_search_to_nth_level, btr_cur_search_to_nth_level será llamado en la función btr_pcur_open_with_no_init Se realizará el proceso de búsqueda de registros del árbol b+. Primero, el nodo hoja donde se encuentra el registro docid se encuentra desde el nodo raíz y luego el registro docid se encuentra mediante búsqueda binaria. */ 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) { /* Si se encuentra el registro docid*/ si (is_id_cluster) { /** 1.1 Si fts_doc_id_index es un índice agrupado, significa que se han encontrado los datos del registro de fila y el registro de fila se guarda directamente**/ doc_pcur = &pcur; } demás { /** 1.2 Si fts_doc_id_index es un índice secundario, debe buscar más en el registro de fila en el índice agrupado según la identificación de la clave principal encontrada en 1.1 y guardar el registro de fila después de encontrarlo **/ btr_pcur_open_with_no_init(clust_index, clust_ref, PAGE_CUR_LE, BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr); doc_pcur = &clust_pcur; } // Recorrer caché->get_docs para (ulint i = 0; i < num_idx; ++i) { /***** 2. Realice la segmentación de palabras en el documento, obtenga el par asociado {palabra, (el documento donde se encuentra la palabra, el desplazamiento de la palabra en el documento)} y agréguelo al caché** ***/ fts_doc_t documento; fts_doc_init(&doc); /** 2.1 Obtenga el documento de contenido de la columna correspondiente del índice de texto completo en el registro de fila de acuerdo con doc_id y analice el documento, principalmente para construir los tokens de la estructura fts_doc_t. Los tokens son un árbol rojo-negro. estructura. Cada elemento es una {palabra, [la estructura de la posición donde aparece la palabra en el documento]}, y los resultados del análisis se almacenan en el documento**/ fts_fetch_doc_from_rec(ftt->fts_trx->trx, get_doc, clust_index,doc_pcur, compensaciones, &doc); /** 2.2 Agrega la {palabra, [la posición donde aparece la palabra en el documento]} obtenida en el paso 2.1 a index_cache**/ fts_cache_add_doc(table->fts->cache, get_doc->index_cache, doc_id, doc.tokens); /***** 3. Determine si cache->total_size alcanza el umbral. Si se alcanza el umbral, agregue un mensaje FTS_MSG_SYNC_TABLE a la cola de mensajes del hilo sucio para notificar al hilo que limpie *****/ bool need_sync = falso; if ((caché->total_size - caché->total_size_before_sync > fts_max_cache_size / 10 || fts_need_sync) &&!cache->sync->in_progress) { /** 3.1 Determinar si se alcanza el umbral**/ need_sync = verdadero; caché->total_size_before_sync = caché->total_size; } si (necesita_sincronización) { /** 3.2 Empaquete el mensaje FTS_MSG_SYNC_TABLE y móntelo en la cola fts_optimize_wq, y notifique al hilo fts_optimize_thread para que lo limpie. El contenido del mensaje es el ID de la tabla **/ fts_optimize_request_sync_table(table); } } } }
Después de comprender el proceso anterior, puede explicar el fenómeno especial del envío de transacciones de índice de texto completo descrito en el sitio web oficial. Consulte la sección Manejo de transacciones de índice de texto completo de InnoDB del Manual de referencia de MySQL 8.0. la tabla de índice de texto completo, si la transacción actual no está confirmada, no podemos encontrar el registro de fila insertado a través del índice de texto completo en la transacción actual. La razón es que la actualización del índice de texto completo se completa cuando se confirma la transacción. Cuando la transacción no se confirma, fts_add_doc_by_id aún no se ha ejecutado, por lo que no se puede encontrar el registro a través del índice de texto completo. Sin embargo, en la Sección 3.1, podemos saber que el registro de fila de Innodb se ha insertado en este momento. Si consulta a través del índice de texto completo, puede encontrar el registro ejecutando directamente SELECT COUNT(*) FROM open_lines.
El flujo de trabajo principal de la fase de limpieza se muestra en la siguiente figura:
Cuando se inicia InnoDB, se creará un subproceso en segundo plano, la función del subproceso es fts_optimize_thread y la cola de trabajo es fts_optimize_wq. En la fase de envío de transacciones de la Sección 3.2, cuando el caché está lleno, la función fts_optimize_request_sync_table agregará un mensaje FTS_MSG_SYNC_TABLE a la cola fts_optimize_wq. El hilo en segundo plano eliminará el mensaje y vaciará el caché en el disco. Su pila de llamadas de funciones es la siguiente:
fts_optimize_thread ->ib_wqueue_timedwait ->fts_optimize_sync_table ->fts_sync_table ->fts_sync ->fts_sync_commit ->fts_cache_clear
Las principales operaciones realizadas por este hilo son las siguientes:
-
Recibir un mensaje de la cola fts_optimize_wq;
-
Determine el tipo de mensaje. Si es FTS_MSG_SYNC_TABLE, realice el vaciado;
-
Vacíe el contenido del caché a la tabla auxiliar en el disco;
-
Borre el caché y configúrelo en su estado inicial;
-
Regrese al paso 1 y reciba el siguiente mensaje;
En la Sección 3.2, cuando se envía la transacción, si el tamaño_total del caché fts es mayor que el umbral de tamaño de memoria establecido, se escribirá un FTS_MSG_SYNC_TABLE y se insertará en la cola fts_optimize_wq. El hilo sucio procesará el mensaje y eliminará los datos. el caché fts al disco y luego borre el caché.
Vale la pena mencionar que cuando el tamaño total de la caché fts es mayor que el umbral de tamaño de memoria establecido, solo se escribirá un mensaje en la cola fts_optimize_wq. En este momento, la caché fts aún puede escribir datos y memoria antes de que sean procesados por el. El hilo de descarga de fondo seguirá aumentando, lo que también es la causa principal del problema de OOM que provoca la inserción simultánea de índices de texto completo. La solución para el problema es el parche de error n.° 32831765. EL SERVIDOR LLEGA A LA CONDICIÓN DE OOM AL CARGAR DOS INNODB . pueden comprobarlo por sí mismos.
Enlace de verificación de OOM: https://bugs.mysql.com/bug.php?id=103523
Si el hilo sucio aún no ha ensuciado el caché fts de una determinada tabla, el proceso MySQL fallará y los datos en el caché se perderán. Después de reiniciar, la primera vez que se ejecuta insert o select en la tabla, los datos en el caché antes del bloqueo se restaurarán en la función fts_init_index. En este momento, el synced_doc_id que se ha colocado en el disco se leerá desde la configuración. tabla, y el synced_doc_id en la tabla será mayor que synced_doc_id. Los registros se leen y se segmentan por palabras y se restauran en la caché. Para una implementación específica, consulte las funciones fts_doc_fetch_by_doc_id y fts_init_recover_doc.
Haga clic para seguir y conocer las nuevas tecnologías de Huawei Cloud lo antes posible ~
Los recursos pirateados de "Celebrating More Than Years 2" se cargaron en npm, lo que provocó que npmmirror tuviera que suspender el servicio unpkg. El equipo de inteligencia artificial de Microsoft en China empacó colectivamente y se fue a los Estados Unidos, involucrando a cientos de personas. Biblioteca de visualización frontal y el conocido proyecto de código abierto ECharts de Baidu: "ir al mar" para respaldar a Fish. ¡ Los estafadores utilizaron TeamViewer para transferir 3,98 millones! ¿Qué deberían hacer los proveedores de escritorio remoto? Zhou Hongyi: No queda mucho tiempo para que Google recomiende que todos los productos sean de código abierto. Un ex empleado de una conocida empresa de código abierto dio la noticia: después de ser desafiado por sus subordinados, el líder técnico se enfureció. Despidió a la empleada embarazada. Google mostró cómo ejecutar ChromeOS en una máquina virtual de Android. Por favor, dame un consejo, ¿qué papel juega aquí time.sleep(6)? Microsoft responde a los rumores de que el equipo de IA de China está "haciendo las maletas para Estados Unidos" El People's Daily Online comenta sobre la carga tipo matrioska del software de oficina: Sólo resolviendo activamente los "conjuntos" podremos tener un futuro