Mysql Indices

De MediaWiki
Saltar a: navegación, buscar

Introdución


Cuando descubramos una de esas sentencias, tendremos que analizarla para ver la razón.
Una posible causa será el no utilizar indices.
  • Un índice es una estructura de datos en la cual los valores guardados son los datos de una o más columnas de una tabla de forma ordenada. Cada entrada del índice apuntará a la ubicación del registro en la tabla.
De esta forma, la búsqueda de registros por un campo indexado será mucho más rápida que si no lo está.
  • Como en todo, el uso de registros lleva aparejado ciertos problemas (sino fuera así podríamos crear índices para todas las columnas de una tabla):
  • Es una estructura de datos que ocupa espacio a mayores del espacio de la propia tabla.
  • En caso de operaciones de insercción, borrado o modificación el índice se debe rehacer con la consiguiente carga de trabajo para el gestor.


  • Toda clave primaria debe tener un índice asociado (de hecho se crea automaticamente).
También deberían tenerlo las claves foráneas.


  • Un ejemplo obtenido de la wiki:
Mysql index 1.png
En este ejemplo, la clave primaria tiene asociado un índice de tipo BTREE, en el que las claves están ordenadas, estando al lado izquierdo las menores y al lado derecho las mayores con respecto al valor referenciado de un nodo dado.


  • ¿ Cuando voy a hacer uso de índices ?
  • En cláusulas WHERE
  • En cláusulas ORDER BY
Si tengo una orden SQL como:
SELECT nombre FROM ALUMNOS WHERE edad between 10 and 20
Tardará menos en ejecutarse si tengo un índice asociado a la edad que si no lo tengo.

Crear índices

  • Podemos crear un índice de dos formas:


  • Veamos un ejemplo sobre una supuesta tabla Alumnos sobre la que queramos indexar su nombre:
  1. CREATE INDEX idx_Alumnos_nombre ON Alumnos (nombre)
Siendo Alumnos el nombre de la tabla y nombre la columna a indexar.
  1. ALTER TABLE Alumnos ADD INDEX idx_Alumnos_nombre (nombre)
Siendo Alumnos el nombre de la tabla y nombre la columna a indexar.


  • Algunos de los modificadores que podemos añadir a la creación de un índice:
  • UNIQUE: índice formado por campos cuyo valor no puede repetirse.
  • PRIMARY: Es el índice que conforma la clave primaria de una tabla. Los valores no pueden repetirse. Es recomendable que sea numérica y en caso de no existir, se debe crear una columna de tipo 'autonumérico'.
  • full-text: Es un tipo de índice 'especial' que se aplica a campos de texto de gran tamaño, como el tipo TEXT y sus variantes, aunque también puede realizarse sobre char y varchar. Al crear un índice de este tipo, cada una de las palabras del texto es 'indexada' para poder hacer búsquedas en las que se busca sólo alguna de las palabras del mismo. Esto se realiza haciendo uso de la orden SQL MATCH(). A diferencia de LIKE, este comando busca por palabras y permite buscar en varias columnas, indicar si deben aparecer ciertas palabras en la columna o que no aparezcan ciertas otras,...
  • SPATIAL: Son índices usados para tipos de datos espaciales como LINE o CURVE.



Modificar índices

  • No es posible.
Podemos utilizar la orden SQL ALTER TABLE con la opción ALTER INDEX para hacer el índice visible o invisible.
Esto hará que el optimizador de consultas no lo use (invisible) o lo tenga en cuenta (visible).
Sólo se puede utilizar en NDB CLUSTER.


  • Un ejemplo:
  1. ALTER TABLE Alumnos ALTER INDEX idx_Alumnos_nombre INVISBLE
Siendo Alumnos el nombre de la tabla.



Borrar índices

  • Podemos usar la orden SQL DROP INDEX.
  • Un ejemplo:
  1. DROP INDEX idx_Alumnos_nombre ON Alumnos
Siendo Alumnos el nombre de la tabla.



Tipos de índices

Parciales

  • Como comentamos en la introducción, los índices van a ocupar espacio en disco.
Una columna de 200 caracteres de ancho (200 bytes) y con 5 millones de registros, si hacemos un índice sobre ella, ocupará 1.000.000.000 de bytes = 953MB
  • Para evitarlo podemos crear un índice parcial, es decir, un índice sobre una parte del tamaño de la columna.
  1. CREATE INDEX idx_Alumnos_direccion ON Alumnos (direccion(100))
En este caso estamos creando un índice de la mitad del tamaño de la columna (100 caracteres) por lo tanto ocupará 953/2 = 476MB
  • Se pueden crear sobre columnas de tipo CHAR, VARCHAR y TEXT.
En el caso de TEXT es obligatorio indicarlo.
También con los tipos de datos BINARY, VARBINARY y BLOG, pero en este caso indicamos el número de bytes. También es obligatorio.



Multicolumna

  • Este tipo de índices abarca más de una columna.
