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

Un enfoque para el ajuste de índice - Parte 2

En mi última publicación, comencé a describir el proceso por el que paso al ajustar las consultas, específicamente cuando descubro que necesito agregar un nuevo índice o modificar uno existente. Hasta este punto, hemos identificado la consulta problemática, el índice que necesito, qué índices existen actualmente en la tabla y si esos índices se están utilizando o no. Una vez que tengamos esos datos, podemos pasar a los siguientes pasos del proceso.

Paso 5:Qué utiliza un índice

Además de ver con qué frecuencia se usa (o no) un índice, es útil saber qué consultas use un índice, particularmente si estoy buscando fusionarlo con otro índice. Afortunadamente, Jonathan Kehayias ya escribió una consulta para ayudar a identificar qué planes usan un índice específico. Su versión se puede usar para el caché del plan:el único desafío es que la información es transitoria, por lo que es posible que no capture todas las consultas que usan un índice en particular. Query Store puede ayudar con eso. Modifiqué su consulta para obtener la misma información de los planes en Query Store:

  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
  DECLARE @IndexName AS NVARCHAR(128) = N'[IX_Sales_OrderLines_AllocatedStockItems]',
          @lb AS nchar(1) = N'[', @rb AS nchar(1) = N']';
 
  -- Make sure the name passed is appropriately quoted
  IF (LEFT(@IndexName, 1) <> @lb AND RIGHT(@IndexName, 1) <> @rb) SET @IndexName = QUOTENAME(@IndexName);
 
  --Handle the case where the left or right was quoted manually but not the opposite side
  IF LEFT(@IndexName, 1)  <> @lb SET @IndexName = @rb + @IndexName;
  IF RIGHT(@IndexName, 1) <> @rb SET @IndexName = @IndexName + @rb;
 
  ;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')   
  SELECT
    stmt.value('(@StatementText)[1]', 'varchar(max)') AS SQL_Text,
    obj.value('(@Database)[1]', 'varchar(128)') AS DatabaseName,
    obj.value('(@Schema)[1]', 'varchar(128)') AS SchemaName,
    obj.value('(@Table)[1]', 'varchar(128)') AS TableName,
    obj.value('(@Index)[1]', 'varchar(128)') AS IndexName,
    obj.value('(@IndexKind)[1]', 'varchar(128)') AS IndexKind,
    query_plan
  FROM 	
  (
    SELECT query_plan
    FROM
    (
      SELECT TRY_CONVERT(XML, [qsp].[query_plan]) AS [query_plan]
      FROM sys.query_store_plan [qsp]
    ) tp
  ) AS tab (query_plan)
  CROSS APPLY query_plan.nodes('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS batch(stmt)
  CROSS APPLY stmt.nodes('.//IndexScan/Object[@Index=sql:variable("@IndexName")]') AS idx(obj)
  OPTION(MAXDOP 1, RECOMPILE);

Vale la pena señalar que este es otro punto en el que puedo encontrarme muy metido en una madriguera de conejo, según la cantidad de índices que estoy revisando y la cantidad de consultas que los usan. Si es posible, también consideraré los recuentos de ejecución (desde Query Store o el caché del plan) para no solo entender qué consulta utiliza un índice, pero con qué frecuencia se ejecuta esa consulta. Aquí es donde el ajuste de índice se convierte en un arte. Puedo recopilar una cantidad ridícula de datos... pero no tengo tiempo infinito para el análisis, por lo que tengo que tomar una decisión sobre cuántas consultas voy a revisar.

Paso 6:Prueba

En su forma más simple, probar un índice significa tomar la consulta problemática y capturar el plan y los datos de rendimiento (duración, IO, CPU, etc.) y luego crear el índice, volver a ejecutar la consulta y capturar la misma información. Si el rendimiento mejora, ¡ya está listo!

Rara vez es tan simple.

Para empezar, a menudo tengo al menos dos variaciones de un índice que quiero probar, a veces más. Comienzo con mi línea de base, luego creo todas las variaciones de índice, borro el caché del plan y veo qué elige SQL Server. Luego repaso y fuerzo cada índice con una sugerencia, capturando el plan y las métricas de rendimiento para cada ejecución. Nota:esto supone que tengo suficiente espacio en disco para todos los índices... si no, entonces los creo uno a la vez y pruebo. Finalmente, comparo los números. Si solo estoy agregando un nuevo índice, ya casi termino. Pero si estoy modificando un índice o fusionando un par, puede complicarse.

En un mundo ideal, si modifico un índice existente, encuentro las consultas más frecuentes/importantes que usan el índice actual y obtengo sus planes y métricas de rendimiento (esto es fácil con Query Store). Luego cambio el índice, ejecuto todas esas consultas nuevamente y veo si obtengo cambios significativos en la forma y/o el rendimiento del plan.

Si fusiono dos índices, hago lo mismo, pero con todas las consultas que usan cualquiera de los índices y luego vuelvo a probar con el índice fusionado.

Si estoy agregando/cambiando/fusionando varios índices para una tabla, entonces necesito obtener todas las consultas relevantes y sus planes y métricas, cambiar los índices, luego obtener toda la información nuevamente y comparar. Esto puede llevar mucho tiempo, dependiendo de cuántas consultas diferentes haya. Aquí es donde es una forma de arte y debe determinar cuántas consultas realmente necesita probar. Es una función de la frecuencia de ejecución, la importancia/relevancia de la consulta y el tiempo que tengo disponible/asignado.

Finalmente, si agrego un índice a una tabla y no elimino ninguno existente, entonces he agregado una sobrecarga para INSERTAR, ELIMINAR y potencialmente ACTUALIZAR. Es posible probar el rendimiento de este cambio, pero necesita un entorno de prueba y la capacidad de ejecutar una prueba de carga y capturar métricas previas y posteriores al cambio relacionadas con la duración, IO y CPU.

Son muchos amigos, por lo que es irónico que inicialmente pensé en afirmar que el ajuste del índice era fácil. Puede que no siempre sea simple, pero es posible. Es una cuestión de diligencia y de estar al tanto de todo.

Paso 7:Implementación

Después de examinar los nuevos índices tanto como sea posible, estamos listos para la producción. Admito que veo los cambios en el índice como de bajo riesgo, particularmente los nuevos. Si es un problema, puede soltarlo inmediatamente y volver al estado original. Con un escenario de modificar/combinar/soltar, desea tener todo escrito, de modo que pueda cambiar y volver a crear índices según sea necesario para restablecer los índices. Siempre recomiendo deshabilitar inicialmente los índices en lugar de eliminarlos, ya que así no tiene que preocuparse por la definición; si necesita volver a agregar el índice, simplemente lo reconstruye.

Resumen

¡Su método para agregar y/o consolidar índices puede ser diferente! Al igual que el ajuste de consultas, no existe un proceso perfecto. Para cualquier persona nueva en el ajuste de índices, esperamos que esto proporcione un inicio de elementos para revisar y consideraciones importantes. Es imposible agregar índices sin agregar una cierta cantidad de gastos generales, y nuevamente aquí es donde entra el arte:debe determinar si el beneficio del índice supera el costo de las modificaciones.

El ajuste del índice es un proceso iterativo perpetuo:no creo que haya terminado nunca, porque se modifica el código, se agregan nuevas tablas o funciones y los datos en las tablas cambian. Kimberly tiene dos publicaciones (https://www.sqlskills.com/blogs/kimberly/spring-cleaning-your-indexes-part-i/ y https://www.sqlskills.com/blogs/kimberly/spring-cleaning- your-indexes-part-ii/) que hablan sobre la limpieza de sus índices:¡ahora es el mejor momento para comenzar! Y finalmente, cada vez que alguien pregunta, "¿cuántos índices debe haber para una tabla?" Respondo con algo como, "el menor número que necesita para satisfacer tantas consultas como sea posible". No hay un número mágico:he visto tablas con índices cero y he visto tablas con más de 100 (estoy seguro de que algunos de ustedes han visto recuentos más altos). Ni el cero ni el 100 son buenos, pero el número "correcto" es uno que debes averiguar utilizando los datos disponibles y tu experiencia.