Optimizando consultas para más de 25 millones de filas

11

Estoy usando MS SQL y tengo que ejecutar varias consultas en la misma tabla con diferentes criterios. Al principio ejecuté cada consulta en la tabla original, aunque todas comparten algo de filtrado (es decir, Fecha, estado). Esto tomó mucho tiempo (alrededor de 2 minutos).

Hay duplicados en las filas de datos y todos los índices NO ESTÁN AGRUPADOS. Solo estoy interesado en 4 columnas para mis criterios y el resultado debería mostrar solo el recuento, para todas las consultas.

columnas necesarias: TABLE, FIELD, AFTER, DATE, y hay un índice en cada uno de DATEy TABLE.

Después de crear una tabla temporal con solo los campos que necesito, se redujo a 1:40 minutos, lo que sigue siendo muy malo.

CREATE TABLE #TEMP
(
    TABLE VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    AFTER VARCHAR(1000) NULL,
    DATE DATETIME,
    SORT_ID INT IDENTITY(1,1)
)
CREATE CLUSTERED INDEX IX_ADT ON #TEMP(SORT_ID)

INSERT INTO #TEMP (TABLE, FIELD, AFTER, DATE)
    SELECT TABLE, FIELD, AFTER, DATE 
    FROM mytbl WITH (NOLOCK)
    WHERE TABLE = 'OTB' AND
    FIELD = 'STATUS'

Ejecutando esto -> (216598 filas afectadas)

Como no todas las consultas dependen del intervalo de fechas, no lo incluí en la consulta. El problema es que solo lleva más de 1 minuto insertarlo . El inserto anterior tomó 1:19 minutos

Quiero ejecutar algo como esto para varias consultas:

SELECT COUNT(*) AS COUNT
FROM #TEMP
WHERE AFTER = 'R' AND
DATE >= '2014-01-01' AND
DATE <= '2015-01-01'

Es un problema con la inserción más que el de la selección, pero la temperatura tiene muchas menos filas que la tabla original, lo que podría ser mejor que recorrer la tabla varias veces.

¿Cómo puedo optimizar esto?

EDITAR

He eliminado el ID de clasificación, pensé que el problema era principalmente con la selección y no con la inserción. Fue una suposición.

No puedo crear un único en ningún índice ya que no hay campos o filas únicos.

Estoy usando SQL Server 2012.

Información de la tabla : es un montón y tiene el siguiente uso de espacio:

name    rows        reserved    data        index_size  unused
mytbl   24869658    9204568 KB  3017952 KB  5816232 KB  370384 KB
Atieh
fuente
@MikaelEriksson No puedo modificar las tablas de producción ..
Atieh
Si las consultas que intenta optimizar son de la forma SELECT COUNT(*) AS COUNT FROM original_table WHERE AFTER = 'R' AND DATE >= '2014-01-01' AND DATE < '2015-01-01', ¿por qué no intenta optimizar cada (consulta) por separado? ¿No está permitido agregar índices a la tabla?
ypercubeᵀᴹ
2
Debes determinar por qué es lento. ¿Está siendo bloqueado? ¿Está esperando que tempdb crezca? ¿Es abismal el plan de ejecución? Nadie puede arreglar "mi consulta es lenta" sin más detalles ...
Aaron Bertrand
3
Bueno, me parece una causa perdida ( "No se me permite optimizar nada, así que simplemente empujemos 200K filas en una tabla temporal cada vez que necesitemos ejecutar algunas consultas" ). Pero podría eliminar las columnas TABLEy FIELDde la #temptabla (después de todo, todas las filas tienen TABLE = 'OTB' AND FIELD = 'STATUS'para la tabla temporal específica).
ypercubeᵀᴹ
2
Solicité una edición y mejoras agregando un comentario detallado (y cortés). Para eso están los comentarios. También debe etiquetar su pregunta con la versión de SQL Server que está utilizando (por ejemplo, SQL Server 2014). El DDL para la tabla también podría ser útil ( CREATE TABLEdeclaración). El voto negativo fue porque la pregunta no estaba clara.
Paul White 9

Respuestas:

12

La pregunta es principalmente sobre cómo optimizar la instrucción select:

SELECT [TABLE], [FIELD], [AFTER], [DATE]
FROM mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB' AND
[FIELD] = 'STATUS'

Eliminando las proyecciones redundantes y agregando el supuesto dboesquema:

SELECT [AFTER], [DATE] 
FROM dbo.mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB'
AND FIELD = 'STATUS';

Sin un índice como ([TABLE],[FIELD]) INCLUDE ([AFTER],[DATE])SQL Server tiene dos opciones principales:

  1. Escanee el montón por completo (3GB +); o
  2. Localice las filas que coinciden [TABLE] = 'OTB'y [FIELD] = 'STATUS'(usando IDX6), luego realice una búsqueda de montón (RID) por fila para recuperar las columnas [AFTER]y [DATE].

