Ejecute procedimientos almacenados en paralelo


9

Estoy intentando probar y ejecutar el mismo procedimiento almacenado varias veces con diferentes parámetros pero al mismo tiempo.

Estoy usando SQL 2014

La razón de esto es que el procedimiento tarda alrededor de 7 horas en completarse. Realmente hace el mismo proceso muchas veces. Entonces, por ejemplo, podría construir una nueva base de datos y tablas para cada rama.

Lo que quiero hacer es desglosar el procedimiento almacenado para poder ejecutar por rama pero luego ejecutar cada consulta en paralelo. He probado esto ejecutándolo en ventanas de consulta separadas y se ejecuta casi un 80% más rápido.

¿Alguien puede darme una guía ficticia para ejecutar consultas en paralelo?

Respuestas:


8

En un momento respondí esta pregunta en StackOverflow , pero parece que sería útil tener esa información también en DBA.SE, revisada y actualizada.

Solo para ser totalmente explícito: TSQL no tiene (por sí mismo) la capacidad de lanzar otras operaciones TSQL de forma asincrónica .

Eso no significa que todavía no tenga muchas opciones (algunas de ellas mencionadas en otras respuestas):

  • Trabajos del Agente SQL : Cree múltiples trabajos SQL, y prográmelos para que se ejecuten en el momento deseado, o inícielos asíncronamente desde un proceso almacenado de "control maestro" usando sp_start_job. Si necesita monitorear su progreso programáticamente, solo asegúrese de que cada trabajo actualice una tabla JOB_PROGRESS personalizada (o puede verificar si han terminado de usar la función no documentada xp_sqlagent_enum_jobscomo se describe en este excelente artículo de Gregory A. Larsen). Debe crear tantos trabajos separados como desee que se ejecuten los procesos paralelos, incluso si están ejecutando el mismo proceso almacenado con diferentes parámetros.
  • Paquete SSIS : cree un paquete SSIS con un flujo de tareas de ramificación simple. SSIS lanzará esas tareas en spids individuales, que SQL ejecutará en paralelo.
  • Aplicación personalizada : escriba una aplicación personalizada simple en el idioma que elija (C #, Powershell, etc.), utilizando los métodos asincrónicos proporcionados por ese idioma. Llame a un proceso almacenado de SQL en cada subproceso de aplicación.
  • Automatización OLE : en SQL, use sp_oacreatey sp_oamethodpara iniciar un nuevo proceso que se llame entre sí a un proceso almacenado como se describe en este artículo , también por Gregory A. Larsen.
  • Service Broker : considere el uso de Service Broker , un buen ejemplo de ejecución asincrónica en este artículo .
  • Ejecución paralela de CLR : utilice los comandos CLR Parallel_AddSqly Parallel_Executecomo se describe en este artículo de Alan Kaplan (solo SQL2005 +).
  • Tareas programadas de Windows : enumeradas para completar, pero no soy fanático de esta opción.

Si fuera yo, probablemente usaría múltiples trabajos de Agente SQL en escenarios más simples y un paquete SSIS en escenarios más complejos.

En su caso, a menos que esté tratando de iniciar 200 subprocesos separados, varios trabajos programados del Agente suenan como una opción simple y manejable.

Un comentario final : SQL ya intenta paralelizar operaciones individuales siempre que puede *. Esto significa que ejecutar 2 tareas al mismo tiempo en lugar de una tras otra no garantiza que terminará antes. Pruebe cuidadosamente para ver si realmente mejora algo o no.

Tuvimos un desarrollador que creó un paquete DTS para ejecutar 8 tareas al mismo tiempo. Desafortunadamente, solo era un servidor de 4 CPU :)

* Asumiendo la configuración predeterminada. Esto puede modificarse alterando el Grado máximo de paralelismo o la Máscara de afinidad del servidor, o utilizando la sugerencia de consulta MAXDOP.


2

Su mejor opción es crear tres trabajos separados con el mismo horario para comenzar los trabajos al mismo tiempo. Dependiendo de lo que estén haciendo los trabajos, debe tener cuidado de monitorear el bloqueo y el interbloqueo.

Otra opción es crear un paquete SSIS con N número de operadores para llamar a los SP en paralelo


2

Podrías usar Powershell. Suponiendo que está trabajando con SQL Server, podría hacer algo como esto: (probado y limpiado ahora)

#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: Tomé esto de algo similar que hice aquí que se prueba: https://sqlstudies.com/2016/02/24/powershell-script-to-create-multiple-sql-server-connections/

En ese, estaba ejecutando un bucle para crear un montón de comandos haciendo lo mismo. Este script utiliza el bloque de script para ejecutar cada comando de forma asincrónica pero con diferentes comandos reales. Para facilitar las cosas, puse la lista de comandos que desea ejecutar en una matriz y recorro la matriz.


1

Uso una aplicación C # con multiproceso Parallel.ForEachpara llamar a sp con diferentes parámetros. Tiene tres secciones. Init, cuerpo, local finalmente

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

También puedes usarlo ForEach -Parallelen Powershell.

El siguiente ejemplo (tomado de mi pregunta Powershell Ejecutar procedimientos almacenados en paralelo en la base de datos ) ejecutará todos los procedimientos almacenados en una base de datos:

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 esto me recuerda un caso de uso que tenía en el trabajo, explicaré cómo lo resolvemos:

Primero, como ya dije, no creo que exista ningún tipo de "nohup" de Unix en SQL: una conexión = una declaración, con todo lo que acompaña (bloqueo, confirmación, error ...)

Encontramos nuestro camino usando el ETL Talend gratuito, configurándolo para conectarse a la base de datos, y ejecutamos un montón de trabajos paralelos que envuelven el procedimiento almacenado.

Utilizamos el Iteratecomponente y el bucle tantas veces como sea necesario, habilitando la multi-threadsopción.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.