sql >> Base de Datos >  >> RDS >> Database

Seguimiento del Summer Performance Palooza 2013

El 27 de junio, PASS Performance Virtual Chapter celebró su Summer Performance Palooza 2013, una especie de 24 horas de PASS reducidas, pero centradas únicamente en temas relacionados con el rendimiento. Di una sesión titulada "10 malos hábitos que pueden matar el rendimiento", tratando los siguientes 10 conceptos:

  1. SELECCIONAR *
  2. Índices ciegos
  3. Sin prefijo de esquema
  4. Opciones de cursor predeterminadas
  5. prefijo sp_
  6. Permitir la sobrecarga de caché
  7. Tipos de datos amplios
  8. Valores predeterminados de SQL Server
  9. Uso excesivo de funciones
  10. "Funciona en mi máquina"

Es posible que recuerde algunos de estos temas de presentaciones tales como mi charla "Malos hábitos y mejores prácticas" o nuestros seminarios web semanales de ajuste de consultas que he estado organizando con Kevin Kline desde principios de junio hasta esta semana. (Por cierto, esos 6 videos estarán disponibles a principios de agosto en YouTube).

Mi sesión tuvo 351 asistentes y obtuve excelentes comentarios. Quería abordar algo de eso.

Primero, un problema de configuración:estaba usando un micrófono nuevo y no tenía idea de que cada pulsación de tecla sonaría como un trueno. Abordé ese problema con una mejor ubicación de mis periféricos, pero quiero disculparme con todos los afectados por eso.

A continuación, las descargas; la plataforma y las muestras se publican en el sitio del evento. Están en la parte inferior de la página, pero también puedes descargarlos aquí mismo.

Finalmente, lo que sigue es una lista de preguntas que se publicaron durante la sesión, y quería asegurarme de abordar las que no se respondieron durante la sesión de preguntas y respuestas en vivo. Me disculpo por haberlo incluido en poco menos de un mes. , pero había hubo muchas preguntas y no quería publicarlas por partes.

P:Si tiene un proceso que puede tener valores de entrada muy variables para los parámetros dados y el resultado es que el plan en caché no es óptimo para la mayoría de los casos, ¿es mejor crear el proceso CON RECOMPILE y tomar el pequeño impacto en el rendimiento cada vez que se ejecuta?

R: Deberá abordar esto caso por caso, ya que realmente dependerá de una variedad de factores (incluida la complejidad del plan). También tenga en cuenta que puede hacer una recompilación a nivel de declaración de modo que solo las declaraciones afectadas tengan que recibir el golpe, a diferencia de todo el módulo. Paul White me recordó que la gente suele 'arreglar' el rastreo de parámetros con RECOMPILE , pero con demasiada frecuencia eso significa WITH RECOMPILE al estilo 2000 en lugar de la mucho mejor OPTION (RECOMPILE) , que no solo se limita a la declaración, sino que también permite la incrustación de parámetros, lo que WITH RECOMPILE no es. Entonces, si vas a usar RECOMPILE para frustrar el rastreo de parámetros, agréguelo a la instrucción, no al módulo.

P:Si usa la opción de recompilar en sql dinámico, verá un gran impacto en el rendimiento

R: Como se indicó anteriormente, esto dependerá del costo y la complejidad de los planes y no hay forma de decir:"Sí, siempre habrá un gran impacto en el rendimiento". También debe asegurarse de comparar eso con la alternativa.

P:Si hay un índice agrupado en la fecha de inserción, más adelante cuando recuperamos datos, usamos la función de conversión, si usamos la comparación directa, la fecha de consulta no se puede leer, en el mundo real, ¿cuál es la mejor opción?

R: No estoy seguro de lo que significa "legible en el mundo real". Si quiere decir que desea la salida en un formato específico, generalmente es mejor convertirlo a una cadena en el lado del cliente. C# y la mayoría de los demás lenguajes que probablemente utilice en el nivel de presentación son más que capaces de formatear la salida de fecha/hora de la base de datos en cualquier formato regional que desee.

P:¿Cómo se determina la cantidad de veces que se usa un plan almacenado en caché? ¿Hay una columna con ese valor o alguna consulta en Internet que proporcione este valor? Finalmente, ¿serían relevantes tales recuentos solo desde el último reinicio?

