Асинхронные триггеры в SQL Server 2005/2008 - PullRequest
33 голосов
/ 20 апреля 2009

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

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

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

Есть ли способ заставить триггер работать асинхронно? Любые примеры.

Ответы [ 9 ]

26 голосов
/ 20 апреля 2009

Вы не можете заставить триггер работать асинхронно, но вы могли бы синхронно отправить триггер в очередь SQL Service Broker . Затем очередь может обрабатываться асинхронно с помощью хранимой процедуры.

7 голосов
/ 20 апреля 2009
4 голосов
/ 21 марта 2015

SQL Server 2014 представил очень интересную функцию под названием Delayed Durability . Если вы можете допустить потерю нескольких строк в случае катастрофического события, такого как сбой сервера, вы действительно можете повысить свою производительность в таких сценариях, как ваш.

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

База данных, содержащая таблицу, должна быть сначала изменена для обеспечения отсроченного срока службы.

ALTER DATABASE dbname SET DELAYED_DURABILITY = ALLOWED

Тогда вы можете контролировать срок службы для каждой транзакции.

begin tran

insert into ChangeTrackingTable select * from inserted

commit with(DELAYED_DURABILITY=ON)

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

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

ALTER DATABASE dbname SET DELAYED_DURABILITY = FORCED

Для отсроченного срока службы нет разницы между неожиданным завершение работы и ожидаемое завершение работы / перезапуск SQL Server. подобно катастрофические события, вы должны планировать потерю данных. В запланированном завершение / перезапуск некоторых транзакций, которые не были записаны на диск может быть сначала сохранен на диск, но вы не должны планировать это. Планировать как хотя остановка / перезапуск, запланированный или незапланированный, теряет данные совпадают с катастрофическим событием.

Надеемся, что этот странный дефект будет исправлен в следующем выпуске, но до тех пор, возможно, будет целесообразно автоматически выполнить процедуру sp_flush_log при перезапуске или завершении работы SQL-сервера.

1 голос
/ 21 марта 2015

Для выполнения асинхронной обработки вы можете использовать Service Broker, но это не единственный вариант, вы также можете использовать объекты CLR.

Ниже приведен пример хранимой процедуры (AsyncProcedure), которая асинхронно вызывает другую процедуру (SyncProcedure):

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Runtime.Remoting.Messaging;
using System.Diagnostics;

public delegate void AsyncMethodCaller(string data, string server, string dbName);

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void AsyncProcedure(SqlXml data)
    {
        AsyncMethodCaller methodCaller = new AsyncMethodCaller(ExecuteAsync);
        string server = null;
        string dbName = null;
        using (SqlConnection cn = new SqlConnection("context connection=true"))
        using (SqlCommand cmd = new SqlCommand("SELECT @@SERVERNAME AS [Server], DB_NAME() AS DbName", cn))
        {
            cn.Open();
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                reader.Read();
                server = reader.GetString(0);
                dbName = reader.GetString(1);
            }
        }
        methodCaller.BeginInvoke(data.Value, server, dbName, new AsyncCallback(Callback), null);
        //methodCaller.BeginInvoke(data.Value, server, dbName, null, null);
    }

    private static void ExecuteAsync(string data, string server, string dbName)
    {
        string connectionString = string.Format("Data Source={0};Initial Catalog={1};Integrated Security=SSPI", server, dbName);
        using (SqlConnection cn = new SqlConnection(connectionString))
        using (SqlCommand cmd = new SqlCommand("SyncProcedure", cn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@data", SqlDbType.Xml).Value = data;
            cn.Open();
            cmd.ExecuteNonQuery();
        }
    }

    private static void Callback(IAsyncResult ar)
    {
        AsyncResult result = (AsyncResult)ar;
        AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
        try
        {
            caller.EndInvoke(ar);
        }
        catch (Exception ex)
        {
            // handle the exception
            //Debug.WriteLine(ex.ToString());
        }
    }
}

Он использует асинхронные делегаты для вызова SyncProcedure:

CREATE PROCEDURE SyncProcedure(@data xml)
AS
  INSERT INTO T(Data) VALUES (@data)

Пример вызова AsyncProcedure:

EXEC dbo.AsyncProcedure N'<doc><id>1</id></doc>'

К сожалению, сборка требует разрешения UNSAFE.

1 голос
/ 20 апреля 2009

Создать таблицу истории. При обновлении (/ удалении / вставке) основной таблицы вставьте старые значения записи (удаленная псевдотаблица в триггере) в таблицу истории; Также необходима некоторая дополнительная информация (временная метка, тип операции, может быть, пользовательский контекст). В любом случае новые значения сохраняются в оперативной таблице.

Таким образом, триггеры работают быстро (er), и вы можете переключать медленные операции в средство просмотра журнала (процедура).

1 голос
/ 20 апреля 2009

Существует очевидный конфликт между "хорошо выполняет свою работу" и "неприемлемо", очевидно.

Мне кажется, что вы пытаетесь использовать триггеры так же, как вы используете события в процедурном приложении OO, которое IMHO не отображает.

Я бы назвал любую логику триггера, которая занимает 30 секунд - нет, более 0,1 секунды, - неработающей. Я думаю, что вам действительно нужно переделать свою функциональность и сделать это другим способом. Я бы сказал «если вы хотите сделать это асинхронным», но я не думаю, что этот дизайн имеет смысл в любой форме.

Что касается "асинхронных триггеров", основной фундаментальный конфликт состоит в том, что вы никогда не сможете включить такую ​​вещь между операторами BEGIN TRAN и COMMIT TRAN, потому что вы потеряли отслеживание того, успешно ли это выполнено.

1 голос
/ 20 апреля 2009

Интересно, можно ли пометить запись для отслеживания изменений, вставив в таблицу "слишком процесс" информацию о том, кто внес изменения и т. Д. И т. Д.

Тогда может появиться другой процесс и регулярно копировать остальные данные.

0 голосов
/ 20 апреля 2009

Я подозреваю, что ваш триггер имеет один из этих общих триггеров генерации CSV / текста, предназначенных для регистрации всех изменений для всех таблиц в одном месте. Хорошо в теории (возможно ...), но трудно поддерживать и использовать на практике.

Если вы могли бы работать асинхронно (что все равно потребовало бы сохранения данных где-нибудь для повторного ведения журнала позже), то вы не проводите аудит и не можете использовать историю.

Возможно, вы могли бы взглянуть на план выполнения триггера и посмотреть, какой бит занимает наибольшее время?

Можете ли вы изменить порядок аудита, скажем, для каждой таблицы? Вы можете разделить текущие данные журнала в соответствующие таблицы.

0 голосов
/ 20 апреля 2009

Не знаю, но вставляете ли вы значения в таблицу аудита, которые также существуют в базовой таблице? Если это так, вы можете отслеживать только изменения. Следовательно, вставка будет отслеживать время изменения, пользователя, extra и группу значений NULL (в действительности значение before). Обновление будет содержать только время изменения, пользователя и т. Д., А также значение перед измененным столбцом. У удаления есть изменение в и т. Д. И все значения.

Кроме того, у вас есть таблица аудита для каждой базовой таблицы или одна таблица аудита для БД? Конечно, последующее может более легко привести к ожиданиям, поскольку каждая транзакция пытается записать в одну таблицу.

...