Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Hacer que una consulta trabaje es una cosa, pero obtener una consulta que trabaje lo
ms rpidamente es otra muy diferente. Podemos acelerar nuestras consultas de dos
maneras bsicamente, una de ellas es afinando nuestro servidor para que responda lo
mejor posible, y la otra, que es la que trataremos en este artculo, es haciendo uso de
los ndices de una manera inteligente. Los ndices son usados para encontrar
rpidamente los registros que tengan un determinado valor en alguna de sus
columnas. Sin un ndice, MySQL tiene que iniciar con el primer registro y leer a travs
de toda la tabla para encontrar los registros relevantes. An en tablas pequeas, de
unos 1000 registros, es por lo menos 100 veces ms rpido leer los datos usando un
ndice, que haciendo una lectura secuencial. Cuando MySQL trata de responder una
consulta, examina una variedad de estadsticas acerca de nuestros datos y decide
como buscar los datos que deseamos de la manera ms rpida. Sin embargo, como se
acaba de mencionar, cuando en una tabla no existen ndices en los cuales pueda
auxiliarse MySQL para resolver una consulta se tendrn que leer todos los registros de
la tabla de manera secuencial. Esto es comnmente llamado un "escaneo completo de
una tabla", y es muchas veces algo que se debe evitar. En particular, debemos evitar
las escaneos completos de tablas por las siguientes razones:
Sobrecarga de CPU. El proceso de checar cada uno de los registros en una tabla
es insignificante cuando se tienen pocos datos, pero puede convertirse en un
problema a medida que va aumentando la cantidad de registros en nuestra
tabla. Existe una relacin proporcional entre el nmero de registros que tiene
una tabla y la cantidad de tiempo que le toma a MySQL revisarla
completamente.
Concurrencia. Mientras MySQL est leyendo los datos de una tabla, ste la
bloquea, de tal manera que nadie ms puede escribir en ella, aunque si pueden
leerla. Cuando MySQL est actualizando o eliminando filas de una tabla, ste la
bloquea, y por lo tanto nadie puede al menos leerla.
Sobrecarga de disco. En una tabla muy grande, un escaneo completo consume
una gran cantidad de entrada/salida en el disco. Esto puede alentar
siginificativamente nuestro servidor de bases de datos, especialmente si
tenemos un disco IDE algo antiguo.
En resumen, lo mejor es tratar de que los escaneos completos de tablas sean mnimos
-- especialmente si nuestra aplicacin necesita escalabilidad en tamao, nmero de
usuarios, o ambos. Las versiones actuales de MySQL hacen distintas mejoras en cuanto
a concurrencia, pero ese tema est ms all de nuestra discusin. Es en estos casos
donde la indexacin puede ayudarnos. De manera simple, un ndice le permite a
MySQL determinar si un valor dado coincide con cualquier fila en una tabla. Cuando
indexamos una columna en particular, MySQL crea otra estructura de datos (un ndice)
que usa para almacenar informacin extra acerca de los valores en la columna
indexada. Los valores indexados son llamados frecuentemente claves. Aunque esta es
la manera simple de explicar los ndices, realmente es un poco ms complejo, ya que
1
MySQL almacena todas las claves del ndice en una estructura de datos de rbol. Esta
estructura de datos de rbol le permite a MySQL encontrar claves muy rpidamente.
Cuando MySQL encuentre que hay un ndice en una columna, lo usar en vez de hacer
un escaneo completo de la tabla. Esto reduce de manera imporante los tiempos de
CPU y las operaciones de entrada/salida en disco, a su vez que se mejora la
concurrencia porque MySQL bloquear la tabla nicamente para obtener las filas que
necesite (en base a lo que encontr en el ndice). Cuando tenemos grandes cantidades
de datos en nuestras tablas, la mejora en la obtencin de los datos puede ser muy
significativa.
Creacin de ndices
Existen cuatro tipos de ndices que podemos utilizar en MySQL; de clave primaria,
nicos, de texto completo, y ordinarios. Cada uno de ellos ser explicado a
continuacin.
ndices de clave primaria
Una clave primaria es un ndice sobre uno o ms campos donde cada valor es nico y
ninguno de los valores son NULL.
Para crear un ndice de clave primaria tenemos bsicamente dos opciones:
1. Crear el ndice de clave primaria al momento de crear la tabla. En este caso se usa la
opcin PRIMARY KEY al final de la definicin de los campos, con una lista de los campos
que sern parte del ndice.
CREATE TABLE nombreTabla(campo1 tipoDato,
[campo2...,] PRIMARY KEY (campo1 [,campo2...]) );
Hacemos nfasis en que la palabra clave NOT NULL es obligatoria para un campo
cuando ste vaya a formar parte de una clave primaria; como mencionamos
anteriormente, las claves primarias no pueden contener valores nulos. Si intentamos
crear una clave primaria sobre un campo nulo, MySQL nos marcar un error.
2. Crear una clave primaria en una tabla existente con el uso del comando ALTER
TABLE:
ALTER TABLE usuarios MODIFY id INT NOT NULL, ADD PRIMARY KEY(id);
Para observar los cambios que hemos hecho, podemos examinar las columnas de la
tabla usuarios con una sentencia DESCRIBE:
mysql> DESCRIBE usuarios;
+-----------+-------------+------+-----+---------+-------+
| Field | Type
| Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id
| int(11) | | PRI | 0
|
|
| nombre | varchar(50) | YES | | NULL |
|
| apellidos | varchar(70) | YES | | NULL |
|
+-----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
El campo id no tiene un valor YES en la columna Null, lo que indica que este campo ya
no podr almacenar valores nulos. Se puede observar tambin que se tiene un valor
PRI en la columna Key, lo que indica que este campo es una clave primaria.
Las claves primarias pueden constar de ms de un campo. Hay algunas veces en las
que un solo campo no puede identificar de manera nica a un registro.
ndices ordinarios
Un ndice que no es primario permite valores duplicados (a menos que los campos
hayan sido especificados como UNIQUE).
Para crear un ndice ordinario tenemos bsicamente dos opciones:
1. Podemos crear un ndice ordinario al mismo tiempo que creamos la tabla con el uso
de la opcin INDEX.
Ambas sentencias piden el nombre del ndice, sin embargo con la sentencia CREATE
INDEX el nombre es obligatorio.
Por ejemplo, para la siguiente definicin de tabla:
La siguiente sentencia tambin se puede usar para crear un ndice cuando la tabla ya
existe.
CREATE FULLTEXT INDEX nombreIndice ON nombreTabla(campo1 [,campo2,...]);
- Nota: Cuando se tienen grandes cantidades de datos, es mucho ms rpido cargar los
datos en una tabla que no tiene ndices de texto completo y despus crear los ndices
necesarios, ya que la carga de datos en una tabla que ya tiene ndices de este tipo es
un proceso lento.
ndices nicos
Los ndices nicos son bsicamente como los ndices ordinarios, excepto que los
valores duplicados no son permitidos.
Para crear un ndice UNIQUE se tienen bsicamente dos opciones:
1. Crear un ndice nico cuando la tabla es creada con el uso de la opcin UNIQUE.
CREATE TABLE nombreTabla(campo1 tipoDato, campo2 tipoDato,..
UNIQUE [nombreIndice] (campo1 [,campo2...]));
2. O bien, si la tabla ya existe, se usa la sentencia ALTER TABLE.
ALTER TABLE nombreTabla ADD UNIQUE [nombreIndice] (campo1, campo2) ...
De igual manera, tambin es posible usar la sentencia CREATE INDEX para crear un
ndice nico en una tabla existente.
CREATE UNIQUE INDEX nombreIndice ON nombreTabla(campo1 [,campo2...]);
ndices compuestos
Los ndices compuestos son simplemente aquellos que estn basados en mltiples
columnas. MySQL nicamente usa un ndice por tabla cuando est procesando una
consulta. Esto significa que si tenemos varias columnas que frecuentemente aparecen
juntas en una clusula WHERE, tenemos la oportunidad de acelerar estas consultas al
crear un ndice compuesto.
Si una tabla tiene un ndice formado por mltiples columnas, cualquier prefijo ms a la
izquierda puede ser usado por el optimizador de consultas de MySQL para encontrar
las filas. Por ejemplo, si tenemos un ndice compuesto por tres columnas (col1, col2,
col3), tendramos capacidades de bsqueda en (col1), (col1, col2) y (col1, col2, col3).
MySQL no puede usar un ndice parcial si las columnas no forman un prefijo ms a la
izquierda del ndice. Supongamos que tenemos unas sentencias SELECT como estas:
mysql> SELECT * FROM algunaTabla WHERE col1=valor1;
mysql> SELECT * FROM algunaTabla WHERE col2=valor2;
mysql> SELECT * FROM algunaTabla WHERE col2=valor2 AND col3=valor3;
Si el ndice existe en (col1, col2, col3), slo la primera de estas consultas usar el
ndice. La segunda y la tercera involucran a las columnas en el ndice, pero (col2) y
(col2, col3) no son los prefijos ms a la izquierda de (col1, col2, col3).
Este es otro ejemplo. Consideremos la siguiente definicin de una tabla:
CREATE TABLE usuarios(id int, nombre varchar(50), apellidos varchar(70));
Si frecuentemente hacemos consultas en la tabla usuarios basadas en el nombre como
en los apellidos, podemos beneficiarnos de un ndice compuesto en las columnas
nombre y apellidos.
ALTER TABLE usuarios ADD INDEX idx_nombre(nombre, apellidos);
Debido a la forma en que MySQL construye los ndices compuestos, ste puede usar el
ndice idx_nombre para resolver consultas basadas slo en el nombre, o en el nombre
y los apellidos, sin embargo, no usar el ndice en una consulta que haga referencia
nicamente a la columna apellidos.
Por ejemplo, de las siguientes tres consultas, slo las dos primeras haran uso de
nuestro ndice idx_nombre.
SELECT * FROM usuarios WHERE nombre='Eduardo';
SELECT * FROM usuarios WHERE nombre='Eduardo' AND apellidos='Zarate M';
SELECT * FROM usuarios WHERE apellidos='Zarate M';
6
La idea es que los ndices compuestos pueden usarse frecuentemente para acelerar
algunas consultas complejas, pero necesitamos entender sus limitaciones y debemos
ejecutar algn tipo de prueba en vez de asumir que estos ndices siempre nos van a
ayudar.
ndices de parte de campos
En las columnas CHAR y VARCHAR se nos permite crear un ndice que no use el campo
por completo. Retomemos el ejemplo anterior de la tabla usuarios. A pesar de que el
nombre de una persona puede ser de hasta 50 caracteres, es muy comn que los
nombres de las personas sean diferentes en los primeros 10 caracteres. Al usar un
ndice de 10 caracteres en lugar de 50, el ndice ser ms pequeo, y permitir que las
consultas INSERT y UPDATE sean ms rpidas, a la vez que no se afecta la velocidad de
las consultas SELECT.
Para crear un ndice como parte de un campo, slo se tiene que especificar el tamao
entre parntesis despus del nombre de la columna. Por ejemplo, nuestro ndice
idx_nombre pudo haber sido creado tambin de la siguiente manera:
ALTER TABLE usuarios ADD INDEX idx_nombre(nombre(10), apellidos(20));
Procesamiento de consultas
Las reglas que usa MySQL para decidir como obtener los datos de una consulta pueden
llegar a ser difciles de entender si dichas consultas son algo complejas.
Afortunadamente hay unas pocas reglas y un comando que nos permiten tener un
mejor entendimiento de qu es lo que est haciendo MySQL. Primero vamos a
comentar las reglas:
Habiendo dicho esto, es importante tener en cuenta que algunas de las desiciones que
toma MySQL estn basadas en suposiciones, y al igual que nosotros los humanos que
hacemos muchas suposiciones, puede que MySQL ocasionalmente haga alguna que
sea incorrecta.
Si sospechamos que esto ha sucedido, o simplemente queremos entender qu es lo
que est haciendo MySQL para procesar una consulta, podemos usar el comando
EXPLAIN. La seccin a continuacin explica ms a detalle el uso de EXPLAIN.
Analizando como se usan los ndices
EXPLAIN muestra (explica) como son procesadas las sentencias SELECT por MySQL,
como se usan los ndices, y como se unen las tablas. Utilizar EXPLAIN puede ayudarnos
a seleccionar mejores ndices y escribir nuestras consultas ms ptimamente. Lo nico
que tenemos que hacer es agregar la palabra EXPLAIN al inicio de la consulta para que
MySQL nos diga como la est ejecutando. En vez de ejecutar la consulta, MySQL
reportar la lista de ndices que se podran usar en la consulta y lo que conoce acerca
de ellos.
EXPLAIN SELECT nombre, apellidos FROM usuarios WHERE id = 1;
A continuacin explicaremos que significa cada una de estas columnas.
ALTER TABLE nombreTabla DROP PRIMARY KEY;
Para eliminar un ndice ordinario, nico, o de texto completo, necesitamos especificar
el nombre del ndice y usar esta sintaxis:
ALTER TABLE nombreTabla DROP INDEX nombreIndice;
8
Descripcin
table
type
10
const
eq_ref
ref
range
index
ALL
possible_keys Los posibles ndices que pueden aplicar a la tabla. Si est vaca
esta celda, no hay posibles ndices a utilizar.
key
key_len
ref
rows
extra
Not exists
range
checked
for each
record
(index
map: #)
Using
filesort
Using
index
Using
Cuando veamos esto, la consulta necesita ser
temporary optimizada. Para resolver la consulta MySQL
necesita crear una tabla temporal para
mantener el resultado. Esto sucede tpicamente
cuando se hace un ORDER BY sobre un conjunto
de columnas diferente al usado en un GROUP
BY.
Where
used
12
Podemos obtener una buena indicacin de que tan buena es una consulta al
multiplicar todos los valores de la columna rows en la salida de EXPLAIN. Esto nos dice
aproximadamente cuntas filas debe examinar MySQL para ejecutar una consulta. De
lo que se trata es que podamos ir mejorando una consulta progresivamente usando la
informacin proporcionada por EXPLAIN.
Cmo evitar escaneos completos de tablas
La salida de EXPLAIN mostrar ALL en la columna type cuando MySQL hace un escaneo
de tabla para resolver una consulta. Esto sucede usualmente bajo las siguiente
condiciones:
Esto no toma en consideracin la sobrecarga inicial de abrir las tablas, lo cual es hecho
una vez por cada consulta en ejecucin de manera concurrente.
El tamao de la tabla hace ms lenta la insercin de los ndices por log N, asumiendo
que los ndices son rboles B.
Por lo tanto, podemos usar los siguientes mtodos para lograr que los INSERTs se
ejecuten ms rpido:
o
o
17