R: La mayoría de los DMV solo son válidos desde el último inicio del servicio, e incluso otros pueden descargarse con más frecuencia (incluso a pedido, tanto inadvertidamente como a propósito). El caché del plan, por supuesto, está en constante cambio, y AFAIK los planes que se caen del caché no mantienen su conteo anterior si vuelven a aparecer. Entonces, incluso cuando ves un plan en el caché, no estoy al 100%. confíe en que puede creer el recuento de uso que encuentre.

Dicho esto, lo que probablemente esté buscando es sys.dm_exec_cached_plans.usecounts y también puede encontrar sys.dm_exec_procedure_stats.execution_count para ayudar a complementar la información de los procedimientos donde las declaraciones individuales dentro de los procedimientos no se encuentran en el caché.

P:¿Cuáles son los problemas al actualizar el motor de base de datos a una nueva versión pero dejando las bases de datos de usuario en modos de compatibilidad anteriores?

R: Las principales preocupaciones en torno a esto son la capacidad de usar cierta sintaxis, como OUTER APPLY o variables con una función con valores de tabla. No tengo conocimiento de ningún caso en el que el uso de una compatibilidad más baja tenga un impacto directo en el rendimiento, pero un par de cosas que normalmente se recomiendan son reconstruir índices y actualizar estadísticas (y hacer que su proveedor admita el nivel de compatibilidad más nuevo lo antes posible). Lo he visto resolver una degradación inesperada del rendimiento en una cantidad considerable de casos, pero también he escuchado algunas opiniones de que hacerlo no es necesario y tal vez incluso imprudente.

P:En el *, ¿importa cuando se hace una cláusula existe?

