Consulta de combinación de MySQL y optimización de combinación de hash | Sesión de intercambio de tecnología de StoneDB n.º 3

Diseño: Xiaoai

Crítico: Ding Qi, Yu Ting

Editor: Yu Ting

Autor 1: Xu Xinqiang (nombre de la flor: Fig)
Universidad de Ciencia y Tecnología Electrónica - Tecnología Informática - estudiante de maestría ,
pasante de I + D del núcleo StoneDB

Autor 2: Liu Zhanyu (nombre de la flor: Wuzi)
Universidad de Zhejiang-Ingeniería de software- Candidato a maestro
, pasante de I + D del kernel StoneDB

1. Introducción al método de conexión MySQL

MySQL admite cinco métodos de conexión: unión natural, unión equivalente (unión interna), unión izquierda, unión derecha y unión cruzada. No admite uniones externas completas. Las uniones externas completas se pueden realizar a través de operaciones de unión. Algoritmos de unión: bucles anidados simples, bucles anidados indexados, bucles anidados en bloque y combinaciones hash.

Bucle anidado simple (unión de bucle anidado simple, SNLJ)

Cada registro de la tabla de control se compara y juzga secuencialmente con todos los registros de la tabla de control, la tabla de control se recorre una vez y la tabla de control se recorre varias veces. Este algoritmo es muy costoso Suponiendo que el número de filas en la tabla de control es M y el número de filas en la tabla de control es N, la complejidad temporal del algoritmo es O (M * N). En realidad, MySQL no usa este algoritmo.

Unión de bucle anidado indexado (INLJ)

Al crear un índice en la tabla controlada, se reduce el número de exploraciones de la tabla controlada. En general, la altura del árbol B+ es de 3 a 4 capas, es decir, el consumo de E/S para una coincidencia es solo de 3 a 4 veces, por lo que el costo de la consulta de índice es relativamente fijo, por lo que el optimizador tiende a usar tablas con menos registros como una tabla de unidad. MySQL intentará usar este algoritmo si hay un índice. Todo el proceso se muestra en la siguiente figura:

Unión de bucle anidado en bloque (BNLJ)

El proceso de escanear una tabla consiste en cargar primero la tabla desde el disco a la memoria y luego comparar si se cumplen las condiciones correspondientes en la memoria. Sin embargo, es posible que no todos los registros de la siguiente tabla estén completamente almacenados en la memoria. Para reducir el número de visitas a la tabla controlada, primero podemos cargar por lotes los datos de la tabla controlada en el búfer de unión (búfer de conexión), y luego, al cargar los registros de la tabla controlada en la memoria, podemos: tiempo y múltiples registros en la tabla controlada Los registros coinciden, lo que puede reducir en gran medida la cantidad de escaneos de la tabla controlada, que es la idea del algoritmo BNL. MySQL intenta usar este algoritmo cuando no se crean índices en la tabla controlada. Comparación de eficiencia general: INLJ > BNLJ > SNLJ. Todo el proceso se muestra en la siguiente figura:

Unión hash

La combinación de bucle anidado es una mejor opción para unir conjuntos de datos pequeños, y Hash Join es una mejor manera para conjuntos de datos grandes. El optimizador usa la más pequeña de las dos tablas para construir una tabla hash en la memoria basada en la clave de combinación, luego escanea la tabla más grande y la prueba para encontrar filas que puedan coincidir con la tabla hash. Hash Join destruirá el orden y la localidad de los datos de la tabla, por lo que solo se puede aplicar a combinaciones de equivalencia.

2. Breve descripción de la dirección de optimización de la conexión hash

Con el soporte de Hash Join en MySQL 8.0, Hash Join es obviamente una mejor opción que BNLJ cuando no hay índice en las tablas internas y externas o una tabla pequeña está controlada por una tabla grande Esto hace que Hash Join tenga múltiples rutas de optimización. Este capítulo presenta brevemente algunas direcciones e ideas para la optimización de Hash Join.

ON <attr1>=<attr2>Una premisa clave es que la introducción antes mencionada mencionó que Hash Join no es adecuado para uniones no equivalentes, por lo que cuando el método de unión equivalente del estilo o la unión equivalente natural predeterminada no se usa en la declaración de unión de la tabla , MySQL no usará Hash Únete

En primer lugar, debe aclararse el proceso de trabajo de Hash Join.Tomando MySQL como ejemplo  [1]  , MySQL utiliza la implementación clásica de Hash Join de un solo subproceso, que tiene dos pasos: construir una tabla y sondear para generar resultados.

  1. "Construir una tabla" : como se muestra en la figura a continuación, construir una tabla es recorrer la OuterTable, calcular el valor hash de su clave de conexión equivalente y construir una tabla hash de acuerdo con la estructura:

  1. "Detección" : como se muestra en la figura a continuación, el paso de detección es recorrer la InnerTable, calcular el valor hash de la clave de conexión de valor de cada tupla y encontrar el cubo correspondiente (cubo) de la tabla hash, y determinar si puede ser hash por Conexión de comparación, y luego completar todo el proceso Hash Join  [2]  .

2.1 Selección del algoritmo hash

El algoritmo hash es la base de Hash Join. Un buen algoritmo hash puede mejorar en gran medida la eficiencia de los programas que se basan en operaciones hash. Una función hash excelente hará un compromiso entre la aleatoriedad (que refleja la probabilidad de colisiones hash) y la eficiencia computacional (la velocidad a la que se codifican las palabras), por lo que las bases de datos con diferentes énfasis también deben elegir funciones hash apropiadas. Por ejemplo, Apache Doris[ 3] eligió CRC32, una función hash que tiene una velocidad de cálculo rápida y puede ser acelerada por SIMD, mientras que DuckDB eligió MurmurHash[4] con mayor aleatoriedad.

Pero hoy, con la llegada de XXhash[5], la elección de la función hash parece realmente tener una panacea. Para la gran mayoría de los escenarios en los que la longitud del hash de HashTable es relativamente pequeña, las diferentes variantes de longitud de XXHash parecen ser la mejor opción:

2.2 Diseño de la estructura básica de la tabla hash

Uno de los puntos básicos del diseño de la infraestructura de la tabla hash es su forma de tratar los conflictos. Los métodos clásicos de procesamiento incluyen el direccionamiento abierto (es decir, usar detección lineal, detección cuadrada, re-hashing, etc. para continuar en la tabla hash cuando hay un conflicto hash. búsqueda) y el método zip (almacenamiento de subelementos en conflicto representados por listas vinculadas o árboles binarios equilibrados en cubos). El método zip es más intuitivo y tiene una tasa de colisión hash más baja, por lo que es más común en implementaciones de tablas hash de lenguajes de programación como Java/Go. Sin embargo, para la tabla hash en el núcleo de la base de datos, la aplicación de un gran volumen de datos, el control del uso de la memoria y la tasa de Cache Miss son requisitos de mayor prioridad Por lo tanto, los métodos de manejo de conflictos de direccionamiento abierto, como la detección lineal, son la clave para construir un hash mesa mejor solución.

Otro diseño de infraestructura clave es el mecanismo de expansión de la tabla hash. El mecanismo de expansión de la mayoría de las tablas hash es cuando la proporción de la capacidad total de las estaciones de elementos existentes excede un cierto umbral (para DuckDB, este umbral es 50% por defecto, para Java HashMap , el umbral es del 75 % de forma predeterminada) y luego se expande. Pero, obviamente, para el método de gestión de conflictos de detección lineal, la probabilidad de colisión de hash aumentará rápidamente a medida que aumenta la tasa de ocupación debido a la agrupación de hash, lo que también conduce a una pérdida de memoria. Por ello, existen una serie de trabajos [6]  para mejorar esta situación mediante métodos como el rehash o el mantenimiento de estructuras de datos de grano fino.

Para el campo de los núcleos de bases de datos, el optimizador del núcleo puede proporcionar optimizaciones estructurales disponibles y conocimiento previo para tablas hash. Por ejemplo, para una base de datos de almacenamiento en columnas, el proceso de creación se puede reducir y la clave comprimida leída en el kernel se puede usar directamente para crear y fusionar la tabla hash, lo que reduce los gastos generales de serialización y deserialización y reduce la longitud de la clave hash. . Además, puede usar los datos estadísticos del optimizador para cortar el prefijo de los datos que deben construirse, lo que puede reducir aún más la longitud de la clave hash, acelerando así la velocidad de cálculo de la función hash.

2.3 Optimización del proceso de detección

Se puede ver en la sección anterior que para la tabla hash construida por el método de detección lineal, la colisión hash es el cuello de botella de rendimiento de la operación de detección, por lo que se puede introducir una capa de filtrado para filtrar los valores clave de detección que no son en la tabla hash lo antes posible, de modo que Para reducir el número de detecciones, la implementación más clásica de esta capa de filtrado es el filtro Bloom [7] que se muestra en la siguiente figura.

Este artículo ya no profundizará en el principio del algoritmo y el método de optimización del filtro Bloom. Los lectores solo necesitan saber que el filtro Bloom puede pasar varias funciones hash (en la práctica, se obtienen mediante una función hash básica y operaciones de desplazamiento y acumulación). Manipula un mapa de bits, construido atravesando las claves de equivalencia de OuterTable y sondeado por Inner Table. Su característica es que hay falsos positivos (False Positive), pero no hay falsos negativos (False Negative), es decir, los registros que pasan el filtro Bloom pueden no coincidir realmente (puede haber conflictos de hash), pero los registros filtrados No debe coincidir.

Sobre la base del filtro Bloom, hay una serie de variantes de la estructura de datos de probabilidad, como Block Bloom Filter, Cuckoo Filter[8], etc. Sin embargo, para escenarios Hash Join donde no se requiere la eliminación y las operaciones son relativamente fijas, los filtros Bloom que son fáciles de implementar y ocupan menos memoria son la mejor opción en la mayoría de los casos.

