Cambiar el pensamiento y comprender el índice del árbol B +

Nota: La evolución del índice descrita en este artículo es para ayudar a comprender la estructura del índice. Algunos conceptos se han simplificado y la implementación de detalles específicos puede ser diferente.

  Supongamos que hay una mesa:

create table i_t(
	`num` bigint;
	`age` int
)

  Ahora necesita insertar 5 datos:

insert into i_t value(3,18);
insert into i_t value(4,19);
insert into i_t value(1,20);
insert into i_t value(2,25);
insert into i_t value(5,12);

  Ahora primero tenemos que diseñar el modelo de almacenamiento de la base de datos, porque estos datos deben almacenarse en el disco eventualmente, entonces, ¿cómo almacenarlos? Es muy simple, solo necesitamos un paso de almacenamiento aleatorio, incluso si cinco datos están separados por decenas de miles de millas en el disco, no importa, de todos modos, los datos se almacenan y luego porque los datos están dispersos , cada fila de las necesidades de registros para añadir un espacio para guardar la siguiente fila. la dirección de los datos, la demanda es completa ~ (primero puede mirar el mínimo resumen del disco I / O para comprender la lectura de disco) Después de que la
Inserte la descripción de la imagen aquí
  información es almacenado, todo lo que tiene que hacer es consultar. Suponga que desea consultar los datos de num == 5 en este conjunto de datos:

select * from i_t where num = 5;

  Primero cargamos el bloque del primer registro (num == 3) del disco a la memoria, y juzgamos que num! = 5, luego continuamos buscando el segundo registro del disco desde la dirección del segundo registro guardado desde el primer registro Cargue el bloque en la memoria, continúe juzgando y encuentre que num! = 5, entonces todavía necesita continuar buscando desde el disco, las siguientes filas de datos son similares. Por lo tanto, se requiere un total de 5 E / S de disco aleatorias para ejecutar este SQL . De hecho, tan pronto como se leyó el primer disco, no solo se leyó un bloque. Debido al mecanismo de lectura previa, también se cargaron muchos datos (múltiplos enteros de la página). El sistema operativo adoptó de forma predeterminada el datos que se pueden utilizar. El segundo registro está "demasiado lejos", por lo que, lamentablemente, no está en los datos.
  La eficiencia de la E / S aleatoria de disco es muy baja, y de acuerdo con el principio de sector de disco, bloque de disco y prelectura página por página del sistema operativo, si juntamos estos datos (no necesariamente físicamente estrictamente continuos), entonces que una E / S de disco puede leerlos todos en la memoria, y luego atravesar los registros que coinciden con num == 5 en la memoria, reduciendo la E / S del disco, y la eficiencia definitivamente será mucho mayor.
  Entonces, en base a esta idea, podemos abstraer el concepto de un nodo . Para los datos que insertamos, los colocamos en un nodo. Es mejor que este nodo haga un uso completo de la función de lectura anticipada del disco, luego podemos configurar el tamaño de este nodo como una operación Múltiplos enteros de la página del sistema , y luego poner los datos en este nodo, para que una E / S de disco pueda cargarlos en la memoria:
Inserte la descripción de la imagen aquí
  De acuerdo con este modo de almacenamiento, ejecutamos este SQL, Necesito cargar este nodo desde el disco primero Vaya a la memoria, y luego recorra los registros en el nodo hasta encontrar el registro con num == 5. Solo hay una E / S de disco, que de hecho es mucho más rápida que antes.
  Pero incluso si solo hay una E / S de disco y los datos se cargan en la memoria, aún necesitamos recorrer todos los registros en el nodo para ubicar los datos que necesitamos encontrar. ¿Hay alguna forma de optimizarlos? Es difícil, porque no hay una regla en la disposición de estos datos, es difícil para nosotros encontrar un algoritmo de recuperación adecuado, pero ¿qué pasa si los datos se ordenan de acuerdo con el campo num? En función de las características del pedido, puede crear un catálogo para él , dividir lógicamente los datos en varios segmentos, encontrar el segmento donde se ubican los datos en el catálogo a través de la dicotomía y luego buscar el segmento específico. De esta manera, a través del método de segmentación + directorio, se puede reducir una gran cantidad de filtrado de datos innecesario bajo una gran cantidad de datos (puede consultar la tabla de salto ), y habrá una gran mejora en el rendimiento.
  Además, los datos ordenados pueden traernos muchas sorpresas más adelante. Pero de acuerdo con esta idea, cada vez que se insertan datos, los