R: No, al menos en términos de rendimiento, una excepción donde SELECT * no importa es cuando se usa dentro de un EXISTS cláusula. Pero, ¿por qué usarías *? ¿aquí? Prefiero usar EXISTS (SELECT 1 ... – el optimizador los tratará de la misma manera, pero de alguna manera autodocumenta el código y asegura que los lectores entiendan que la subconsulta no devuelve ningún dato (incluso si se pierden el gran EXISTS fuera). Algunas personas usan NULL , y no tengo idea de por qué comencé a usar 1, pero encuentro NULL un poco poco intuitivo también.

*Nota* deberá tener cuidado si intenta usar EXISTS (SELECT *) dentro de un módulo que está vinculado a un esquema:

CREATE VIEW dbo.ThisWillNotWork
WITH SCHEMABINDING
AS
  SELECT BusinessEntityID
    FROM Person.Person AS p
	WHERE EXISTS (SELECT * FROM Sales.SalesOrderHeader AS h
	  WHERE h.SalesPersonID = p.BusinessEntityID);

Obtienes este error:

Mensaje 1054, nivel 15, estado 6, procedimiento ThisWillNotWork, línea 6
La sintaxis '*' no está permitida en objetos vinculados al esquema.

Sin embargo, cambiarlo a SELECT 1 funciona bien Así que tal vez ese sea otro argumento para evitar SELECT * incluso en ese escenario.

P:¿Hay algún enlace de recursos para los mejores estándares de codificación?

R: Probablemente hay cientos en una variedad de idiomas. Al igual que las convenciones de nombres, los estándares de codificación son algo muy subjetivo. Realmente no importa qué convención decidas que funciona mejor para ti; si te gusta tbl prefijos, ¡vuélvete loco! Prefiere Pascal sobre bigEndian, hazlo. Desea anteponer los nombres de sus columnas con el tipo de datos, como intCustomerID , no te voy a detener. Lo más importante es que defina una convención y la emplee *consistentemente.*

Dicho esto, si quieres mis opiniones, no me faltan.

P:¿XACT_ABORT es algo que se puede usar en SQL Server 2008 en adelante?

R: No sé de ningún plan para desaprobar XACT_ABORT por lo que debería seguir funcionando bien. Francamente, no veo que esto se use muy a menudo ahora que tenemos TRY / CATCH (y THROW a partir de SQL Server 2012).

P:¿Cómo se compara una función de tabla en línea en una aplicación cruzada con la función escalar que se llamó 1000x?

R: No probé esto, pero en muchos casos, reemplazar una función escalar con una función con valores de tabla en línea puede tener un gran impacto en el rendimiento. El problema que encuentro es que hacer este cambio puede ser una cantidad sustancial de trabajo en el sistema que se escribió antes de APPLY existieron, o aún son administrados por personas que no han adoptado este mejor enfoque.

P:Tengo una consulta que se ejecuta muy lentamente la primera vez (~1 minuto) y rápido (~3 segundos) cada dos veces. ¿Por dónde empiezo a ver de dónde viene el problema de rendimiento la primera vez?

R: Dos cosas saltan a la mente:(1) la demora tiene que ver con el tiempo de compilación o (2) la demora tiene que ver con la cantidad de datos que se cargan para satisfacer la consulta, y la primera vez tiene que provenir del disco y no la memoria. Para (1) puede ejecutar la consulta en SQL Sentry Plan Explorer y la barra de estado le mostrará el tiempo de compilación para la primera invocación y las subsiguientes (aunque un minuto parece bastante excesivo para esto, y poco probable). Si no encuentra ninguna diferencia, puede deberse simplemente a la naturaleza del sistema:memoria insuficiente para admitir la cantidad de datos que intenta cargar con esta consulta en combinación con otros datos que ya estaban en el grupo de búfer. Si no cree que ninguno de estos sea el problema, vea si las dos ejecuciones diferentes realmente producen planes diferentes; si hay alguna diferencia, publique los planes en answers.sqlperformance.com y estaremos encantados de echar un vistazo. . De hecho, la captura de planes reales para ambas ejecuciones utilizando el Explorador de planes en cualquier caso también puede informarle sobre las diferencias en la E/S y puede conducir a dónde SQL Server está gastando su tiempo en la primera ejecución más lenta.

P:Recibo análisis de parámetros con sp_executesql. ¿Optimize para cargas de trabajo ad hoc resolvería esto ya que solo el resguardo del plan está en caché?

R: No, no creo que la configuración Optimizar para cargas de trabajo ad hoc ayude en este escenario, ya que la detección de parámetros implica que las ejecuciones posteriores del mismo plan se usan para diferentes parámetros y con comportamientos de rendimiento significativamente diferentes. Optimize for ad hoc workloads se usa para minimizar el impacto drástico en la memoria caché del plan que puede ocurrir cuando tiene una gran cantidad de instrucciones SQL diferentes. Entonces, a menos que esté hablando del impacto en el caché del plan de muchas declaraciones diferentes que está enviando a sp_executesql – que no se caracterizaría como rastreo de parámetros – creo que experimentando con OPTION (RECOMPILE) puede tener un mejor resultado o, si conoce los valores de los parámetros que *sí* producen buenos resultados en una variedad de combinaciones de parámetros, use OPTIMIZE FOR . Esta respuesta de Paul White puede proporcionar una perspectiva mucho mejor.

P:¿Hay alguna forma de ejecutar SQL dinámico y NO guardar el plan de consulta?

R: Claro, solo incluye OPTION (RECOMPILE) en el texto SQL dinámico:

DBCC FREEPROCCACHE;
 
USE AdventureWorks2012;
GO
SET NOCOUNT ON;
GO
 
EXEC sp_executesql 
  N'SELECT TOP (1) * INTO #x FROM Sales.SalesOrderHeader;';
GO
EXEC sp_executesql 
  N'SELECT TOP (1) * INTO #x FROM Sales.SalesOrderDetail OPTION (RECOMPILE);'
GO
 
SELECT t.[text], p.usecounts
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.[plan_handle]) AS t
WHERE t.[text] LIKE N'%Sales.' + 'SalesOrder%';

Resultados:1 fila que muestra Sales.SalesOrderHeader consulta.

Ahora, si alguna declaración en el lote NO incluye OPTION (RECOMPILE) , es posible que el plan aún se almacene en caché, simplemente no se puede reutilizar.

P:¿Puede usar BETWEEN en el ejemplo de fecha del n.° 9 si>=y

R: Bueno, BETWEEN no es semánticamente equivalente a >= AND < , sino >= AND <= , y optimiza y funciona exactamente de la misma manera. En cualquier caso, deliberadamente no uso BETWEEN en consultas de rango de fechas, nunca, porque no hay forma de convertirlo en un rango abierto. Con BETWEEN , ambos extremos son inclusivos, y esto puede ser muy problemático según el tipo de datos subyacente (ahora o debido a algún cambio futuro que quizás no conozca). El título puede parecer un poco duro, pero entro en detalle sobre esto en la siguiente publicación de blog:

¿Qué tienen BETWEEN y el diablo en común?

P:En un cursor, ¿qué hace realmente "local fast_forward"?