2.4 Optimización de Cache Miss

La aleatoriedad del acceso a la memoria de la tabla hash aumentará inevitablemente la tasa de errores de caché, y se debe acceder a la memoria a través de la tabla de páginas, lo que hará que Hash Join encuentre cuellos de botella en el rendimiento. La granularidad de caché máxima de los procesadores informáticos modernos es LLC (caché de última capa, que es caché L3 en procesadores Intel/AMD ampliamente utilizados), por lo que la operación del área de memoria con tamaño LLC como unidad es el punto de partida para optimizar la tasa de fallas de caché. Un método simple es la captación previa condicional (captación previa condicional): la tabla hash puede mantener metadatos sobre el número de colisiones hash, la ocupación y los puntos críticos de colisión hash, y juzgar si es posible realizar una sonda en función de estos metadatos. Generar una gran cantidad de colisiones de hash, y leer previamente varios cubos en la memoria en unidades de tamaño LLC cuando pueden ocurrir conflictos, de modo que el método de detección lineal pueda reducir la cantidad de errores de caché.

Un método más complejo es construir una tabla hash dividida como se muestra en la figura a continuación, es decir, colocar la tabla externa en una tabla sub-hash diferente (llamada partición), y al atravesar la tabla interna, se puede usar el mismo estándar para enrute la clave comparada a la partición correspondiente para la búsqueda y comparación de hash. Hay tres puntos en este

  1. En primer lugar, al permitir que cada partición se cargue en LLC, la tasa de errores de caché se reduce considerablemente al procesar las tareas de construcción y detección de una partición;

  2. El uso de memoria de la tabla hash se puede administrar de una manera más granular. La tabla hash no puede asignar memoria en forma de una potencia de 2 (con la partición como unidad de asignación básica), y al mismo tiempo, en casos extremos , también puede liberar algunas Particiones vacías para mover a Usa;

  3. Hace posible construir tablas hash en paralelo, lo cual se explica en la Sección 2.5.

La siguiente pregunta es cómo dividir de manera rápida y efectiva toda la tabla hash en varias tablas de partición Para garantizar la eficiencia de este proceso, se propuso el proceso Radix Hash Join [9].

2.5 Optimización de hash join multiproceso

Hash Join de MySQL se ejecuta en un solo hilo. Pero, por ejemplo, mediante los métodos mencionados anteriormente de construcción de filtros Bloom y tablas hash particionadas, podemos lograr una ejecución de subprocesos múltiples.

La construcción y detección del propio filtro Bloom es similar a la construcción y detección de la tabla hash, por lo que ambos pueden analizarse por analogía. Se ha demostrado matemáticamente que una variante del filtro Bloom, Blocked Bloom Filter, tiene un rendimiento similar al del filtro Bloom, pero cada bloque se puede construir en paralelo abriendo varios subprocesos, y el tamaño del bloque del valor nulo se adapta a la memoria caché. del núcleo de la CPU y, a través de SIMD, acelerar las operaciones de sondeo. La operación de detección de Hash Join es similar. Después de que los registros de la tabla interna se cortan en segmentos de tareas procesados ​​por varios subprocesos al mismo tiempo, la tabla hash se detecta en paralelo y el resultado final se fusiona cuando es necesario para garantizar el orden de los datos. ., que es similar al algoritmo de combinación de clasificación y fusión.

La operación de construcción es más complicada porque tiene operaciones de escritura Incluso para la tabla hash particionada, las operaciones de bloqueo y desbloqueo por partición o por cubeta todavía se requieren para ejecutarse en paralelo. Por lo tanto, se requieren medidas de sincronización para garantizar la consistencia y corrección de los datos entre subprocesos. En la práctica industrial actual, la cmpxchgoperación CAS (Comparar e intercambiar) implementada por instrucciones compatibles con la mayoría de los procesadores convencionales es la mejor práctica para uso intensivo de CPU.

Esta imagen se cita de Internet, se eliminará el contacto de infracción

Aclaración sobre MyBatis-Flex plagiando el navegador MyBatis-Plus Arc lanzado oficialmente 1.0, afirmando ser un reemplazo para Chrome OpenAI lanzó oficialmente la versión de Android ChatGPT VS Code optimizó la compresión de ofuscación de nombres, ¡reducción de JS incorporado en un 20%! LK-99: ¿El primer superconductor de temperatura y presión ambiente? Musk "compró por cero yuanes" y robó la cuenta de Twitter @x. El Comité Directivo de Python planea aceptar la propuesta PEP 703, haciendo que el bloqueo del intérprete global sea opcional . El número de visitas al software de código abierto y de captura de paquetes gratuito del sistema Stack Overflow ha disminuido significativamente, y Musk dijo que ha sido reemplazado por LLM
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/StoneDB/blog/10092212
Recomendado
Clasificación