SQL Server: not only SQL, асинхронний виклик команд
— Алло, швидка?!! У нас людина штопор проковтнула, що нам робити?!!
— Ну-у... Спробуйте відкривати виделкою!
Вітаю, спільното! Мене звати Володимир Жадан, я працюю з Microsoft SQL Server понад 15 років і хотів би поділитися думками щодо використання деяких його можливостей, які часто залишаються поза увагою розробників.
Вступ
Ця стаття призначена радше для розробників бекенду або архітекторів, бо, як правило, розробники/адміністратори баз даних знають про механізми, описані в статті. Як і в епіграфі, будемо намагатись застосувати інструменти не за призначенням, бо жоден з описаних механізмів не розроблявся саме для асинхронного виклику, але вони дозволяють його реалізувати.
До мене кілька разів зверталися розробники на мейнстрімних мовах з питанням, як можна в SQL Server реалізувати асинхронний запуск SQL-коду. Мене дивувало, чому з цим питанням звертаються саме вони, оскільки в цих мовах майже завжди є якісь async/await або їх аналоги, через які все це реалізується елементарно на рівні застосунку. Саме на тому в цих випадках і зупинялись.
Проте інколи виникає потреба запустити якийсь код асинхронно з-під SQL.
Звичайно, неважко асинхронне виконання реалізувати самотужки, наприклад, як запуск зовнішнього файлу через xp_cmdshell. Або створивши таблицю в ролі черги, куди поточний процес буде додавати рядки, в яких містимуться параметри завдання, і окремий процес для їх виконання (найпростіше — це процедура, або SQL-скрипт, запущені в SQL Server Agent або в сторонній програмі, які читають таблицю і виконують завдання).
Але в SQL Server є і власні механізми, які хоч і призначені для інших цілей, можуть використовуватись для асинхронного виклику. Два з них доволі широко описані в інтернеті, але я наведу приклади їх використання, щоб при бажанні їх спробувати не потрібно було ходити до інших джерел. Це запуск через Service Broker і через SQL Server Agent.
В цій статті я хотів описати їх та ще пару способів, про які згадують менше, і дати приклади асинхронного виклику SQL-команд з їх використанням. Кожен з механізмів потребує деякої підготовчої роботи, тому тут це реалізувати складніше, ніж в мовах загального призначення.
Створення оточення для тестування
Щоб продемонструвати дію та порівняти швидкість запуску механізмів, створимо для початку в якій-небудь базі (я створив окрему базу AsyncTest для цієї демонстрації) таблицю, куди будемо логувати роботу наших скриптів:
CREATE TABLE dbo.AsyncCallsLog( ExecutionId int IDENTITY(1,1) PRIMARY KEY CLUSTERED, AsyncMethod varchar(50) NOT NULL, StartMainScriptTime datetime2(3) NOT NULL DEFAULT (sysdatetime()), FinishMainScriptTime datetime2(3) NULL, FinishAsyncCallTime datetime2(3) NULL, [MainScriptDuration, ms] AS datediff(millisecond,StartMainScriptTime,FinishMainScriptTime), [TotalDuration, ms] AS datediff(millisecond,StartMainScriptTime,FinishAsyncCallTime), [AsyncEngineDuration, ms] AS datediff(millisecond,FinishMainScriptTime,FinishAsyncCallTime)-10000 ) GO
Тепер нам потрібна процедура, яка зареєструє в цій таблиці факт старту скрипта, запише назву механізму, який ми тестуємо і поверне значення первинного ключа для ідентифікації рядка, куди будемо писати час закінчення основної та «асинхронної» частини виконання коду:
CREATE PROCEDURE dbo.AsyncCallStart @AsyncMethod varchar(50), @ExecutionId int OUTPUT AS INSERT INTO dbo.AsyncCallsLog (AsyncMethod) VALUES (@AsyncMethod) SET @ExecutionId = @@IDENTITY GO
І, нарешті процедуру, яку ми будемо запускати асинхронно. В неї вставлено паузу 10 секунд, щоб наочно побачити, що ця процедура закінчується після основного скрипта, і зазначити час закінчення, який ми використаємо для оцінки швидкодії механізму асинхронного запуску:
CREATE PROCEDURE dbo.AsyncCallExecute @ExecutionId int AS BEGIN -- Pause for 10 seconds WAITFOR DELAY '00:00:10'; UPDATE dbo.AsyncCallsLog SET FinishAsyncCallTime = SYSDATETIME() WHERE ExecutionId = @ExecutionId END GO
Можна було, звичайно, передавати як параметр текст SQL-інструкції для виконання, але задля безпеки не будемо робити аналог динамічного SQL.
Здається, все.
Service Broker
Service Broker — це вбудований механізм асинхронного обміну повідомленнями між базами або/та серверами SQL Server, який дозволяє надіслати повідомлення в чергу, яку потім обробляє спеціальний активатор або отримувач повідомлень. Працює всередині SQL Server, без потреби зовнішніх агентів чи додаткового коду.
Основні компоненти:
- Message Types — визначають формат повідомлень.
- Contracts — описують, які типи повідомлень можна надсилати між учасниками.
- Queues — черги для зберігання повідомлень.
- Services — логічні точки надсилання/отримання повідомлень.
- Activation Procedures — процедури, що автоматично запускаються при появі нового повідомлення в черзі.
Алгоритм роботи:
- Застосунок або T-SQL процедура відправляє повідомлення у queue.
- SQL Server зберігає повідомлення та гарантує його доставку.
- Коли в черзі з’являється повідомлення, може бути викликана activation procedure, яка обробляє його асинхронно.
Щоб реалізувати асинхронний запуск коду, потрібно створити кожен з основних компонентів.
Давайте спробуємо це.
Передусім перевіримо, чи увімкнено в базі Service Broker. Якщо ні, то увімкнемо.
-- 0. Enable Service Broker on the database if not already enabled IF NOT EXISTS ( SELECT * FROM sys.databases WHERE name = 'AsyncTest' AND is_broker_enabled = 1 ) BEGIN ALTER DATABASE [AsyncTest] SET ENABLE_BROKER WITH NO_WAIT; END Тепер створимо тип повідомлення, контракт, чергу, сервіс і процедуру активації: -- 1. Create a message type CREATE MESSAGE TYPE [AsyncProcMessage] VALIDATION = NONE; GO -- 2. Create a contract CREATE CONTRACT [AsyncProcContract] ([AsyncProcMessage] SENT BY INITIATOR); GO -- 3. Create a queue CREATE QUEUE [AsyncProcQueue]; GO -- 4. Create a service CREATE SERVICE [AsyncProcService] ON QUEUE [AsyncProcQueue] ([AsyncProcContract]); GO -- 5. Create the activation procedure CREATE PROCEDURE [dbo].[AsyncProc_Activation] AS BEGIN SET NOCOUNT ON; DECLARE @conversation_handle UNIQUEIDENTIFIER, @message_body NVARCHAR(MAX), @message_type_name SYSNAME; WHILE (1 = 1) BEGIN WAITFOR ( RECEIVE TOP(1) @conversation_handle = conversation_handle, @message_body = message_body, @message_type_name = message_type_name FROM [AsyncProcQueue] ), TIMEOUT 1000; IF @@ROWCOUNT = 0 BREAK; DECLARE @ExecutionId int = JSON_VALUE(@message_body, '$.ExecutionId'); EXEC dbo.AsyncCallExecute @ExecutionId; END CONVERSATION @conversation_handle; END END GO
І додамо у визначення черги параметри активації:
-- 6. Set queue activation ALTER QUEUE [AsyncProcQueue] WITH ACTIVATION ( STATUS = ON, PROCEDURE_NAME = [dbo].[AsyncProc_Activation], MAX_QUEUE_READERS = 100, EXECUTE AS OWNER ); GO
І, власне, код для тестування:
-- 0. Get the next ExecutionId DECLARE @ExecutionId int EXEC [dbo].[AsyncCallStart] @AsyncMethod = 'Service Broker', @ExecutionId = @ExecutionId OUTPUT SELECT @ExecutionId as N'@ExecutionId' -- 1. To execute the procedure asynchronously, send a message: DECLARE @conversation_handle UNIQUEIDENTIFIER; BEGIN DIALOG CONVERSATION @conversation_handle FROM SERVICE [AsyncProcService] TO SERVICE 'AsyncProcService' ON CONTRACT [AsyncProcContract] WITH ENCRYPTION = OFF; -- If your database does not have a master key, you must disable encryption DECLARE @message_body NVARCHAR(MAX) = ( SELECT @ExecutionId AS ExecutionId FOR JSON PATH, WITHOUT_ARRAY_WRAPPER ); -- Set parameters as XML/JSON here SEND ON CONVERSATION @conversation_handle MESSAGE TYPE [AsyncProcMessage] (@message_body); -- Optionally pass parameters as XML/JSON here -- Conversation will be ended by the activation procedure -- 2. Update the main script finish time UPDATE dbo.AsyncCallsLog SET FinishMainScriptTime = SYSDATETIME() WHERE ExecutionId = @ExecutionId GO
Процедура AsyncCallStart починає тест, записуючи в таблицю AsyncCallsLog рядок з часом початку старту і назвою механізму Service Broker і повертає параметр @ExecutionId, значення якого через тіло повідомлення @message_body в форматі JSON ми передаємо для виклику процедури AsyncCallExecute.
Запустимо цей код кілька разів, щоб зібрати якусь статистику щодо швидкості виконання, бо іноді перший запуск після довгої перерви проходить повільніше.
SQL Server Agent
Тут є два шляхи — створювати нове завдання (job) або запустити існуюче, тільки в цьому випадку, якщо воно вже запущене, повторно викликати його не вдасться.
Приклад коду, який для асинхронного виклику створює нову джобу:
-- Create a SQL Server Agent Job that deletes itself after successful execution -- 0. Get the next ExecutionId DECLARE @ExecutionId int EXEC [dbo].[AsyncCallStart] @AsyncMethod = 'SQL Server Agent (new job)', @ExecutionId = @ExecutionId OUTPUT SELECT @ExecutionId as N'@ExecutionId' -- 1. Create the job DECLARE @command NVARCHAR(MAX) = N'EXEC dbo.AsyncCallExecute @ExecutionId = ' + CAST(@ExecutionId AS NVARCHAR(10)); DECLARE @jobName NVARCHAR(128) = N'AsyncCallJob_' + CAST(@ExecutionId AS NVARCHAR(10)); DECLARE @jobId UNIQUEIDENTIFIER; EXEC msdb.dbo.sp_add_job @job_name = @jobName, @enabled = 1, @owner_login_name = N'sa', @delete_level = 1, -- Delete the job when it succeeds @job_id = @jobId OUTPUT; -- 2. Add a job step EXEC msdb.dbo.sp_add_jobstep @job_id = @jobId, @step_name = N'AsyncCallStep', @subsystem = N'TSQL', @database_name=N'AsyncTest', @command = @command; -- 3. Add the job to the server EXEC msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' -- 4. Start the job EXEC msdb.dbo.sp_start_job @job_name = @jobName; -- 5. Update the main script finish time UPDATE dbo.AsyncCallsLog SET FinishMainScriptTime = SYSDATETIME() WHERE ExecutionId = @ExecutionId GO
Параметр @delete_level ставимо 1, щоб у разі помилки завдання не видалялось і можна було проаналізувати повідомлення про помилку.
Запустимо скрипт кілька разів, щоб зібрати якусь статистику.
І приклад скрипту для створення постійно існуючого завдання. Спочатку створимо джобу:
-- 1. Create the job DECLARE @command NVARCHAR(MAX) = N'EXEC dbo.AsyncCallExecute @ExecutionId = 1'; DECLARE @jobName NVARCHAR(128) = N'AsyncCallJob'; DECLARE @jobId UNIQUEIDENTIFIER; EXEC msdb.dbo.sp_add_job @job_name = @jobName, @enabled = 1, @owner_login_name = N'sa', @delete_level = 0, @job_id = @jobId OUTPUT; -- 2. Add a job step EXEC msdb.dbo.sp_add_jobstep @job_id = @jobId, @step_name = N'AsyncCallStep', @subsystem = N'TSQL', @database_name=N'AsyncTest', @command = @command; -- 3. Add the job to the server EXEC msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' GO
І для запуску з передачею параметра:
-- 0. Get the next ExecutionId DECLARE @ExecutionId int EXEC [dbo].[AsyncCallStart] @AsyncMethod = 'SQL Server Agent (existing job)', @ExecutionId = @ExecutionId OUTPUT SELECT @ExecutionId as N'@ExecutionId' -- 1. Update the job step command DECLARE @command NVARCHAR(MAX) = N'EXEC dbo.AsyncCallExecute @ExecutionId = ' + CAST(@ExecutionId AS NVARCHAR(10)) EXEC msdb.dbo.sp_update_jobstep @job_name = 'AsyncCallJob', @step_id=1 , @command=@command -- 2. Start the job EXEC msdb.dbo.sp_start_job @job_name = 'AsyncCallJob' -- 3. Update the main script finish time UPDATE dbo.AsyncCallsLog SET FinishMainScriptTime = SYSDATETIME() WHERE ExecutionId = @ExecutionId
Також виконаємо скрипт кілька разів.
SSIS Catalog
SSIS (SQL Server Integration Services) Catalog вперше з’явився в SQL Server 2012, до цього пакети зберігались як файли або в базі msdb.
В SSIS SQL код запускається через задачу Execute SQL Task, тобто для підготовки нам потрібно в новому або існуючому проєкті створити пакет з єдиною задачею Execute SQL Task і опублікувати цей пакет в проєкті SSIS Catalog.
Створимо в новому або існуючому SSIS проєкті пакет AsyncCallPackage.dtsx:

В параметри пакета додамо параметр ExecutionId, який буде передаватись в нашу процедуру:


Опублікуємо цей пакет на сервері в SSIS Catalog в складі існуючого або нового проєкта.

Тепер ми можемо запускати нашу процедуру асинхронно за допомогою такого коду:
-- 0. Get the next ExecutionId DECLARE @ExecutionId int EXEC [dbo].[AsyncCallStart] @AsyncMethod = 'SSIS Catalog', @ExecutionId = @ExecutionId OUTPUT SELECT @ExecutionId as N'@ExecutionId' -- 1. Create the execution DECLARE @execution_id bigint EXEC [SSISDB].[catalog].[create_execution] @package_name=N'AsyncCallPackage.dtsx', @execution_id=@execution_id OUTPUT, @folder_name=N'AsyncCall', @project_name=N'AsyncCall' EXEC [SSISDB].[catalog].[set_execution_parameter_value] @execution_id, @object_type=30, @parameter_name=N'ExecutionId', @parameter_value=@ExecutionId -- 2. Start the execution EXEC [SSISDB].[catalog].[start_execution] @execution_id -- 3. Update the main script finish time UPDATE dbo.AsyncCallsLog SET FinishMainScriptTime = SYSDATETIME() WHERE ExecutionId = @ExecutionId GO
Також зробимо кілька запусків.
Можна помітити, що перший запуск проходить трішки довше, ніж наступні. Ймовірно, це пов’язано з очисткою кеша (JIT, плани виконання, дані в пам’яті, завантажені DLL) після довгого «простою».
Це наразі все щодо «рідних» засобів асинхронного запуску коду в Transact-SQL.
Першою думкою буде питання, а як щодо SQL CLR та sp_execute_external_script? Там використовуються мови високого рівня, які мають вбудовані засоби для асинхронного виконання.
SQL CLR
Щодо SQL CLR. Якщо, наприклад, опублікувати таку SQL CLR процедуру з PERMISSION_SET = SAFE,
using Microsoft.SqlServer.Server;
using System;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Threading;
public partial class StoredProcedures
{
[SqlProcedure]
public static void AsyncCallThread(SqlInt32 ExecutionId)
{
string connStr = "context connection=true";
Thread thread = new Thread(() =>
{
try
{
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand($"EXEC dbo.AsyncCallExecute @ExecutionId = {ExecutionId.Value}", conn))
{
cmd.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
SqlContext.Pipe.Send($"Error calling service: {ex.Message}");
}
});
thread.IsBackground = true;
thread.Start();
}
}
то при її виклику
DECLARE @ExecutionId int EXEC [dbo].[AsyncCallStart] @AsyncMethod = 'SQL CLR Thread', @ExecutionId = @ExecutionId OUTPUT SELECT @ExecutionId as N'@ExecutionId' EXEC [dbo].[AsyncCallThread] @ExecutionId = @ExecutionId GO
ми отримаємо помилку:
Msg 6522, Level 16, State 1, Procedure dbo.AsyncCallThread, Line 0 A .NET Framework error occurred during execution of user-defined routine or aggregate "AsyncCallThread": System.Security.HostProtectionException: Attempted to perform an operation that was forbidden by the CLR host. The protected resources (only available with full trust) were: All The demanded resources were: SelfAffectingThreading System.Security.HostProtectionException: at StoredProcedures.AsyncCallThread(SqlInt32 ExecutionId) .
У SQL Server CLR код виконується всередині спеціального «hosted CLR» середовища, і за замовчуванням заборонені певні речі, в тому числі створення нових потоків і асинхронне виконання в наборі дозволів SAFE та EXTERNAL_ACCESS.
Якщо ж ви спробуєте надати вашій збірці набір дозволів UNSAFE:
ALTER ASSEMBLY [AsyncCall] WITH PERMISSION_SET = UNSAFE
То запуск завершиться без помилок, але цільова процедура виконана не буде, потік піде в «нікуди»:

Але коли ми змінимо рядок з’єднання з «context connection=true» на звичайний рядок зовнішнього з’єднання типу «Data Source=<server>;Initial Catalog=<database>; Integrated Security=True», то все працює.
Причина в наступному. «context connection=true» — це спеціальний режим, коли CLR-код не відкриває «новий» TCP-конект, а користується тим самим внутрішнім з’єднанням, через яке викликається наша процедура. Усі команди в такому режимі виконуються синхронно в рамках того ж сесійного контексту. І, наприклад, якщо ми запускаємо новий потік і там теж користуємося «context connection=true», то ми намагаємось відкрити друге логічне з’єднання поверх того ж сесійного контексту, який уже «зайнятий» нашим CLR-викликом. SQL Server це не дозволяє — ми отримуємо винятки типу приведеного вище при наборі дозволів SAFE та EXTERNAL_ACCESS. А при UNSAFE SQL Server просто не дозволяє другому потоку зайти у той самий контекст, і виклик ігнорується.
У випадку зовнішнього з’єднання відкривається новий TCP-конект до SQL Server. Він працює як клієнтський. Провайдер SqlClient у цьому режимі повністю підтримує async/await. SQL CLR запускає новий потік, відкриває справжнє з’єднання до SQL Server і асинхронно виконує запит.
Ось приклад SQL CLR процедури з використанням async/await:
[SqlProcedure]
public static void AsyncCallAsyncAwait(SqlString connectionString, SqlInt32 ExecutionId)
{
_ = ExecuteAsync(connectionString.Value, ExecutionId.Value);
}
private static async Task ExecuteAsync(string connectionString, int executionId)
{
try
{
using (var conn = new SqlConnection(connectionString))
{
await conn.OpenAsync().ConfigureAwait(false);
using (SqlCommand cmd = new SqlCommand($"EXEC dbo.AsyncCallExecute @ExecutionId = {executionId}", conn))
{
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
SqlContext.Pipe.Send($"Error async calling: {ex.Message}");
}
}
Приклад запуску:
-- 0. Get the next ExecutionId DECLARE @ExecutionId int DECLARE @connectionString nvarchar(4000) = N'Data Source=' + @@SERVERNAME + N';Initial Catalog=' + DB_NAME() + N';Integrated Security=True' EXEC [dbo].[AsyncCallStart] @AsyncMethod = 'SQL CLR', @ExecutionId = @ExecutionId OUTPUT SELECT @ExecutionId as N'@ExecutionId' -- 1. Start the async execution EXEC [dbo].[AsyncCallAsyncAwait] @connectionString = @connectionString, @ExecutionId = @ExecutionId -- 2. Update the main script finish time UPDATE dbo.AsyncCallsLog SET FinishMainScriptTime = SYSDATETIME() WHERE ExecutionId = @ExecutionId GO
Є ще варіант спробувати асинхронне виконання через sp_execute_external_script, але тут нас чекає невдача.
Всередині sp_execute_external_script код запускається не як звичайний процес, а під SQL Server Launchpad, у sandbox-оточенні.
Наприклад, для Python ми можемо використати асинхронні бібліотеки asyncio або aioodbc, але реально асинхронного ефекту з точки зору SQL Server ми не отримаємо, бо, по-перше, sp_execute_external_script завжди блокує виконання, поки Python-код не завершиться; по-друге, навіть якщо ви запустите в Python asyncio.create_task(...), по виходу з sp_execute_external_script процес Python буде знищений і задачі обірвуться.
Тобто asyncio можна використати для паралельної обробки усередині самого скрипта (наприклад, ходити одночасно у кілька джерел даних), але запустити процедуру у SQL Server «у фоні» і відпустити через sp_execute_external_script — ні, бо канал із SQL Server повинен отримати відповідь, перш ніж закрити сесію.
Але, раз ми уже дійшли до цього, то залишимо приклад коду для асинхронного запуску SQL запиту всередині sp_execute_external_script:
-- 0. Get the next ExecutionId
DECLARE @ExecutionId int
EXEC [dbo].[AsyncCallStart]
@AsyncMethod = 'External Script',
@ExecutionId = @ExecutionId OUTPUT
SELECT @ExecutionId as N'@ExecutionId'
-- 1. Start the external script
EXEC sp_execute_external_script
@language = N'Python',
@script = N'
import pyodbc
import asyncio
server = "..."
database = "..."
username = "..."
password = "..."
conn_str = f"DRIVER={{ODBC Driver 17 for SQL Server}};Server={server};Database={database};UID={username};PWD={password};"
sql = f"EXEC dbo.AsyncCallExecute @ExecutionId = {ExecutionId}"
def run_proc(conn_str, sql):
with pyodbc.connect(conn_str, autocommit = True) as conn:
with conn.cursor() as cursor:
cursor.execute(sql)
async def run_proc_async(conn_str, sql):
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, run_proc, conn_str, sql)
asyncio.run(run_proc_async(conn_str, sql))
',
@params = N'@ExecutionId int',
@ExecutionId = @ExecutionId;
Давайте спробуємо проаналізувати результати виконання наших скриптів.

Думаю, з назв і даних колонок зрозуміло їх зміст. Про всяк випадок додам пояснення щодо колонок з часом.
StartMainScriptTime — час старту основного скрипта, який запускає асинхронний виклик.
FinishMainScriptTime — час закінчення основного скрипта.
FinishAsyncCallTime — час закінчення виконання коду, який був запущений асинхронно. [MainScriptDuration, ms] — тривалість виконання основного скрипта в мілісекундах. [TotalDuration, ms] — проміжок часу від старту основного скрипта до закінчення виконання асинхронної частини.
[AsyncEngineDuration, ms] — умовно-приблизний час виконання асинхронного виклику саме частинами SQL Server, через які реалізовано асинхронність, дорівнює [TotalDuration, ms] — [AsyncEngineDuration, ms] — 10000 ms (пауза 10 секунд в процедурі dbo.AsyncCallExecute), дозволяє відносно оцінити швидкість відпрацювання механізму.
Які можна зробити попередні висновки?
- «Старт» кожного «механізму» йде довше, ніж наступні виклики. Напевно, для цього є і загальні, і окремі причини, властиві саме цьому механізму.
- Обробка черги Service Broker в «асинхронній» частині йде з затримками, можливо, через взаємні блокування, що потребує додаткового дослідження. Проте основний скрипт найшвидший серед усіх.
- Очікувано, що виконання через SQL Server Agent та SSIS Catalog проходить довше через більшу кількість задіяних ресурсів і складнішу внутрішню реалізацію, а через SQL CLR швидше, бо виконується в середовищі Common Language Runtime.
- Ну, і видно, що sp_execute_external_script не завершується, поки не закінчиться виконання всього зовнішнього скрипта, включно з асинхронним запуском.
І спробую дати порівняння описаних методів і навести сценарії, де їх можна застосовувати.
|
Механізм |
Простота реалізації |
Швидкодія |
Недоліки |
Типові сценарії |
|---|---|---|---|---|
|
Service Broker |
Середня: потрібна підготовка (контракти, черги, служби, процедура активації). |
Висока в основному скрипті, низька в «асинхронній частині», де обробка черги може мати затримки. |
Складна конфігурація. |
Внутрішня асинхронна обробка подій, черги повідомлень між БД. |
|
SQL Server Agent |
Середня: створення job динамічно, автоматичне видалення після виконання. |
Середня. |
Якщо завдання не видалилось — накопичення «сміття» в SQL Server Agent. |
Запуск «важких» процедур, коли треба логувати й контролювати їх виконання. |
|
SQL Server Agent (existing job) |
Легка: достатньо оновлювати команду й стартувати job. |
Середня. |
Неможливо паралельно запустити один і той же job. |
Регулярні завдання (backup, ETL-процеси). |
|
SSIS Catalog |
Складна: потрібен проєкт SSIS, пакет, публікація в SSIS Catalog. |
Низька. |
Вимагає наявності SSIS Catalog і налаштованого середовища. |
Складні ETL-сценарії, коли асинхронність — лише додатковий бонус. |
|
SQL CLR |
Середня: потрібна збірка, налаштування прав (UNSAFE). |
Висока: CLR виконується у середовищі .NET. |
Обмеження безпеки, робота з потоками може бути нестабільна. |
Коли треба максимально швидке виконання «фонових» завдань без зовнішніх інструментів. |
Наразі все. Буду вдячний за коментарі й зауваження та виправлення помилок! Дякую за увагу!
Сподобалась стаття автора? Підписуйтесь на його акаунт вгорі сторінки, щоб отримувати сповіщення про нові публікації на пошту.
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів