Executar procedimentos armazenados em paralelo


9

Eu estou olhando para tentar executar o mesmo procedimento armazenado várias vezes com parâmetros diferentes, mas ao mesmo tempo.

Estou usando o SQL 2014

A razão para isso é que o procedimento leva cerca de 7 horas para ser concluído. Na verdade, ele faz o mesmo processo várias vezes. Por exemplo, ele pode criar um novo banco de dados e tabelas para cada filial.

O que eu quero fazer é quebrar o procedimento armazenado para que eu possa executar por filial, mas depois executar cada consulta em paralelo. Eu testei isso executando-o em janelas de consulta separadas e é executado quase 80% mais rápido.

Alguém pode me dar um guia de manequins para executar consultas em paralelo?

Respostas:


8

Em um ponto, respondi a essa pergunta no StackOverflow , mas parece que seria útil ter essas informações no DBA.SE também, revisadas e atualizadas.

Apenas para ser totalmente explícito: o TSQL não possui (por si só) a capacidade de iniciar outras operações do TSQL de forma assíncrona .

Isso não significa que você ainda não tem muitas opções (algumas delas mencionadas em outras respostas):

  • Tarefas do SQL Agent : crie várias tarefas SQL e agende-as para serem executadas no horário desejado ou inicie-as de forma assíncrona a partir de um processo armazenado "controle mestre" sp_start_job. Se você precisar monitorar seu progresso programaticamente, verifique se os trabalhos atualizam uma tabela JOB_PROGRESS personalizada (ou verifique se eles terminaram de usar a função não documentada, xp_sqlagent_enum_jobsconforme descrito neste excelente artigo de Gregory A. Larsen). Você precisa criar quantos trabalhos separados quiser que processos paralelos sejam executados, mesmo se eles estiverem executando o mesmo processo armazenado com parâmetros diferentes.
  • Pacote SSIS : crie um pacote SSIS com um fluxo de tarefas de ramificação simples. O SSIS iniciará essas tarefas em spids individuais, que o SQL executará em paralelo.
  • Aplicativo personalizado : escreva um aplicativo personalizado simples no idioma de sua escolha (C #, Powershell etc.), usando os métodos assíncronos fornecidos por esse idioma. Chame um proc armazenado SQL em cada encadeamento de aplicativo.
  • Automação OLE : no SQL, use sp_oacreatee sp_oamethodpara iniciar um novo processo chamando um ao outro proc armazenado, conforme descrito neste artigo , também por Gregory A. Larsen.
  • Service Broker : consulte o Service Broker , um bom exemplo de execução assíncrona neste artigo .
  • Execução paralela de CLR : use os comandos CLR Parallel_AddSqle Parallel_Executeconforme descrito neste artigo por Alan Kaplan (somente SQL2005 +).
  • Tarefas agendadas do Windows : listadas quanto à integridade, mas não sou fã dessa opção.

Se fosse eu, provavelmente usaria vários trabalhos do SQL Agent em cenários mais simples e um pacote SSIS em cenários mais complexos.

No seu caso, a menos que você esteja tentando iniciar 200 threads separados, vários trabalhos agendados do Agente parecerão uma opção simples e gerenciável.

Um comentário final : o SQL já tenta paralelizar operações individuais sempre que pode *. Isso significa que a execução de duas tarefas ao mesmo tempo, em vez de uma após a outra, não garante que será concluída mais cedo. Teste com cuidado para ver se ele realmente melhora alguma coisa ou não.

Tivemos um desenvolvedor que criou um pacote DTS para executar 8 tarefas ao mesmo tempo. Infelizmente, era apenas um servidor com 4 CPUs :)

* Assumindo configurações padrão. Isso pode ser modificado alterando o Grau máximo de paralelismo ou Máscara de afinidade do servidor ou usando a dica de consulta MAXDOP.


2

Sua melhor aposta é criar três trabalhos separados com a mesma programação para iniciar os trabalhos ao mesmo tempo. Dependendo do que os trabalhos estão fazendo, você deve ter cuidado para monitorar o bloqueio e o deadlock.

Outra opção é criar um pacote SSIS com um número N de operadores para chamar os SPs em paralelo


2

Você poderia usar o Powershell. Supondo que você esteja trabalhando com o SQL Server, você pode fazer algo assim: (testado e limpo agora)

#This script creates a number of connections (one per entry in $Commands) 
# to a SQL Server instance ($Server) and database ($DBName)
#Driver variables


#Set Initial collections and objects    
$Server= "(local)\sql2016cs" ; #Server to connect to
$DBName = "Test" ; #Database to connect to

$Commands = @()
$Commands += "EXEC sp_LogMe 'a'"
$Commands += "EXEC sp_LogMe 'b'"