Un caso típico podría ser el de columnas nombre y apellidos.
Si quiero crear un índice por apellidos y dentro de apellidos que ordene por nombre, crearía un único índice con las dos columnas, de la forma:
  1. CREATE INDEX idx_Alumnos_nombre_apellidos ON Alumnos (apellidos(100),nombre)
Nota Fijarse que el orden de las columnas en el índice es importante. No es lo mismo crear un índice de 'nombre-apellidos' que de 'apellidos-nombre'.


  • Si se realizan consultas en los que se busca por varios campos, se debe de crear un índice multicolumna mejor que varios índices parciales La primera parte del índice debería ser siempre la columna más usada. En el caso de usar siempre varias columnas es mejor que la primera columna sea la que tenga más duplicados.



Secundarios o clusters

  • Cuando creamos una clave primaria, realmente estamos creando un índice sobre las columnas que conforman la clave primaria, un tipo de índice que impide que haya dos valores iguales.
Al crearlo, Mysql ordena físicamente los registros para que coincidan con el índice, de tal forma que índice y registro están 'unidos'. El registro se guarda junto a su clave primaria.
Así, si tengo creado como índice de clave primaria una columna id, físicamente los registros estarán:
ID-------Nombre
1--------Angel
2--------Maria
3--------Juan
4--------Ana
Este tipo de índice se denomina cluster.


  • En una tabla, como vimos, podemos crear otros índices. Cuando usamos INNODB como motor de almacenamiento, éstos guardan el valor de la clave primaria para buscar el registro, a parte del valor del índice creado. Este tipo de índices se denominan índices secundarios o no clusterizados.
En el ejemplo anterior, si creamos un índice sobre la columna Nombre tendremos:
Nombre-------ID
Ana----------4
Angel--------1
Juan---------3
Maria--------2
Para recuperar un registro cuando usamos un índice secundario, necesitamos dos operaciones de disco, una para buscar el valor en la tabla del índice y otra para buscar el registro a partir de la clave primaria.
En el motor MyISAM pasa lo mismo, pero la diferencia es que los índices se guardan en archivos separados y el valor asociado al índice no es la clave primaria, sino el valor desplazamiento (offset) del registro en la tabla.
  • Visto lo anterior es importante elegir bien la clave primaria:
  • Debemos elegir un campo que no implique modificaciones, ya que será necesario una reordenación de los registro.
  • Además es mejor que tenga un tamaño pequeño, ya que, si por ejemplo es una cadena de 100 caracteres, estamos guardando en todos los índices secundarios 100 caracteres en cada entrada del índice.



Estructura de índices

  • Si miramos la sintaxis de la orden SQL CREATE INDEX, en la opción index_type podemos escoger entre BTREE o HASH