Si el optimizador elige una exploración de montón o búsqueda de índice con búsqueda RID depende de la selectividad estimada de los predicados [TABLE] = 'OTB'y [FIELD] = 'STATUS'. Verifique si el número estimado de filas de la búsqueda coincide con la realidad. Si no, actualice sus estadísticas. Pruebe la consulta con una sugerencia de tabla que fuerce el uso del índice, si esa condición es razonablemente selectiva . Si el optimizador está eligiendo actualmente la búsqueda de índice, pruebe el rendimiento con una INDEX(0)o una FORCESCANpista para escanear el montón.

Más allá de eso, podría intentar mejorar un poco el escaneo del montón eliminando parte del espacio no utilizado (370 MB). En SQL Server 2008, esto se puede hacer reconstruyendo el montón. El espacio no utilizado en los montones a menudo es el resultado de eliminaciones realizadas sin tomar un bloqueo de tabla (sin un bloqueo de tabla, las páginas vacías no se desasignan de un montón). Las tablas que experimentan eliminaciones frecuentes a menudo se almacenan mejor como una tabla agrupada por este motivo.

El rendimiento de la exploración de almacenamiento dinámico depende de la cantidad de la tabla almacenada en la memoria, la cantidad que debe leerse desde el disco, la capacidad de las páginas, la velocidad del almacenamiento persistente, si la exploración es de E / S o de CPU ( el paralelismo puede ayudar).

Si el rendimiento sigue siendo inaceptable después de haber investigado todo lo anterior, intente presentar un nuevo índice. Si está disponible en su versión de SQL Server, un posible índice filtrado para la consulta dada sería:

CREATE INDEX index_name
ON dbo.mytbl ([DATE],[AFTER])
WHERE [TABLE] = 'OTB'
AND [FIELD] = 'STATUS';

También considere la compresión del índice, si está disponible y es beneficioso. Sin un nuevo índice de algún tipo, hay relativamente poco que pueda hacer para mejorar el rendimiento de la consulta dada.

Paul White 9
fuente
Lo sentimos Paul, existe: IDX6 nonclustered located on PRIMARY TABLE, FIELD. ¿Quizás esto cambiaría las cosas que mencionaste?
Atieh
6

Creo que hay un caso para cambiar los índices aquí porque:

  • tienes una tarea que hacer (estas consultas múltiples)
  • volúmenes de almacenamiento de datos (más de 25 millones de filas) y
  • Un problema de rendimiento.

Este también sería un buen caso de uso para los índices de almacén de columnas no agrupados introducidos en SQL Server 2012, es decir, resumir / agregar algunas columnas en una tabla grande con muchas columnas.

Aunque estos índices tienen el efecto secundario de hacer que la tabla sea de solo lectura (con la excepción del cambio de partición), pueden transformar el rendimiento de las consultas agregadas en las condiciones adecuadas. El aspecto de solo lectura se puede administrar, ya sea soltando y recreando el índice o los datos de cambio de partición simple en la tabla.

Configuré una plataforma de prueba simple para imitar su configuración, y vi una buena mejora en el rendimiento:

USE tempdb
GO

SET NOCOUNT ON
GO

-- Create a large table
IF OBJECT_ID('dbo.largeTable') IS NOT NULL
DROP TABLE dbo.largeTable
GO
CREATE TABLE dbo.largeTable ( 

    [TABLE] VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    [AFTER] VARCHAR(1000) NULL,
    [DATE] DATETIME,
    SORT_ID INT IDENTITY(1,1),

    pad VARCHAR(100) DEFAULT REPLICATE( '$', 100 )
)
GO

-- Populate table
;WITH cte AS (
SELECT TOP 100000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
SELECT 
    x.tableName, 
    y.field,
    z.[after],
    DATEADD( day, rn % 1111, '1 Jan 2012' )
FROM cte c
    CROSS JOIN ( VALUES ( 'OTB' ), ( 'AAA' ), ( 'BBB' ), ( 'CCCC' ) ) x ( tableName )
    CROSS JOIN ( VALUES ( 'STATUS' ), ( 'TIME' ), ( 'POWER' ) ) y ( field )
    CROSS JOIN ( VALUES ( 'R' ), ( 'X' ), ( 'Z' ), ( 'A' ) ) z ( [after] )

CHECKPOINT

GO 5

EXEC sp_spaceused 'dbo.largeTable'
GO

SELECT MIN([DATE]) xmin, MAX([DATE]) xmax, FORMAT( COUNT(*), '#,#' ) records
FROM dbo.largeTable
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff1
GO

-- Add the non-clustered columnstore
CREATE NONCLUSTERED COLUMNSTORE INDEX _cs ON dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

-- Check query again
DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff2
GO

Mis resultados, 6 segundos v 0.08 segundos:

ingrese la descripción de la imagen aquí

En resumen, intente crear un caso con su jefe para cambiar los índices o al menos crear algún tipo de proceso nocturno en el que estos registros se graben en una tabla / base de datos de solo lectura donde pueda hacer su trabajo y agregue indexación apropiado para esa carga de trabajo.

wBob
fuente