Otimizando consultas para mais de 25 milhões de linhas

11

Estou usando o MS SQL e preciso executar várias consultas na mesma tabela com diferentes critérios. Inicialmente, executei cada consulta na tabela original, embora todas compartilhem alguma filtragem (por exemplo, data, status). Isso levou muito tempo (cerca de 2 minutos).

Existem duplicatas nas linhas de dados e todos os índices NÃO SÃO CLUSTERADOS. Estou interessado apenas em 4 colunas para meus critérios e o resultado deve gerar apenas a contagem, para todas as consultas.

colunas necessário: TABLE, FIELD, AFTER, DATE, e existe um índice em cada um dos DATEe TABLE.

Depois de criar uma tabela temporária com apenas os campos necessários, ela caiu para 1:40 minutos, o que ainda é muito ruim.

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'

Executar isto -> (216598 linhas afetadas)

Como nem todas as consultas dependem do período, não o incluí na consulta. O problema é que está demorando bem acima de 1 minuto para inserir apenas . A inserção acima levou 1:19 minutos

Eu quero executar algo parecido com isto para várias consultas:

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

É um problema com a inserção mais do que a seleção, mas a temperatura tem muito menos linhas que a tabela original, o que poderia ser melhor do que passar pela tabela várias vezes.

Como posso otimizar isso?

EDITAR

Eu removi o ID de classificação, pensei que o problema estava principalmente no select e não no insert. Foi um palpite.

Não consigo criar um único em nenhum índice, pois não há campos ou linhas exclusivos.

Estou usando o SQL Server 2012.

Informações da tabela : É uma pilha e possui o seguinte uso de espaço:

name    rows        reserved    data        index_size  unused
mytbl   24869658    9204568 KB  3017952 KB  5816232 KB  370384 KB
Atieh
fonte
@MikaelEriksson Não consigo modificar as tabelas de produção ..
Atieh
Se as consultas que você está tentando otimizar são do formato SELECT COUNT(*) AS COUNT FROM original_table WHERE AFTER = 'R' AND DATE >= '2014-01-01' AND DATE < '2015-01-01', por que você não tenta otimizar cada uma (consulta) separadamente? Você não tem permissão para adicionar índices à tabela?
usar o seguinte comando
2
Você precisa determinar por que é lento. Está sendo bloqueado? Está esperando o tempdb crescer? O plano de execução é péssimo? Ninguém pode corrigir "minha consulta é lenta" sem mais detalhes ...
Aaron Bertrand
3
Bem, parece uma causa perdida para mim ( "Eu não tenho permissão para otimizar nada, então vamos enviar 200K linhas em uma tabela temporária toda vez que precisamos executar algumas consultas" ). Mas você pode remover as colunas TABLEe FIELDda #temptabela (afinal, todas as linhas têm TABLE = 'OTB' AND FIELD = 'STATUS'para a tabela temporária específica).
ypercubeᵀᴹ
2
Pedi uma edição e melhorias adicionando um comentário detalhado (e educado). É para isso que servem os comentários. Você também deve marcar sua pergunta com a versão do SQL Server que está usando (por exemplo, SQL Server 2014). O DDL da tabela também pode ser útil ( CREATE TABLEinstrução). O voto negativo foi porque a questão não estava clara.
Paul White 9

Respostas:

12

A questão é principalmente sobre como otimizar a instrução select:

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

Removendo as projeções redundantes e adicionando o dboesquema presumido :

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

Sem um índice como o ([TABLE],[FIELD]) INCLUDE ([AFTER],[DATE])SQL Server, há duas opções principais:

  1. Digitalize a pilha completamente (3 GB +); ou
  2. Localize as linhas correspondentes [TABLE] = 'OTB'e [FIELD] = 'STATUS'(usando IDX6) e execute uma pesquisa de heap (RID) por linha para recuperar as colunas [AFTER]e [DATE].

Se o otimizador escolhe uma varredura de heap ou procura de índice com pesquisa RID depende da seletividade estimada dos predicados [TABLE] = 'OTB'e [FIELD] = 'STATUS'. Verifique se o número estimado de linhas da busca corresponde à realidade. Caso contrário, atualize suas estatísticas. Teste a consulta com uma dica de tabela que força o uso do índice, se essa condição for razoavelmente seletiva . Se o otimizador estiver atualmente escolhendo a busca de índice, teste o desempenho com uma INDEX(0)ou FORCESCANdica para verificar a pilha.

Além disso, você pode melhorar um pouco a varredura da pilha removendo parte do espaço não utilizado (370 MB). No SQL Server 2008, isso pode ser feito reconstruindo a pilha. O espaço não utilizado nos heaps geralmente resulta de exclusões executadas sem que um bloqueio de tabela seja executado (sem um bloqueio de tabela, as páginas vazias não são desalocadas de um heap). As tabelas que apresentam exclusões freqüentes costumam ser melhor armazenadas como uma tabela em cluster por esse motivo.

O desempenho da varredura de heap depende de quanto da tabela está armazenada na memória, quanto deve ser lido do disco, quão cheias as páginas estão, a velocidade do armazenamento persistente, se a varredura é de E / S ou ligada à CPU ( paralelismo pode ajudar).

Se o desempenho ainda for inaceitável após a investigação de todas as opções acima, tente defender um novo índice. Se disponível na sua versão do SQL Server, um possível índice filtrado para a consulta especificada seria:

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

Considere também a compactação de índice, se disponível e benéfica. Sem um novo índice de algum tipo, há relativamente pouco que você pode fazer para melhorar o desempenho da consulta especificada.

Paul White 9
fonte
Desculpe Paul, existe: IDX6 nonclustered located on PRIMARY TABLE, FIELD. Talvez isso mude as coisas que você mencionou?
Atieh 27/02
6

Eu acho que há um caso para alterar os índices aqui porque:

  • você tem uma tarefa a realizar (essas várias consultas)
  • volumes de data warehouse (mais de 25 milhões de linhas) e
  • um problema de desempenho.

Esse também seria um bom caso de uso para índices columnstore não agrupados, introduzidos no SQL Server 2012, ou seja, resumir / agregar algumas colunas em uma tabela grande com muitas colunas.

Embora esses índices tenham o efeito colateral de tornar a tabela somente leitura (com exceção da alternância de partição), eles podem transformar o desempenho de consultas agregadas nas condições corretas. O aspecto somente leitura pode ser gerenciado, descartando e recriando o índice ou os dados simples da chave de partição na tabela.

Configurei uma plataforma de teste simples para imitar sua configuração e vi uma boa melhoria no desempenho:

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

Meus resultados, 6 segundos v 0,08 segundos:

insira a descrição da imagem aqui

Em resumo, tente criar um caso com seu chefe para que os índices sejam alterados ou, pelo menos, crie algum tipo de processo durante a noite em que esses registros sejam gravados em uma tabela / banco de dados de relatório somente leitura, onde você pode fazer seu trabalho e adicionar indexação apropriado para essa carga de trabalho.

wBob
fonte