Pildoritas de Bases de Datos: Optimización. Condiciones SARGABLE

Después de la pildorita de la semana pasada sobre cuestiones de optimización de operadores, aprovecho que tenéis el tema fresco y sobre todo que escribí ambos posts de forma casi simultanea, para desentrañar por fin que es eso de SARGABLE xD

SARGABLE viene del acrónico SARG (Search ARGument) y se refiere a una clausula WHERE que compara una columna con una constante. ¿Y eso que significa? Pues que un WHERE es SARGABLE cuando se puede apoyar en índices para mejorar la respuesta. Los WHERE NON-SARGABLE, en cambio, son aquellos que no pueden ayudarse de índices a la hora de realizar el filtrado de datos. Ahora ya entendeis por qué decía en el tema anterior que había que evitar los operadores NON-SARGABLE.

Como vimos ya por encima, hay algunos operadores que pueden impedir (aunque no siempre) la utilización de índices en un WHERE. Los operadores en cuestión son: “IS NULL”, “<>”, “!=”, “!>”, “!<” (estados dos en caso de que existan para ese motor, ya que no son comunes), “NOT”, “NOT EXIST” , “NOT IN”, “NOT LIKE”. Básicamente todas las que usan NOT)

Además, las expresiones que incluyen una función sobre una columna, comparaciones contra una columna – no es una constante – u operadores con la misma columna en ambos lados, son también NON-SARGABLE.

No obstante, a veces que haya alguna condición con estos operadores no significa necesariamente que el WHERE sea SARGABLE, porque otra condición puede hacer que la consulta utilice un índice que evite el TABLE/INDEX SCAN (es lo que ocurre con WHERE NON-SARGABLE) y realice un COVERED INDEX sin necesidad de utilizar el índice. Esto ocurre cuando el índice incluye los campos devueltos por el SELECT y los del JOIN. Por otra parte, un WHERE puede también estar compuesto por otra clausula que tire de otro índice.

Además, no siempre viene bien hacer un COVERED INDEX, ya que cuando los índices son muy extensos puede aumentar mucho la utilización de CPU y bus de Entrada / Salida, que puede resultar en algunos casos más perjudicial incluso que una query que no utilice índices.

Al igual que en el resto de pildoritas de optimización, vamos a ver una serie de pautas que pueden llevar a mejoras de rendimiento en las consultas.

Pautas para convertir condiciones NON-SARGABLE a SARGABLE.

No utilizar funciones sobre columnas

Como decíamos en la introducción, ésta es una de las causas más comunes de que una condición sea NON-SARGABLE, y muchas veces es perfectamente reescribible a una condición que no se apoye en la columna, sino en una constante (literal, parámetro).

Imaginad que queremos obtener aquellos empleados cuyo Apellido comience por la M. La query sería tal que así:

SELECT NOMBRE, APELLIDO FROM EMPLEADO WHERE SUBSTRING(APELLIDO, 1, 1) = ‘M’

Esta query utiliza sobre la columna APELLIDOS la función SUBSTR, por lo que no es SARGABLE, pero con un pequeño cambio de operador podemos tener el mismo resultado y una query SARGABLE.

SELECT NOMBRE, APELLIDO FROM EMPLEADO WHERE APELLIDO LIKE ‘M%’

Como veis, utilizando el LIKE dejamos de utilizar una función sobre la columna por lo que el WHERE pasa a ser SARGABLE. Más adelante entraremos en consideraciones particulares del operador LIKE, ya que dependiendo de algunos factores de la cadena a comparar, este puede ser NON-SARGABLE.

Otro ejemplo podría ser el de una query que buscara los proyectos que empiezan en menos de un mes de una fecha introducida por parámetro.

SELECT ID_PROYECTO FROM PROYECTO 
WHERE DATEDIFF(FECHA_INICIO , :FECHA_PARAMETRO) < 30

Esta query no es SARGABLE porque se está aplicando una función a una columna. Ahora bien, si pensamos un poco podemos sacar una query equivalente, eso sí, utilizando otro operador.

SELECT ID_PROYECTO FROM PROYECTO 
WHERE FECHA_INICIO > DATE_ADD(:FECHA_PARAMETRO , INTERVAL 30 DAYS)

Operador NOT

El operador NOT siempre es NON-SARGABLE, pero de igual que en el caso anterior, se pueden encontrar formas diferentes de escribir una consulta sin necesidad de usarlo.

Por ejemplo, vamos, a imaginar que nuestra tabla Empleado tiene un campo SALARIO, donde se indica la cantidad de dinero que cobra el trabajador bruto al año.

Si quisiéramos obtener aquellos trabajadores que no cobran más de 20.000€ brutos / años, nuestra primera idea podría ser ésta:

SELECT NOMBRE, APELLIDOS FROM EMPLEADO WHERE NOT SALARIO > 20.000

*También podríamos haber usado el operador !>, que funciona exactamente igual, pero este operador es muy poco común en la mayoría de motores.

Una forma muy sencillita de variar esta consulta y no utilizar el operador NOT es la siguiente:

SELECT NOMBRE, APELLIDOS FROM EMPLEADO WHERE SALARIO <= 20.000

Utilización de índices por otras vías

Pese a estos consejos, muchas veces no podemos deshacernos de las clausulas NON-SARGABLE. En estos casos se pueden definir índices computados, es decir, índices que son resultado de aplicar una función.

Por ejemplo, podríamos definir un índice para las iniciales de apellidos que fuera IDX(SUBSTRING(APELLIDO, 1, 1))

Esta solución sólo debe utilizarse cuando la query se lance constantemente y no haya forma de intentar quitar la función de la propia columna, ya que los índices computados requieren una sobrecarga de la BBDD importante.


Y ya con esto ponemos punto y final a la idea inicial de optimización que os comenté en este post, pero que de nuevo he vuelto a cambiar.

Al hablaros en este tema de cuestiones como TABLE SCAN o COVERED INDEX, he creído conveniente hacer una pildorita más explicando de forma muy básica como lanzar un explain plan e interpretarlo.

Así que nada, todavía queda una pildorita más de BBDD, que odio esto de las despedidas xD

Índice de Pildoritas

Share