No que diz respeito à metodologia, acredito que você está latindo para a b-tree errada ;-).
O que nós sabemos:
Primeiro, vamos consolidar e revisar o que sabemos sobre a situação:
O que podemos supor:
Em seguida, podemos analisar todos esses pontos de dados juntos para ver se podemos sintetizar detalhes adicionais que nos ajudarão a encontrar um ou mais gargalos e apontar para uma solução ou, pelo menos, descartar algumas soluções possíveis.
A direção atual dos comentários nos comentários é que o principal problema é a transferência de dados entre o SQL Server e o Excel. Esse é realmente o caso? Se o procedimento armazenado for chamado para cada uma das 800.000 linhas e demorar 50 ms por cada chamada (ou seja, por cada linha), isso adicionará até 40.000 segundos (não ms). E isso é equivalente a 666 minutos (hhmm ;-), ou pouco mais de 11 horas. No entanto, o processo todo levou apenas 7 horas para ser executado. Já temos 4 horas sobre o tempo total e ainda adicionamos tempo para fazer os cálculos ou salvar os resultados novamente no SQL Server. Então, algo não está bem aqui.
Observando a definição do Stored Procedure, existe apenas um parâmetro de entrada para @FileID
; não há nenhum filtro ativado @RowID
. Portanto, suspeito que um dos dois cenários a seguir esteja acontecendo:
- Na verdade, esse procedimento armazenado não é chamado por cada linha, mas por cada um
@FileID
, que parece abranger aproximadamente 4000 linhas. Se as 4000 linhas retornadas declaradas forem uma quantidade bastante consistente, haverá apenas 200 delas agrupadas nas 800.000 linhas. E 200 execuções com 50 ms cada equivale a apenas 10 segundos nas 7 horas.
- Se esse procedimento armazenado realmente for chamado para todas as linhas, a primeira vez que uma nova
@FileID
for passada levará um pouco mais para puxar novas linhas para o Buffer Pool, mas as próximas execuções 3999 normalmente retornarão mais rapidamente porque já estão sendo em cache, certo?
Eu acho que incidindo sobre este "filtro" procedimento armazenado ou qualquer transferência de dados do SQL Server para o Excel, é um arenque vermelho .
No momento, acho que os indicadores mais relevantes de desempenho sem brilho são:
- Existem 800.000 linhas
- A operação funciona em uma linha por vez
- Os dados estão sendo salvos no SQL Server, portanto, "[usa] valores de algumas colunas para manipular outras colunas " [minha fase é ;-)]
Eu suspeito que:
- Embora exista espaço para melhorias na recuperação e nos cálculos de dados, torná-los melhores não equivaleria a uma redução significativa no tempo de processamento.
- o principal gargalo está na emissão de 800.000
UPDATE
declarações separadas , que são 800.000 transações separadas.
Minha recomendação (com base nas informações atualmente disponíveis):
Sua maior área de melhoria seria atualizar várias linhas ao mesmo tempo (ou seja, em uma transação). Você deve atualizar seu processo para trabalhar em termos de cada FileID
um deles RowID
. Então:
- leia todas as 4000 linhas de um determinado
FileID
em uma matriz
- a matriz deve conter elementos representando os campos que estão sendo manipulados
- percorrer a matriz, processando cada linha como você faz atualmente
- uma vez que todas as linhas da matriz (ou seja, para este particular
FileID
) tenham sido calculadas:
- iniciar uma transação
- chame cada atualização por cada
RowID
- se não houver erros, confirme a transação
- se ocorreu um erro, reverter e manipular adequadamente
Se o seu índice de cluster ainda não estiver definido como (FileID, RowID)
você deve considerar isso (como @MikaelEriksson sugeriu em um comentário sobre a Pergunta). Isso não ajudará essas UPDATEs singleton, mas pelo menos melhoraria um pouco as operações agregadas, como o que você está fazendo nesse procedimento armazenado "filtro", pois todas elas são baseadas FileID
.
Você deve considerar mover a lógica para uma linguagem compilada. Eu sugeriria a criação de um aplicativo .NET WinForms ou mesmo do console. Prefiro o Console App, pois é fácil agendar via SQL Agent ou Windows Scheduled Tasks. Não importa se é feito em VB.NET ou C #. O VB.NET pode ser um ajuste mais natural para o seu desenvolvedor, mas ainda haverá alguma curva de aprendizado.
Não vejo nenhuma razão neste momento para mudar para SQLCLR. Se o algoritmo for alterado com frequência, isso seria irritante e teria que reimplantar o Assembly o tempo todo. A reconstrução de um aplicativo de console e a colocação do .exe na pasta compartilhada adequada na rede, de modo que você execute o mesmo programa e sempre esteja atualizado, deve ser bastante fácil de fazer.
Eu não acho que mover o processamento totalmente para o T-SQL ajudaria se o problema é o que suspeito e você está apenas fazendo uma atualização de cada vez.
Se o processamento for movido para o .NET, você poderá usar TVPs (Parâmetros com Valor de Tabela) para passar a matriz para um Stored Procedure que chamaria um UPDATE
que JOINs para a variável de tabela TVP e, portanto, é uma transação única. . O TVP deve ser mais rápido do que fazer 4000 INSERT
s agrupados em uma única transação. Mas o ganho resultante do uso de TVPs acima de 4000 INSERT
s em uma transação provavelmente não será tão significativo quanto a melhoria observada ao passar de 800.000 transações separadas para apenas 200 transações de 4000 linhas cada.
A opção TVP não está disponível nativamente para o lado do VBA, mas alguém apresentou uma solução alternativa que pode valer a pena testar:
Como melhoro o desempenho do banco de dados ao passar do VBA para o SQL Server 2008 R2?
SE o processo de filtro estiver sendo usado apenas FileID
na WHERE
cláusula, e se esse processo estiver realmente sendo chamado por cada linha, você poderá economizar algum tempo de processamento armazenando em cache os resultados da primeira execução e usando-os pelo restante das linhas FileID
, certo?
Depois de conseguir o processamento feito por FileID , então podemos começar a falar de processamento paralelo. Mas isso pode não ser necessário nesse momento :). Dado que você está lidando com três partes não ideais bastante importantes: transações Excel, VBA e 800k, qualquer conversa sobre SSIS, paralelogramos ou quem sabe o que é otimização prematura / coisas do tipo carroça antes do cavalo . Se conseguirmos reduzir esse processo de 7 horas para 10 minutos ou menos, você ainda estaria pensando em outras maneiras de torná-lo mais rápido? Existe um prazo de conclusão que você tem em mente? Lembre-se de que, uma vez concluído o processamento em um FileID Por isso, se você tivesse um aplicativo de console do VB.NET (ou seja, linha de comando .EXE), não haveria nada impedindo a execução de alguns desses FileIDs por vez :), seja pela etapa CmdExec do SQL Agent ou pelas Tarefas agendadas do Windows, etc.
E, você sempre pode adotar uma abordagem em fases e fazer algumas melhorias de cada vez. Por exemplo, começando com as atualizações FileID
e, portanto, usando uma transação para esse grupo. Então, veja se você consegue fazer o TVP funcionar. Em seguida, veja como pegar esse código e movê-lo para o VB.NET (e os TVPs funcionam no .NET para que sejam portados corretamente).
O que não sabemos ainda pode ajudar:
- O procedimento armazenado "filtro" é executado por RowID ou FileID ? Temos a definição completa desse procedimento armazenado?
- Esquema completo da tabela. Qual a largura dessa mesa? Quantos campos de comprimento variável existem? Quantos campos são NULLable? Se algum for NULLable, quantos contêm NULLs?
- Índices para esta tabela. É particionado? A compactação ROW ou PAGE está sendo usada?
- Qual é o tamanho dessa tabela em termos de MB / GB?
- Como a manutenção de índice é tratada para esta tabela? Quão fragmentados são os índices? Quão atualizadas são as estatísticas?
- Algum outro processo grava nesta tabela enquanto esse processo de 7 horas está ocorrendo? Possível fonte de discórdia.
- Algum outro processo é lido nesta tabela enquanto esse processo de 7 horas está ocorrendo? Possível fonte de discórdia.
ATUALIZAÇÃO 1:
** Parece haver alguma confusão sobre o que VBA (Visual Basic for Applications) e o que pode ser feito com ele, portanto, isso é apenas para garantir que estamos todos na mesma página da web:
ATUALIZAÇÃO 2:
Mais um ponto a considerar: como as conexões estão sendo tratadas? O código VBA está abrindo e fechando a conexão a cada operação ou abre a conexão no início do processo e fecha no final do processo (ou seja, 7 horas depois)? Mesmo com o pool de conexões (que, por padrão, deve estar habilitado para o ADO), ainda deve haver um grande impacto entre abrir e fechar uma vez, em vez de abrir e fechar 800.200 ou 1.600.000 vezes. Esses valores são baseados em pelo menos 800.000 UPDATEs mais 200 ou 800k EXECs (dependendo da frequência com que o procedimento armazenado do filtro está realmente sendo executado).
Esse problema de muitas conexões é mitigado automaticamente pela recomendação que descrevi acima. Ao criar uma transação e realizar todas as atualizações dentro dessa transação, você manterá essa conexão aberta e a reutilizará para cada uma UPDATE
. Se a conexão é mantida aberta ou não a partir da chamada inicial para obter as 4000 linhas de acordo com o especificado FileID
ou fechada após a operação "get" e aberta novamente para as UPDATEs, é muito menos impactante, pois agora estamos falando de uma diferença de 200 ou 400 conexões totais em todo o processo.
ATUALIZAÇÃO 3:
Eu fiz alguns testes rápidos. Lembre-se de que este é um teste de pequena escala e não exatamente a mesma operação (puro INSERT vs EXEC + UPDATE). No entanto, as diferenças de tempo relacionadas à maneira como as conexões e transações são tratadas ainda são relevantes, portanto, as informações podem ser extrapoladas para causar um impacto relativamente semelhante aqui.
Parâmetros de teste:
- SQL Server 2012 Developer Edition (64 bits), SP2
Mesa:
CREATE TABLE dbo.ManyInserts
(
RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
SomeValue BIGINT NULL
);
Operação:
INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
- Total de inserções por cada teste: 10.000
- Redefinições para cada teste:
TRUNCATE TABLE dbo.ManyInserts;
(dada a natureza desse teste, executar o FREEPROCCACHE, FREESYSTEMCACHE e DROPCLEANBUFFERS não parecia agregar muito valor).
- Modelo de recuperação: SIMPLES (e talvez 1 GB grátis no arquivo de log)
- Os testes que usam transações usam apenas uma única conexão, independentemente de quantas transações.
Resultados:
Test Milliseconds
------- ------------
10k INSERTs across 10k Connections 3968 - 4163
10k INSERTs across 1 Connection 3466 - 3654
10k INSERTs across 1 Transaction 1074 - 1086
10k INSERTs across 10 Transactions 1095 - 1169
Como você pode ver, mesmo que a conexão do ADO ao banco de dados já esteja sendo compartilhada em todas as operações, é garantido que o agrupamento em lotes usando uma transação explícita (o objeto ADO deve ser capaz de lidar com isso) é significativamente garantido (ou seja, mais de 2x melhoria) reduza o tempo total do processo.