Запускайте хранимые процедуры параллельно


9

Я пытаюсь запустить одну и ту же хранимую процедуру несколько раз с разными параметрами, но в то же время.

Я использую SQL 2014

Причина в том, что процедура занимает около 7 часов. Это на самом деле делает один и тот же процесс много раз. Так, например, он может построить новую базу данных и таблицы для каждой ветви.

То, что я хочу сделать, это разбить хранимую процедуру, чтобы я мог работать в каждой ветви, но затем выполнять каждый запрос параллельно. Я проверил это, запустив его в отдельных окнах запросов, и он работает почти на 80% быстрее.

Кто-нибудь может дать мне руководство по выполнению запросов параллельно?

Ответы:


8

В какой-то момент я ответил на этот вопрос в StackOverflow , но, похоже, было бы полезно, чтобы эта информация также была в DBA.SE, пересмотрена и обновлена.

Просто чтобы быть абсолютно четко: TSQL делает не (само по себе) имеют возможность запускать другие операции Tsql асинхронно .

Это не значит, что у вас все еще нет большого количества вариантов (некоторые из них упомянуты в других ответах):

  • Задания агента SQL : создайте несколько заданий SQL и либо запланируйте их выполнение в нужное время, либо запустите их асинхронно из хранимого процесса «главный элемент управления», используя sp_start_job. Если вам нужно контролировать их прогресс программно, просто убедитесь, что каждое из заданий обновляет пользовательскую таблицу JOB_PROGRESS (или вы можете проверить, закончили ли они еще, используя недокументированную функцию, xp_sqlagent_enum_jobsкак описано в этой замечательной статье Грегори А. Ларсена). Вам нужно создать столько отдельных заданий, сколько вы хотите, чтобы выполнялись параллельные процессы, даже если они запускают один и тот же хранимый процесс с разными параметрами.
  • Пакет служб SSIS : создайте пакет служб SSIS с простым потоком задач ветвления. Службы SSIS будут запускать эти задачи в отдельных спидах, которые SQL будет выполнять параллельно.
  • Пользовательское приложение : напишите простое пользовательское приложение на языке по вашему выбору (C #, Powershell и т. Д.), Используя асинхронные методы, предоставляемые этим языком. Вызовите хранимую процедуру SQL в каждом потоке приложения.
  • Автоматизация OLE : В SQL используйте sp_oacreateи sp_oamethodдля запуска нового процесса, вызывающего друг друга хранимым процессом, как описано в этой статье , также Грегори А. Ларсен.
  • Компонент Service Broker . Рассмотрим использование компонента Service Broker , который является хорошим примером асинхронного выполнения в этой статье .
  • CLR Параллельное выполнение : С помощью команды CLR Parallel_AddSqlи , Parallel_Executeкак описано в этой статье Алан Каплан (SQL2005 + только).
  • Запланированные задачи Windows : перечислены для полноты, но я не фанат этой опции.

Если бы это был я, я бы, вероятно, использовал несколько заданий агента SQL в более простых сценариях и пакет служб SSIS в более сложных сценариях.

В вашем случае, если вы не пытаетесь запустить 200 отдельных потоков, несколько запланированных заданий агента звучат как простой и управляемый выбор.

Последний комментарий : SQL уже пытается распараллеливать отдельные операции, когда это возможно *. Это означает, что выполнение 2 задач одновременно, а не после друг друга, не гарантирует, что оно завершится раньше. Проверьте внимательно, чтобы увидеть, действительно ли это что-то улучшает или нет.

У нас был разработчик, который создал пакет DTS для запуска 8 задач одновременно. К сожалению, это был только 4-х процессорный сервер :)

* Предполагая настройки по умолчанию. Это можно изменить, изменив максимальную степень параллелизма или маску сходства на сервере, или воспользовавшись подсказкой запроса MAXDOP.


2

Лучше всего создать три отдельных рабочих места с одинаковым графиком, чтобы начать работу одновременно. В зависимости от выполняемых заданий вы должны внимательно следить за блокировкой и взаимоблокировкой.

Другой вариант - создать пакет служб SSIS с N числом операторов для параллельного вызова SP.


2

Вы могли бы использовать Powershell. Предполагая, что вы работаете с SQL Server, вы можете сделать что-то вроде этого: (протестировано и исправлено)

#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
}

Примечание: я взял это из чего-то похожего, что я сделал здесь, который тестируется: https://sqlstudies.com/2016/02/24/powershell-script-to-create-multiple-sql-server-connections/

В этом я выполнял цикл, чтобы создать кучу команд, делающих одно и то же. Этот сценарий использует блок сценария для выполнения каждой команды асинхронно, но с разными фактическими командами. Чтобы упростить задачу, я поместил список команд, которые вы хотите запустить, в массив и переберите массив.


1

Я использую приложение C # с многопоточностью Parallel.ForEachдля вызова sp с различными параметрами. Есть три раздела. 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

Вы также можете использовать ForEach -Parallelв Powershell.

В приведенном ниже примере (взятом из моего вопроса Powershell параллельно выполнять хранимые процедуры в базе данных ) будут запускаться все хранимые процедуры в базе данных:

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

Поскольку это напоминает мне случай использования, который я имел на работе, я опишу, как мы его решаем:

Во-первых, как уже было сказано, я не думаю, что в SQL существует подобный Unix "nohup" -подобный: одно соединение = один оператор, со всем, что происходит (блокировка, фиксация, ошибка ...)

Мы находим наш путь, используя бесплатный ETL Talend, настраивая его для подключения к БД, и запускаем несколько параллельных заданий, оборачивая хранимую процедуру.

Мы использовали Iterateкомпонент и цикл столько раз, сколько нам нужно, чтобы включить эту multi-threadsопцию.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.