R: FAST_FORWARD es en realidad la forma abreviada de READ_ONLY y FORWARD_ONLY . Esto es lo que hacen:

  • LOCAL hace que los ámbitos externos (por defecto, un cursor es GLOBAL a menos que haya cambiado la opción de nivel de instancia).
  • READ_ONLY hace que no pueda actualizar el cursor directamente, p. usando WHERE CURRENT OF .
  • FORWARD_ONLY impide la capacidad de desplazamiento, p. usando FETCH PRIOR o FETCH ABSOLUTE en lugar de FETCH NEXT .

La configuración de estas opciones, como demostré (y escribí en un blog), puede tener un impacto significativo en el rendimiento. Muy rara vez veo cursores en producción que realmente necesitan desviarse de este conjunto de funciones, pero generalmente están escritos para aceptar los valores predeterminados mucho más costosos de todos modos.

P:¿Qué es más eficiente, un cursor o un ciclo while?

R: UN WHILE loop probablemente será más eficiente que un cursor equivalente con las opciones predeterminadas, pero sospecho que encontrará poca o ninguna diferencia si usa LOCAL FAST_FORWARD . En términos generales, un WHILE loop *es* un cursor sin llamarse cursor, y el año pasado desafié a algunos colegas muy estimados a demostrar que estaba equivocado. Su WHILE a los bucles no les fue tan bien.

P:No recomienda el prefijo usp para los procedimientos almacenados del usuario, ¿tiene esto el mismo impacto negativo?

R: Un usp_ prefijo (o cualquier prefijo que no sea sp_ , o sin prefijo para el caso) *no* tiene el mismo impacto que demostré. Sin embargo, encuentro poco valor en el uso de un prefijo en los procedimientos almacenados porque rara vez hay alguna duda de que cuando encuentro un código que dice EXEC something , ese algo es un procedimiento almacenado, por lo que tiene poco valor allí (a diferencia de, por ejemplo, anteponer vistas para distinguirlas de las tablas, ya que se pueden usar indistintamente). Dar a cada procedimiento el mismo prefijo también hace que sea mucho más difícil encontrar el objeto que busca, por ejemplo, en el Explorador de objetos. Imagínese si cada apellido en la guía telefónica tuviera el prefijo LastName_ – ¿De qué manera te ayuda eso?

P:¿Hay alguna forma de limpiar los planes almacenados en caché cuando hay varias copias?

R: ¡Sí! Bueno, si está en SQL Server 2008 o superior. Una vez que haya identificado dos planes que son idénticos, seguirán teniendo plan_handle separados valores. Entonces, identifique el que *no* desea conservar, copie su plan_handle y colóquelo dentro de este DBCC comando:

DBCC FREEPROCCACHE(0x06.....);
P:¿Usar if else, etc. en un proceso genera malos planes? ¿Se optimiza para la primera ejecución y solo se optimiza para esa ruta? Entonces, ¿las secciones de código en cada IF deben convertirse en procedimientos separados?

R: Dado que SQL Server ahora puede realizar la optimización a nivel de declaración, esto tiene un efecto menos drástico hoy que en versiones anteriores, donde todo el procedimiento tenía que volver a compilarse como una sola unidad.

P:A veces descubrí que escribir SQL dinámico puede ser mejor porque elimina el problema de rastreo de parámetros para sp. Es esto cierto ? ¿Hay compensaciones u otras consideraciones que hacer sobre este escenario?

R: Sí, el SQL dinámico a menudo puede frustrar la detección de parámetros, particularmente en el caso de que una consulta masiva de "fregadero de cocina" tenga muchos parámetros opcionales. Traté algunas otras consideraciones en las preguntas anteriores.

P:Si tuviera una columna calculada en mi tabla como DATEPART(mycolumn, year) y en el índice, ¿el servidor SQL usaría esto con SEEK?

R: Debería, pero por supuesto depende de la consulta. Es posible que el índice no sea adecuado para cubrir las columnas de salida o satisfacer otros filtros, y es posible que el parámetro que utilice no sea lo suficientemente selectivo como para justificar una búsqueda.

P:¿Se genera un plan para CADA consulta? ¿Se genera un plan incluso para los triviales?

R: Hasta donde yo sé, se genera un plan para cada consulta válida, incluso los planes triviales, a menos que haya un error que impida que se genere un plan (esto puede ocurrir en múltiples escenarios, como sugerencias no válidas). Si están en caché o no (y cuánto tiempo permanecen en caché) depende de una variedad de otros factores, algunos de los cuales he discutido anteriormente.

P:¿Una llamada a sp_executesql genera (y reutiliza) el plan en caché?

R: Sí, si envía exactamente el mismo texto de consulta, no importa si lo emite directamente o lo envía a través de sp_executesql , SQL Server almacenará en caché y reutilizará el plan.

P:¿Está bien aplicar una regla (para un entorno de desarrollo) en la que todas las máquinas de desarrollo utilicen la inicialización instantánea de archivos?

R: No veo por qué no. La única preocupación que tendría es que con la inicialización instantánea de archivos, es posible que los desarrolladores no noten una gran cantidad de eventos de crecimiento automático, lo que puede reflejar configuraciones de crecimiento automático deficientes que pueden tener un impacto muy diferente en el entorno de producción (especialmente si alguno de esos servidores *no * tener IFI habilitado).

P:Con la función en la cláusula SELECT, ¿sería correcto decir entonces que es mejor duplicar el código?

R: Personalmente, diría que sí. Obtuve mucho rendimiento al reemplazar funciones escalares en SELECT list con un equivalente en línea, incluso en los casos en que tengo que repetir ese código. Sin embargo, como se mencionó anteriormente, es posible que en algunos casos reemplace eso con una función con valores de tabla en línea que le permita reutilizar el código sin la desagradable penalización de rendimiento.

P:¿Podemos usar generadores de datos para obtener el mismo tamaño de datos para uso de desarrollo en lugar de usar datos de producción (difíciles de obtener)? ¿El sesgo de datos es importante para los planes resultantes?

R: El sesgo de datos puede tener un factor, y sospecho que depende del tipo de datos que esté generando / simulando y qué tan lejos pueda estar el sesgo. Si tiene, digamos, una columna varchar(100) que en producción suele tener 90 caracteres y su generación de datos produce datos con un promedio de 50 (que es lo que asumirá SQL Server), encontrará un impacto muy diferente en el número de páginas y optimización, y probablemente pruebas no muy realistas.

Pero seré honesto:esta faceta específica no es algo en lo que haya invertido mucho tiempo, porque generalmente puedo intimidar para obtener datos reales. :-)

P:¿Todas las funciones se crean de la misma manera al examinar el rendimiento de las consultas? Si no, ¿hay una lista de funciones conocidas que debería evitar cuando sea posible?

R: No, no todas las funciones son iguales en términos de rendimiento. Hay tres tipos diferentes de funciones que podemos crear (ignorando las funciones CLR por el momento):

  • Funciones escalares de instrucciones múltiples
  • Funciones con valores de tabla de instrucciones múltiples
  • Funciones con valores de tabla en línea
    Las funciones escalares en línea se mencionan en la documentación, pero son un mito y, a partir de SQL Server 2014 al menos, bien podría mencionarse junto con Sasquatch y el Monstruo del Lago Ness.

En general, y lo pondría en una fuente de 80 puntos si pudiera, las funciones con valores de tabla en línea son buenas, y las demás deben evitarse cuando sea posible, ya que son mucho más difíciles de optimizar.

Las funciones también pueden tener diferentes propiedades que afectan su rendimiento, como si son deterministas y si están vinculadas a un esquema.

Para muchos patrones de funciones, definitivamente hay consideraciones de rendimiento que debe tener en cuenta, y también debe tener en cuenta este elemento de conexión que tiene como objetivo abordarlas.

P:¿Podemos seguir ejecutando totales sin cursores?

R: Si podemos; hay varios métodos además de un cursor (como se detalla en mi publicación de blog, Mejores enfoques para ejecutar totales, actualizado para SQL Server 2012):

  • Subconsulta en la lista SELECT
  • CTE recursivo
  • Auto-unión
  • "Actualización peculiar"
  • Solo SQL Server 2012+:SUM() OVER() (usando predeterminado / RANGE)
  • Solo SQL Server 2012+:SUM() OVER() (usando FILAS)

La última opción es, con mucho, el mejor enfoque si está en SQL Server 2012; si no, hay restricciones en las otras opciones que no son de cursor que a menudo harán que un cursor sea la opción más atractiva. Por ejemplo, el peculiar método de actualización no está documentado y no se garantiza que funcione en el orden esperado; el CTE recursivo requiere que no haya espacios en cualquier mecanismo secuencial que esté utilizando; y los enfoques de subconsulta y autounión simplemente no escalan.