#Loop through commands array, create script block for establishing SMO connection/query
#Start-Job for each script block
foreach ($sql in $Commands ) {

# All of that extra information after "Smo" tells it to load just v12 (for when you have multiple
#   versions of SQL installed.)  Note: V13 is 2016.
 $cmdstr =@"
`Add-Type -AssemblyName "Microsoft.SqlServer.Smo,Version=$(13).0.0.0,Culture=neutral,PublicKeyToken=89845dcd8080cc91"
`[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
`$SqlConn = New-Object Microsoft.SqlServer.Management.Smo.Server ("$Server")
`$SqlConn.Databases["$DBName"].ExecuteNonQuery("$sql")
"@

#Uncomment the next like to print the command string for debugging
# $cmdstr
#Execute script block in jobs to run the command asyncronously
$cmd = [ScriptBlock]::Create($cmdstr)
Start-Job -ScriptBlock $cmd
}

Nota: Eu peguei isso de algo semelhante que fiz aqui que foi testado: https://sqlstudies.com/2016/02/24/powershell-script-to-create-multiple-sql-server-connections/

Nesse, eu estava executando um loop para criar um monte de comandos fazendo a mesma coisa. Este script usa o bloco de scripts para executar cada comando de forma assíncrona, mas com diferentes comandos reais. Para facilitar as coisas, coloquei a lista de comandos que você deseja executar em uma matriz e percorrer a matriz.


1

Eu uso um aplicativo c # com multithread Parallel.ForEachpara chamar sp com parâmetros diferentes. Tem três seções. Init, Body, localFinally

public void NearLinkParallelGeneration(avl_range avl_pending, DateTime dt_start_process)
    {
        var parallelOptions = new ParallelOptions
        {
            MaxDegreeOfParallelism = Environment.ProcessorCount + 2
        };

        // create the partition based on the input
        var partitions = Partitioner
                            .Create(
                                fromInclusive: avl_pending.begin,
                                toExclusive: avl_pending.end,
                                rangeSize: 100
                            )
                            .GetDynamicPartitions();

        Parallel.ForEach(
            source: partitions,
            parallelOptions: parallelOptions,
            localInit: () =>
            {
                NpgsqlConnection conn = new NpgsqlConnection(strConnection);
                NpgsqlCommand cmd = new NpgsqlCommand();
                try
                {
                    conn.Open();
                    cmd.Connection = conn;
                    cmd.CommandText = "SELECT * FROM avl_db.process_near_link(@begin, @end, @start_time);";
                    cmd.CommandType = CommandType.Text;

                    NpgsqlParameter p = new NpgsqlParameter("@begin", NpgsqlDbType.Bigint);
                    cmd.Parameters.Add(p);

                    p = new NpgsqlParameter("@end", NpgsqlDbType.Bigint);
                    cmd.Parameters.Add(p);

                    p = new NpgsqlParameter("@start_time", NpgsqlDbType.Timestamp);
                    p.Value = dt_start_process;
                    cmd.Parameters.Add(p);
                }
                catch (NpgsqlException ex)
                {
                    Console.WriteLine(ex.InnerException);
                }
                catch (System.Exception ex)
                {
                    Console.WriteLine(ex.InnerException);
                }

                return new { Connection = conn, Command = cmd };
            },
            body: (source, state, local) =>
            {
                if (local.Connection.State == ConnectionState.Open)
                {
                    string strResult = String.Format("From: {0} - To: {1}", source.Item1, source.Item2);
                    Console.WriteLine(strResult);

                    try
                    {
                        local.Command.Parameters["@begin"].Value = source.Item1;
                        local.Command.Parameters["@end"].Value = source.Item2;
                        local.Command.ExecuteNonQuery();
                    }
                    catch (NpgsqlException ex)
                    {
                        Console.WriteLine(ex.InnerException);
                    }
                    catch (System.Exception ex)
                    {
                        Console.WriteLine(ex.InnerException);
                    }

                    //strResult = String.Format("DONE From: {0} - To: {1}", source.Item1, source.Item2);
                    //Console.WriteLine(strResult);

                }
                return local;
            },
            localFinally: local =>
            {
                local.Command?.Dispose();
                local.Connection?.Dispose();
            }
        );
    }

1

Você também pode usar ForEach -Parallelno Powershell.

O exemplo abaixo (tirado da minha pergunta Powershell executar procedimentos armazenados em paralelo no banco de dados ) executará todos os procedimentos armazenados em um banco de dados:

Workflow TestRunParallelExecute
{
    $ServerName = "localhost"
    $DatabaseName = "testrun"
    $Procedure_Query = "select name from sys.procedures"
    $Procedure_List = (Invoke-Sqlcmd -Server $ServerName -Database $DatabaseName -Query $Procedure_Query)

    ForEach -Parallel ($Procedure in $Procedure_List.Name)
    {
         Invoke-Sqlcmd -Server $ServerName -Database $DatabaseName -Query $Procedure 
    }
}
TestRunParallelExecute
cls

0

Como isso me lembra um caso de uso que eu tinha no trabalho, vou explicar como resolvemos:

Primeiro, como já foi dito, não acho que exista um tipo de "nohup" do Unix no SQL: uma conexão = uma instrução, com tudo o que acompanha (bloqueio, confirmação, erro ...)

Encontramos nosso caminho usando o ETL Talend gratuito, configurando-o para conectar-se ao banco de dados, e executamos vários trabalhos paralelos envolvendo o procedimento armazenado.

Usamos o Iteratecomponente e o loop quantas vezes precisarmos, ativando a multi-threadsopção.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.