BTREE

  • Es una estructura en forma de árbol-b (b-tree) balanceado (se intenta que haya una cantidad similar de nodos a un lado y a otro del árbol.
Cada nodo del árbol posee un valor del índice, ordenado. Cada nodo 'apunta' a registros del disco.
La cantidad de nodos hijos permitida es variable (si fuera dos, tendríamos un árbol binario, por ejemplo).


Mysql index 1.png


  • Su uso es adecuado para consultas basadas en operadores de comparación (<,>, =, < >) y rangos de valores (between).




HASH

  • Se basan en el uso de una función de HASH. Este tipo de función, transforma una entrada (en nuestro caso, la columna de la tabla sobre la que vamos a crear un índice) en un valor único.
Ese valor único es guarda en una tabla, y cada entrada de esa tabla tiene asociada el valor de la clave primaria para buscar el registro.
Prog colecciones 4 Tabla hash1.png


  • La estructura de HASH es más rápida que la B-Tree cuando la utilizamos para comparar valores de la forma: columna = valor en la cláusula WHERE.
Presenta los siguientes inconvenientes:
  • Puede haber colisiones (en el caso de dos valores, al aplicarse la función de HASH den el mismo resultado)
  • En el caso de búsquedas por rango, es más lenta que en el caso de B-TREE.
  • En el caso de cláusulas ORDER BY, Mysql no puede emplear índices HASH.


Mysql colecciones Tabla hash 2.jpg


Más información en este enlace.


Trabajando con índices

  • Para obtener información sobre los índices creados en una tabla, usaremos la orden SQL SHOW INDEX.
  • Veamos un ejemplo:
  1. SHOW INDEX FROM employees.departments;
Obtendrías información sobre los índices creados en la tabla 'departments' de la base de datos 'employees' (la base de datos se podría omitir y buscaría la tabla en la base de datos activa).
Mysql indices 2.jpg


  • La salida indica lo siguiente:
  • Table: Nombre de la tabla que contiene el índice.
  • Non_unique: Indica si el índice es UNIQUE/PRIMARY o no (1 o 0) (recordar que todo índice PRIMARY es UNIQUE).
  • Key_name: Nombre del índice.
  • Seq_in_index: Indica el orden del índice dentro de un índice multicolumna.
En el ejemplo siguiente, está creado un índice sobre las columnas 'apellidos' y 'nombre' en ese orden:
Mysql indices 3.jpg
.
  • Column_name: Nombre de la columna a la que hace referencia el índice.
  • Collation: Forma en como está ordenado el índice. NULL indica sin ordenar y 'A' indica de forma ascendente.
  • Cardinality: Indica el número de valores diferentes que está almacenando el índice de forma aproximada.
  • Sub_part: En caso de que el índice no se cree sobre todo la columna, sino sobre una parte de su tamaño, indicaría el número de caracteres/bytes que conforman el índice:
Mysql indices 4.jpg
.
  • Packed: Indica si la columna está empaquetada. NULL indica que no lo está.
  • NULL: Indica si la columna admite valores NULOS.
  • Index_type: Estructura de datos del índice. Puede ser btree, hash, fulltext o rtree.



Optimizando los índices

  • Los índices no pueden ser actualizados y es necesario borrarlos y volverlos a crear.
Además, los índices generan ciertas estadísticas que son utilizadas por el optimizador de consultas para determinar cual es la mejor forma de obtener los resultados de una consulta.
  • Por estas dos razones, debemos de utilizar el comando OPTIMIZE TABLE.
Este comando va a desfragmentar la tabla y a actualizar y reordenar sus índices. Sólo se puede aplicar sobre motores INNODB, MYISAM y Archive.
En el caso del motor INNODB, lo que hace 'realmente' es crear una tabla copia de la tabla a optimizar, borra la original y renombra la nueva, haciendo después un ANALYZE TABLE. Más información en este enlace.


Por defecto. si está activo el registro binario de log, las operaciones realizadas por el comando son guardadas en dicho fichero. Si queremos que esto no ocurra, tendremos que llamar al comando con la opción 'LOCAL' o 'NO_WRITE_TO_BINLOG'. Esto es así para que en caso de un entorno de maestro-esclavos, los cambios sean replicados a los esclavos.


  • Por tanto, tendremos que ejecutar operaciones de optimización sobre tablas que se modifiquen habitualmente (operaciones de borrado, inserción y modificación sobre los datos asociados al índice).
  • Veamos un ejemplo:
  1. OPTIMIZE TABLE ALUMNOS;


  • Según esta documentación, en el caso de tener índices secundarios es mejor borrarlos, optimizar la tabla y volver a crearlos.


Ejercicios propuestos

Sobre la base de datos employees
  • Dada la siguiente consulta SQL:
  1. select *
  2. from salaries
  3. order by from_date;
Ejecuta dicha sentencia y comprueba el tiempo de ejecución.
Averigua cuales son los índices asociados a la tabla. Si ya existe un índice sobre la columna 'from_date' ¿ por qué no funciona adecuadamente ?
Crea el índice adecuado y comprueba cual es el tiempo ahora.
Muestra los índices creados en la tabla.
Borra 300.000 filas (haz una copia antes de los datos de la tabla. Vas a tener que poner la variable secure-file-priv="" dentro del archivo de configuración y reiniciar el servidor) y muestra la información sobre los índices asociados a la tabla. ¿ Qué diferencias encuentras?
Para borrar puedes utilizar esta orden SQL: DELETE FROM employees.salaries WHERE YEAR(from_date)<1990
Comprueba el tamaño de la tabla:
  1.      SELECT table_name AS `Table`,
  2.     round(((data_length + index_length) / 1024 / 1024), 2) `Size in MB`
  3.     FROM information_schema.TABLES
  4.     WHERE table_schema = "$DB_NAME" AND table_name = "$TABLE_NAME";
Realiza una optimización de la tabla y sus índices.
Recupera los registros borrados.


  • Crea en SQL una tabla en memoria que sea una copia de la tabla employees (botón derecho sobre la tabla, obtener la orden create y modificar el motor de almacenamiento):
  1. CREATE TABLE `employees_m` (
  2.   `emp_no` int(11) NOT NULL,
  3.   `birth_date` date NOT NULL,
  4.   `first_name` varchar(14) NOT NULL,
  5.   `last_name` varchar(16) NOT NULL,
  6.   `gender` enum('M','F') NOT NULL,
  7.   `hire_date` date NOT NULL,
  8.   PRIMARY KEY (`emp_no`)
  9. ) ENGINE=MEMORY;
Modifica la variable del sistema que controla el tamaño máximo de memoria por tabla para que quepan los datos de la tabla employees más los índices a crear (cuenta el número de registros y multiplica por el número de filas. Para la estructura del registro ten en cuenta que se guarda el campo más la clave primaria)
Pasa los datos de la tabla 'employees' a la tabla 'employees_m' con la orden SELECT campos FROM TABLA INTO NUEVA_TABLA
Comprueba el tiempo de ejecución de la orden SQL: SELECT * FROM employees.employees_m where first_name='Parto'
Crea el tipo de índice que mejor se ajuste al tipo de sentencia. Comprueba el nuevo tiempo de ejecución.
Crea el otro tipo de índice (borrando el anterior) y compara los tiempos de ejecución de la sentencia (mirar columna Fetch, el la parte baja, donde muestra el número de filas devueltas)




-- Ángel D. Fernández González -- (2017).