Inserte la descripción de la imagen aquí
  clasificamos de acuerdo con el campo num para asegurarnos de que los datos en el nodo estén en orden: obviamente, la clasificación también consumirá rendimiento. ¿No sería mejor si el orden de inserción de datos se ordenara según el campo num? Solo necesita volver a agregar datos y no hay trabajo de clasificación.
  Después de ordenar los datos, segmentamos lógicamente los datos en el nodo de acuerdo con los supuestos anteriores, y luego creamos un directorio con la siguiente estructura: Después de
Inserte la descripción de la imagen aquí
  tener este directorio, si queremos encontrar num == 5 datos, entonces podemos dividir los datos por el directorio. La búsqueda salta el primer segmento y localiza directamente el segundo segmento, reduciendo la carga de trabajo, especialmente cuando la cantidad de datos es mayor, los beneficios serán mayores. Esta es una idea de intercambiar espacio por tiempo.
  Pero dicho esto, la cantidad de datos es cada vez mayor, y el espacio ocupado por este nodo es cada vez mayor. Nuestra intención original de diseñar este nodo es aprovechar al máximo las características de lectura anticipada del disco y minimizar E / S de disco.Grandes, no es práctico cargar en la memoria a la vez.
  Naturalmente, necesitamos especificar la cantidad de espacio en disco que ocupa este nodo. Como se mencionó anteriormente, para hacer un mejor uso de la lectura anticipada del disco, configúrelo en un múltiplo entero de la página del sistema operativo, como 4K, 8K, 16K, etc. Cuando los datos están llenos, es necesario abrir un nuevo nodo. Si desea asegurarse de que los dos nodos sean físicamente continuos, el costo de mantenimiento es un poco alto, o puede usar la estructura de lista vinculada para organizar (ahora el valor máximo de num ha aumentado a 10):
Inserte la descripción de la imagen aquí
  ahora tenemos dos nodos, y cada fila de datos se visualiza en el nodo, o en general, están todos en orden. Basado en tal estructura, si queremos encontrar:

select * from i_t where num = 10;

  ¿Cómo encontrarlo? Obviamente, primero debe cargar el primer nodo en la memoria y luego usar el directorio del nodo uno para determinar que los datos de num == 10 pueden estar en el segundo segmento, y luego ir al segundo segmento para buscar y encontrar el más grande El número también es igual a 5, lo que significa que los datos que se buscarán no están disponibles o están en el siguiente nodo. Por lo tanto, necesitamos cargar el segundo nodo en la memoria. Afortunadamente, el primer nodo tiene el puntero de dirección del segundo nodo, por lo que podemos ubicar directamente el segundo nodo. Después de que el segundo nodo se carga en la memoria, es necesario repetir la lógica de búsqueda anterior. De acuerdo con el directorio del segundo nodo, se determina que los datos pueden estar en el segundo segmento, y luego directamente en el segundo segmento para buscar, y finalmente encontró num = = 10 datos.
  Este proceso parece no ser un problema, tan sedoso como Dove, pero como ahora tenemos dos nodos, puede haber tres nodos, cuatro nodos, n nodos:
Inserte la descripción de la imagen aquí
  si queremos encontrar num == 892 ¿Qué pasa con los datos? ¿No es necesario atravesar desde el primer nodo hasta el último nodo? La carga de un nodo puede corresponder a la E / S del disco, lo cual es terrible. Resolver este problema es realmente muy simple, el problema que estamos encontrando es en realidad el mismo que encontramos en un solo nodo al principio: la lista enlazada es demasiado larga y debe recorrerse desde el principio. Hemos creado un directorio en un solo nodo. De hecho, también puede crear un directorio para los nodos aquí. La idea es similar, es decir, extraer el valor num más pequeño en estos nodos (es decir, el valor num de la primera fila de datos en el nodo) y ponerlo en uno En el nodo superior, por supuesto, se debe mantener la dirección inicial del nodo (el valor máximo de num ha aumentado a 15):
Inserte la descripción de la imagen aquí
  ahora tenemos otra capa de directorios, si desea consultar:

select * from i_t where num = 15;

  ¿Hay dos formas?

  • La primera es atravesar secuencialmente desde el primer nodo en el lado izquierdo de la capa inferior como se describió anteriormente.Este método requiere 3 E / S de disco.
  • El segundo es cargar el directorio de nivel superior en la memoria y luego usar la búsqueda binaria para determinar en qué nodo secundario pueden estar los datos, y luego cargar el nodo secundario correspondiente en la memoria y recuperar los datos de la misma manera que en el nodo de la sección anterior.

  Obviamente, los datos de num == 15 solo pueden estar en el nodo más a la derecha, y luego cargar el nodo más a la derecha en la memoria, y finalmente encontrar los datos. Este método solo requiere 2 E / S de disco, y para el directorio superior, porque solo hay Con el campo num y el puntero de dirección, incluso podemos poner este directorio directamente en la memoria, ¡y solo se necesita una E / S de disco! Con el crecimiento continuo de datos, hay cada vez más nodos y podemos almacenar más subnodos en el nodo de directorio de nivel superior La reducción de los tiempos de E / S de disco que trae esta estructura será más optimista.
  ¡Hasta ahora hemos experimentado las enormes sorpresas que nos traen los datos ordenados! Pero dicho esto, este nodo de directorio de nivel superior también es un nodo, y todavía sigue el tamaño del nodo que definimos anteriormente. Aunque no almacena todos los datos en comparación con el nodo de nivel inferior, siempre hay un nodo superior límite. ¿Qué pasa si la memoria del nodo de directorio de nivel superior está llena? Ya debes haberlo pensado. Este es el mismo que encontramos antes que los datos en un nodo están llenos y necesitamos crear un nuevo nodo. Si el nodo directorio está lleno, puedes abrir un nuevo nodo directorio! Siguiendo la misma lógica, después de que abrimos un nuevo nodo de directorio, la estructura se volvió así (debido al tamaño de la imagen, se supone que el nodo del directorio superior solo puede contener como máximo dos bytes, y el valor máximo de num ha aumentado a 20):
Inserte la descripción de la imagen aquí
  ahora el directorio superior Los nodos se han convertido en dos y los nodos inferiores se han convertido en cuatro. Si desea realizar una consulta ahora:

select * from i_t where num = 20;

  ¿Cómo encontrarlo? Todavía hay dos formas, una es consultar secuencialmente desde el primer nodo en la parte inferior izquierda, un total de 4 E / S de disco; la segunda es pasar por el directorio superior, pero aquí hay un problema, ¿cómo encontramos el segundo ¿Qué pasa con un directorio de nivel superior? En otras palabras, esperamos usar estos dos directorios de nivel superior para determinar en qué nodo subyacente pueden estar los datos. Puede que lo hayas pensado, la lógica es la misma que antes, es decir, para crear otro directorio de nivel superior para los dos nodos de directorio de nivel superior. El método de creación es el mismo, extrayendo el valor num más pequeño en cada nodo, mientras se mantienen sus respectivos punteros de dirección:
Inserte la descripción de la imagen aquí
  ahora busque los datos con num == 20: primero cargue el nodo de nivel superior en la memoria y use la dicotomía para determinar que los datos pueden estar a la derecha. Luego, cargue el directorio correcto del segundo nivel en la memoria, y luego use la dicotomía para determinar que los datos pueden estar en el nodo inferior derecho, y luego cargue el nodo inferior derecho en la memoria y encontrar los datos de acuerdo con la lógica de búsqueda en el nodo anterior. Se han experimentado un total de 3 E / S de disco. El nodo de nivel superior también solo tiene el campo num y el puntero de dirección. Lo colocamos en la memoria. Si el directorio de segundo nivel tiene condiciones, también lo colocamos en la memoria ¿Hay solo una E / S de disco?

Nota: Debido al tamaño de la imagen, solo hay dos nodos secundarios en el nodo de la capa de directorio aquí, por lo que no hay una gran diferencia. De hecho, un nodo de directorio puede contener muchos nodos secundarios. Aquí puede calcularlo: el puntero de dirección ocupa 6 bytes, se supone que num es de tipo bigint, que ocupa 8 bytes, y un nodo secundario en el directorio ocupa 14 bytes, si el tamaño de un nodo se establece en 16K, ignorando otro almacenamiento adicional que pueda consumirse por rendimiento u otras razones durante la implementación específica, entonces un nodo de directorio puede almacenar 16 * 1024/14 = 1170 nodos secundarios, que pueden verse como enormes La diferencia es que si alcanza este orden de magnitud, se ahorrará una gran cantidad de E / S de disco.

  Ahora consideremos este sql:

select * from i_t where id < 16 order by id desc limit 4;

  Según esta estructura, es realmente difícil implementar este SQL. Solo hay una forma que podemos elegir, que es retroceder desde el primer nodo en la esquina inferior izquierda, recopilar datos y luego buscar los últimos 4 datos. . Esto es equivalente a un requisito de consulta en orden inverso . De hecho, podemos modificar ligeramente la estructura existente para optimizar esta situación: extienda el puntero unidireccional entre los nodos subyacentes a punteros bidireccionales y proporcione a un nodo la capacidad de buscar hacia adelante.
