Академический Документы
Профессиональный Документы
Культура Документы
NOLOCK
No hay mucho que decir de ste mtodo (cualquier persona con ms de 1 mes en
SQL lo debera conocer), pero se me hace importante mencionar que se tiene
planificado deprecar (vase como "ya no usar") ste mtodo en futuras versiones
de SQL por lo que se debera dejar de usar en nuevos desarrollos y considerar el
ir migrando los desarrollos ya realizados.
Recomendara usar WITH (REPEATABLEREAD) o alguna de sus derivaciones
[http://msdn.microsoft.com/es-es/library/ms173763.aspx]
SQL Dinmico
Citando:
Procedimientos Almacenados
Datos Temporales
Cuando los datos que se desea almacenar no son muchos, se debe preferir utilizar
variables de tipo tabla, pues estas mantienen los datos en memoria evitando tener
que ir a disco.
Si se necesita consultar repetitivamente los mismos datos es necesario crear
ndices temporales sobre los campos de la tabla temporal, esto no es posible con
las otras alternativas de almacenamiento temporal por lo que se recomienda
utilizar tablas temporales para este caso.
Evale reemplazar las tablas derivadas por JOINs.
Selects
Where
Las columnas filtro TIENEN QUE SER del mismo tipo de la columna que existe
en la tabla (para evitar conversiones al momento de ejecucin)
No usar funciones sobre columnas que estn en el Where dado que SQL no
tiene ndices basados en funciones por lo que tendra que recorrer toda la tabla.
No usar concatenaciones de cadenas
Si se usa LIKE en la clusula WHERE, se debe evitar el uso del operador "%" al
principio de la cadena a buscar dado que originaria que se tienen que leer todos
los datos de la tabla para poder responder dicha consulta, adicionalmente es
recomendable que existan (como mnimo) 3 caracteres antes del operador "%".
Dentro de lo que sea posible, usar BETWEEN en lugar de IN.
Si una consulta tiene uno o ms operadores OR, considera reescribir la consulta
en varias consultas que se unen usando el operador UNION ALL.
SELECT TOP 10
DB_NAME(qt.dbid) 'Base de Datos',
OBJECT_NAME(qt.objectid,qt.dbid)AS 'Nombre Objeto',
SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(qt.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1) AS 'Texto',
qs.execution_count AS 'Veces ejecutado',
qs.total_logical_reads AS 'Total lecturas lgicas',
qs.last_logical_reads AS 'Lecturas lgicas del ltimo proceso',
qs.total_logical_writes AS 'Total escrituras lgicas',
qs.last_logical_writes AS 'Escrituras lgicas del ltimo proces',
qs.total_worker_time AS 'Total tiempo CPU',
qs.last_worker_time AS 'Tiempo CPU del ltimo proceso',
qs.min_worker_time AS 'Minimo tiempo CPU',
qs.max_worker_time AS 'Maximo tiempo CPU',
qs.total_elapsed_time/1000000 AS 'Total tiempo (en seg)',
qs.last_elapsed_time/1000000 AS 'Tiempo del ltimo proceso (en seg)',
qs.min_elapsed_time/1000000 AS 'Tiempo mnimo (en seg)',
qs.max_elapsed_time/1000000 AS 'Tiempo mximo (en seg)',
qs.last_execution_time AS 'Ultima vez que se ejecut',
qp.query_plan AS 'Plan de ejecucin'
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
--WHERE DB_NAME(qt.dbid) = 'NOMBRE_DE_BD'
ORDER BY qs.total_elapsed_time DESC
Siempre pueden listar ms items (quitando el TOP 10), ordenar el resultado por
otro factor (cambiando el ORDER BY) o restringiendo la bsqueda a una base de
datos especfica (descomentando la penltima lnea y cambiando
NOMBRE_DE_BD por el nombre de su Base de Datos)
Probar procedures en SQL
Hace algn tiempo me dijeron "tienes que optimizar este procedure", as que lo
analic, hice algunas mejoras, y en la primera corrida del procedure, demor
aproximadamente 3 veces ms de lo que demoraba el procedure original, pens
que haba un error dado a que haba aplicado (segn yo) las "buenas prcticas",
as que lo ejecut denuvo y demor la mitad del procedure original.
Buscando un poco con internet, di con un mtodo que me ayudara un poco con
esto de los tiempos (dejando de lado el usar el analizador de querys obviamente) y
es el hecho de simplemente limpiar el cach y el buffer antes de ejecutar el
procedure y esto se hace con los siguientes mtodos
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE
OBJETIVO
Seleccionar solo aquellos campos que se necesiten, cada campo extra genera
tiempo extra.
Dado lo anterior, para realizar las relaciones entre las tablas, se deben utilizar las
instrucciones:
INNER JOIN
LEFT JOIN
RIGHT JOIN
CROSS JOIN
Si se utiliza varias tablas en la consulta, hay que especificar siempre a que tabla
pertenece cada campo. Esto ahorra al gestor el tiempo de localizar a que tabla
pertenece el campo.
Ejemplo
En lugar de:
Usar:
Utilizar lo menos posible las clusulas ANY, SOME, IN (SELECT), NOT, IS NULL, !
= , <>, !>, !<,
NOT EXISTS, NOT IN, NOT LIKE, LIKE %ab.
No utilizar tablas temporales pblicas. En el caso que se utilicen tablas temporales
locales en algn procedimiento, estas siempre se deben eliminar al terminar de
utilizarse en el procedimiento en cuestin, de igual modo antes de crear alguna
tabla temporal local, siempre se debe ver la opcin de utilizar variables tipo tabla
como prioridad.
Ejemplo
Evitar usar la instruccin UNION, a menos que este eliminado filas duplicadas.
No Utilizar WITH NOLOCK o WITH ROWLOCK porque esta sentencia puede leer
la tabla aun cuando tenga transacciones pendientes (update, delete e insert), por
ende podra mostrarse y leerse informacin que puede no sea real.
Ejemplo
BASE.. TABLA
Reemplazar por
BASE.dbo.TABLA
Indentar u ordenar el cdigo fuente del programa para mejorar la legibilidad por
parte de los programadores.
CURSORES
Evitar utilizar Cursores sin antes ver la posibilidad de que la misma operacin se
realice por medio de sentencia SQL.
Utilizar el tipo de datos Table en vez de cursores, pero solo cuando estas tengan
pocos datos.
Ejemplo
Ejemplo
Este mtodo es mucho ms rpido ya que el primero cuenta todas las filas para
poder traer un resultado, mientras que la segunda opcin busca de inmediato si
existe o no alguna fila para as seguir efectuando la consulta formulada.
Esta entrada es una recopilacin de muchos aos, en los cuales muchas personas
me brindaron tips respecto a cmo implementar consultas en RDBMS con alto
rendimiento.
Hace poco algunos compaeros de trabajo me solicitaron que compartiera este
conjunto de prcticas con el fin de que fueran evolucionando; Muchos pueden
aportar.
Si bien no estn todos los posibles tips en afinamiento, si hay un conjunto bsico
de prcticas a seguir, que espero les sea til.
Posdata: si tienen otra prctica a incluir, por favor dejen el comentario para
incluirla.
Problema / Contexto
Solucin
Justificacin
Optimizar el producto cartesiano de las tablas a utilizar en una consulta.
Prefiere los JOINS en todas sus variedades (Left, right, inner,etc.) en el FROM
antes que en el WHERE.
1). Siempre lo primero que hace un motor de BD es ejecutar la sentencia FROM
realizando un producto cartesiano.
2). Luego, ejecuta la clausula where.
3). Calcula las filas del SELECT.
4). Si existe un select distinc, ordena y luego elimina las filas duplicadas.
5). Si existe el order by, ordena las filas.
La TABLA1 debe tener menos registros que la TABLA2 y esta menos que la
TABLA3. Lo anterior porque recuerda que en el FROM siempre se realiza un
producto cartesiano (revisar algebra y clculo relacional)
Se aplica la teora de conjuntos, en dnde la tabla con menos registros intercepta
a la que tiene ms registros, y por tal motivo si se enlaza una tercera se tendr un
conjunto a interceptar ms pequeo, agilizando la consulta pues se manejan
menos datos.
Nunca uses sentencias LIKE al iniciar un where.
Es importante asegurarse que ya se han filtrado los datos para que el conjunto de
datos objetivo sea ms pequeo y por lo tanto manejable en el caso en que se
requiera el uso de LIKE. Es importante recordar que la sentencia LIKE implica que
no se usen ndices.
Las sentencias LIKE no utlizan ndices. Evita el LIKE "%AS%", revisa si lo que
requieres es un LIKE "%AS" o un LIKE "AS%"
Nunca use DISTINCT
Use EXISTS. Por ejemplo: Escriba la consulta como
En lugar de:
1. No usar Select *. Siempre que se utiliza Select * todas las columnas en la tabla
o unin se incluyen en el conjunto de resultados, as que el incluir todas las
columnas aunque no sean necesarias provoca un exceso de entradas/salidas en
el servidor y un consumo innecesario del ancho de banda de la red.
4. Usar la clusula Join con estndar ANSI. Para unir tablas es mejor usar la
clusula Join que hacer una unin por medio de la clusula Where. A pesar de que
a partir de SQL Server 7.0 las uniones de tablas usando Where pueden ser
traducidas por el plan de ejecucin a uniones explcitas, el hecho es que el
compilador es quien hace esa conversin, lo cual le toma tiempo y recursos.
8. Usar tablas derivadas siempre que sea posible. Las tablas derivadas tienen un
mejor desempeo. Considerando la siguiente consulta para encontrar el segundo
salario mas alto de la tabla de Empleados:
SELECT MIN(Salary) FROM Employees WHERE EmpID IN ( SELECT TOP 2
EmpID FROM Employees ORDER BY Salary DESC )
La misma consulta puede ser re-escrita usando una tabla derivada, como se
muestra a continuacin, y ser el doble de rpida que la consulta anterior:
11. Tratar de no usar tipos de datos TEXT o NTEXT para almacenar datos
textuales grandes. El tipo de datos TEXT tiene ciertos problemas inherentes a l.
Por ejemplo, no se puede grabar o actualizar datos de texto usando las
instrucciones INSERT o UPDATE. En vez de eso, es necesario usar declaraciones
especiales como READTEXT, WRITETEXT y UPDATETEXT.
Tambin existen muchos errores asociados con la replicacin de tablas que
contienen columnas de tipo TEXT. Por eso, si no se necesita almacenar ms de 8
KB de texto, es preferible usar los tipos de datos CHAR (8000) o VARCHAR
(8000).
13. Usar el tipo de datos CHAR para una columna solamente cuando no pueda
contener valores nulos. Si una columna CHAR puede contener valores nulos, es
tratada como una columna de ancho fijo en SQL Server 7.0+. As que un CHAR
(100) cuando sea nulo ocupara 100 bytes, resultando en un desperdicio de
espacio. Para esta situacin es mejor usar VARCHAR(100). Ciertamente las
columnas de ancho variable tienen un poco ms de overhead de procesamiento
en comparacin con lascolumnas de ancho fijo. Se debe escoger con cuidado
entre CHAR y VARCHAR dependiendo del ancho de los datos que se van a
almacenar.
@variable_1 INT,
@variable_2 VARCHAR(10),
@variable_n BIT
AS
BEGIN
--INSTRUCCIN 1
--INSTRUCCIN 2
--INSTRUCCIN N
END
Aunque resulte fcil y cmodo usar el comodn (*) para traer todos los campos, este debe
omitirse y en su lugar especificarse los campos que sean necesario traerse. El uso del
comodn impide adems un uso efectivo de forma eficiente de los ndices. En caso de
que sean todos como el uso del * establece, especifique cada uno de ellos, un simple
ALT+F1 sobre el nombre de la tabla seleccionada traer su estructura, por lo que copiar,
pegar los campos y aadir una coma supone un esfuerzo mnimo.
Me he topado en varias ocasiones que al hacer un chequeo de existencia se recurre por
lo regular a la siguiente sintaxis:
BEGIN
--INSTRUCCIONES 1,2N
END
Evtelo, no existe ningn motivo para cargar ms trabajo a la base de datos trayendo
todos los campos si lo nico que se desea saber es si existe. Puede sustituirse sin ningn
problema el * por o 1 cumpliendo con su funcin de chequeo de existencia sin necesidad
de traer datos:
BEGIN
--INSTRUCCIONES 1,2N
END
O bien:
BEGIN
--INSTRUCCIONES 1,2N
END
SET NOCOUNT ON
SELECT
Campo1,
Campo2,
CampoN
WHERE
<Condicionantes>
Este detalle en particular apoya en la forma que SQL Server usa para localizar el
procedimiento almacenado cuando intentamos ejecutarlo, asumiendo que se trata de un
procedimiento almacenado de sistema, y por lo tanto deber estar en la base de
datosMASTER, donde se ubican todos los Procedimientos Almacenados de esta clase.
Intentando primeramente localizar el procedimiento en la base de datos MASTER. Al no
encontrarlo continua su bsqueda la base de datos activa, provocando con esto una cada
del rendimiento que, aunque parezca insignificante en un ambiente de transacciones
pequeo podra influir significantemente si estamos hablando de un ambiente mucho ms
grande en donde se ejecuten miles de transacciones por minuto.
Los cursores son una herramienta usada para acceder y modificar el resultado de una
clusula SELECT fila por fila. El problema con su uso es que consumen una enorme
cantidad de recursos, especialmente de memoria. Siempre que sea posible, se debe
omitir el uso de cursores o minimizar su implementacin. Algunas alternativas y sustitutos
al uso de cursores pueden ser:
Siempre es mejor usar variables tipo tabla en lugar de tablas temporales?, revisemos las
caractersticas y diferencias entre una y otra.
Variables tipo tabla:
Su uso en procedimientos almacenados provoca menos re compilaciones.
Apuntan a estructuras de memoria por lo que producen menos consumo de
recursos que las tablas temporales.
Su contenido no siempre est en memoria. En caso de que se inserte una
cantidad grande de registros esta se almacena en TEMPDB.
El contenido de la variable no es afectado por el comando ROLLBACK.
No se pueden generar al vuelo.
No usan paralelismo (multiple threads) en su plan de ejecucin. Su uso para tablas
de concurrencia alta o con una gran cantidad de datos puede afectar su desempeo,
siendo este menor en comparacin con una tabla temporal.
No se les puede agregar ndices.
No se les pueden modificar ni truncar una vez creadas.
Tablas temporales:
En general, se puede sugerir el uso de variables tipo tabla siempre que sea posible en
lugar de tablas temporales. Use estas ltimas solo en caso de que se maneje una
cantidad muy grande de informacin y siempre procurando crear su estructura
previamente nunca crendolas al vuelo.
Aunque en general el uso de SQL Dinmico esta algo condenado debido a que una mala
implementacin puede resultar en un grieta de seguridad que de entrada a un severo
caso de SQL Injection(http://www.websec.mx/blog/ver/Referencia_para_Inyeccion_SQL).
La implementacin que se sugiere esta encapsulada dentro de un procedimiento
almacenado, no es del todo dinmica para la aplicacin cliente, es decir, no le permite
estructurar sentencias libres y est orientada ms que nada a procedimientos
almacenados cuya funcin principal es una consulta parametrizada con opciones
variables. Usare la base de datos AdventureWorksDW2008R2 para ilustrar un ejemplo.
Probaremos con el siguiente procedimiento almacenado:
@ProductKey INT=NULL,
@SalesOrderLineNumber TINYINT=NULL,
@OrderQuantity SMALLINT=NULL,
@CurrencyKey INT=NULL
AS
BEGIN
SET NOCOUNT ON
SELECT
FIS.ProductKey,
FIS.OrderQuantity,
FIS.UnitPrice,
FIS.SalesOrderNumber,
FIS.CurrencyKey,
FIS.SalesOrderLineNumber
END
En este caso la consulta acepta valores nulos y se usa ISNULL (se puede
usar COALESCE en su lugar, pero para razones prcticas en el ejemplo no tiene mucho
caso y ISNULL es un poco ms rpido que COALESCE) para dar la flexibilidad al
procedimiento almacenado y poder consultar ya sea usando uno, los cuatro o cualquier
combinacin de parmetros. Ahora, convirtiendo la consulta en SQL Dinmico, quedara
de la siguiente forma:
CREATE PROCEDURE AfterRedCodeSELECT
@ProductKey INT=NULL,
@SalesOrderLineNumber TINYINT=NULL,
@OrderQuantity SMALLINT=NULL,
@CurrencyKey INT=NULL
AS
BEGIN
SET NOCOUNT ON
SET @Query=N'
SELECT
FIS.ProductKey,
FIS.OrderQuantity,
FIS.UnitPrice,
FIS.SalesOrderNumber,
FIS.CurrencyKey,
FIS.SalesOrderLineNumber
BEGIN
END
IF @SalesOrderLineNumber IS NOT NULL
BEGIN
END
BEGIN
END
BEGIN
END
--PRINT @Query
, @ProductKey = @ProductKey
, @SalesOrderLineNumber = @SalesOrderLineNumber
, @OrderQuantity = @OrderQuantity
, @CurrencyKey = @CurrencyKey
END
Aqu el procedimiento almacenado solo incluir las condiciones que sean necesarias y
que no tengan un valor nulo. Ambos procedimientos devuelven los mismos resultados y
son equivalentes el uno al otro. Usando SQL Profiler, podemos apreciar la diferencia en
la optimizacin de recursos:
Las lecturas se reducen para este escenario particular, en poco ms de un 80%, la
duracin en un 60% y en el uso del CPU, un cambio total.
Por lo que se aprecia el SQL Dinmico podra no ser tan malo como parece y puede
ofrecernos alternativas creativas para optimizar nuestras consultas. Evalu sus consultas
complejas y pesadas para sopesar si una refactorizacin usando SQL Dinmico puede
ayudarle, cuidando siempre que esta no se preste para casos de SQL Injection.
Es ya bien conocido el uso de ndices para acelerar consultas en SQL Server y otros
motores de bases de datos. Estos le indican al motor de la base de datos en turno, que
esa columna o conjunto de columnas se van a usar con mayor frecuencia, lo cual hace
que los datos se almacenen en memoria para contar con un acceso ms eficiente a los
datos.
Pese a que los ndices generen una mejora en tiempos de respuesta, tampoco se puede
abusar indiscriminadamente de ellos. En contraposicin a su mejora de tiempo de
respuesta cada ndice ocupa un espacio equivalente al nmero de registros en la tabla,
penalizando el rendimiento en la base de datos al
ejecutar INSERT, UPDATES y DELETE.
Omita crear ndices con campos que contienen pocos valores, como aquellos que son de
tipo BIT o CHAR (de uno a cuatro) y asegrese de no agregar el mismo ndice con
distintos nombres.
El estudio profundo de la creacin, diseo y mantenimiento de ndices escapa del alcance
de este articulo (espero escribir algo ms elaborado sobre el tema de ndices en un
futuro), sin embargo es importante que se tome el tiempo para comprenderlos. Su
correcta aplicacin puede favorecer por mucho el tiempo de respuesta de sus consultas.
RECOMENDACIONES ADICIONALES
RESUMEN
Los puntos presentados en este artculo sirven como esquema general para la
optimizacin de consultas y procedimientos almacenados, pero, cabe aclarar que la
optimizacin es ms bien un asunto de entornos particulares. Quiz lo ideal sera
establecer estas recomendaciones desde el inicio de un desarrollo, si el desarrollo ya est
en produccin lo ms coherente es evaluar qu puntos pueden o no aplicarse. Menciono
esto ltimo por escenarios en los que, por ejemplo, ya se efectu todo el desarrollo de X
aplicacin; Algunos programadores usan el retorno del conteo de las filas afectadas para
validar que el procedimiento almacenado efectivamente se ejecut y en base a eso la
aplicacin sigue o no adelante (esto no debera ser as, pero pasa con mayor frecuencia
de la que uno espera). En este escenario, aplicar SET NOCOUNT ON podra generar
muchos errores, en especial si la aplicacin es grande y compleja y la cantidad de
procedimientos almacenados a los que se les debe aplicar tambin es alta. El uso
indiscriminado de NOLOCK en las tablas puede crear tambin problemas, sobre todo si la
aplicacin debe mostrar informacin consistente y se aplica esta prctica a
procedimientos que deben de respetar ciertas transacciones antes de efectuar cambios.
En conclusin, la optimizacin es un trabajo ms que nada artesanal y depende en
muchos casos de en qu punto (inicio, mitad o fase final del desarrollo) decidamos
aplicarla.