Inserte la descripción de la imagen aquí
  De acuerdo con esta estructura, no podemos ubicar directamente una fila de datos a través del nodo del directorio, pero solo podemos ubicar un cierto nodo inferior a través del directorio, y luego leer el nodo inferior en la memoria y recuperarlo en la memoria. Para el nodo inferior, creamos una página de directorio para mejorar la velocidad de recuperación de datos en el nodo. De hecho, estos nodos de directorio también son nodos y deben recuperarse después de cargarse en la memoria para determinar qué nodo subyacente leer a continuación. Por lo tanto, para la recuperación de nodos de directorio en la memoria, también podemos adoptar métodos como la dicotomía para mejorar la eficiencia de la recuperación, o debido al tamaño de la imagen, la visualización en algunos lugares se reducirá.

para resumir

  Se puede ver que el directorio completo finalmente se convierte en una estructura de árbol de búsqueda de múltiples vías , que puede llamarse árbol de índice , y el proceso de recuperación de todo el árbol de índice es también un proceso de "división múltiple". Solo los nodos hoja tienen datos completos. Los nodos no hoja solo retienen el campo de índice y los punteros de dirección de los nodos secundarios, y el primer campo de índice (el más pequeño) de cada nodo hoja también se almacena de forma redundante en el nodo de directorio. El primer dato del primer nodo de la esquina inferior izquierda debe ser el más pequeño (para el campo num) y, en su conjunto, aumenta de forma secuencial. Dado que el árbol de índices contiene todos los datos completos, puede denominarse " índice agrupado ".
  Cada nodo es un nodo hoja o ya sean nodos hoja, estamos en una página como una unidad a administrar (páginas en la base de datos, no el sistema operativo de la página), con el fin de hacer un mejor uso de la pre-lectura, configurar a la página del sistema operativo Múltiplos enteros. A excepción de los nodos hoja, se puede hacer referencia tentativamente a la página de datos , el nodo de directorio se puede referir a la página de índice , el campo NUM se puede llamar campo de índice , donde se puede describir adicionalmente como una clave primaria .
  En cada nodo, se utiliza un directorio integrado para mejorar la velocidad de recuperación de datos en el nodo. Para hacer frente a varios escenarios de consulta, también ampliamos la conexión entre los nodos hoja a punteros bidireccionales.
  Con base en esta estructura, tenemos dos formas de consultar un dato:

  • Una es buscar horizontalmente desde el primer nodo hoja en la esquina inferior izquierda a su vez.
  • Uno es buscar desde el nodo raíz del árbol.

  El primer método puede denominarse exploración de tabla completa , y el segundo método incluso utiliza un índice. Si usa un escaneo de tabla completo, puede haber muchas E / S de disco y filtrado de datos, y si puede usar el índice, puede reducir considerablemente la E / S de disco y el filtrado de datos, porque podemos ubicar rápidamente la hoja a través de el nodo de índice.

  Desde el punto de vista estructural, el diseño tiene los siguientes puntos clave:

  • Ordenadamente, esta es la base de toda la estructura del índice, la piedra angular del alto rendimiento.
  • Solo las páginas de datos (nodos hoja) tienen datos completos. En general, las páginas de índice solo guardan campos de índice y punteros de dirección. Esto asegura que una página de índice pueda contener más nodos secundarios, lo que reduce en gran medida la altura del árbol.
  • Dependiendo del segundo punto, cada página de índice puede almacenar más nodos secundarios, lo que significa que la altura del árbol se puede controlar dentro de un cierto rango, y la altura del árbol tiene el mayor impacto en el índice, porque un nodo puede significar E / S de un disco. De hecho, si la página está configurada en 16K, en circunstancias normales, un árbol de índice de tres niveles puede proporcionar servicios para decenas de millones de datos.
  • Las páginas de datos están conectadas por punteros bidireccionales
  • Solo se puede ubicar una página a través del árbol de índice, pero no se puede ubicar una fila específica de datos en la página. Aún es necesario cargar la página en la memoria y luego recuperarla en la memoria. Aunque la recuperación de la memoria es rápida, debemos encontrar formas de mejorar la eficiencia de recuperación de los datos en la página, y el orden de los datos también nos proporciona los requisitos previos para la optimización.

  Esta es una estructura general del índice de árbol innodb B + en MySQL, pero hay muchos detalles en el diseño. Por ejemplo, la estructura de la página innodb es realmente muy complicada. Además de los registros de datos (registros de usuario), hay estructuras que pueden ayudar a mejorar la eficiencia de la recuperación de datos en la página (Page Directory, Infimum, Supremum, etc.) son Encabezado de archivo, Encabezado de página, Tráiler de archivo, etc. Este es un conjunto completo de diseño que considera todos los aspectos de la integridad de los datos, la eficiencia de recuperación, la eficiencia del almacenamiento, la tolerancia a fallas, etc.

Nota: Este artículo se basa en la comprensión personal del bloguero, si es incorrecto, ¡gracias por señalarlo!

Supongo que te gusta

Origin blog.csdn.net/huangzhilin2015/article/details/115060065
Recomendado
Clasificación