Таблица истории SQL Server - заполняет через SP или Триггер?

Используйте .observeOn (AndroidSchedulers.mainThread (), true) вместо .observeOn (AndroidSchedulers.mainThread ()

public final Observable observeOn(Scheduler scheduler, boolean delayError) {
        return observeOn(scheduler, delayError, RxRingBuffer.SIZE);
    }

Выше была подпись функции observOn. Работает следующий код.

  Observable.mergeDelayError(
                Observable.error(new RuntimeException()),
                Observable.just("Hello")
        )
                .observeOn(AndroidSchedulers.mainThread(), true)
                .subscribeOn(Schedulers.io())
                .subscribe(new Subscriber() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(String s) {

                    }
                });

Получил этот трюк из потока ConcatDelayError: https://github.com/ReactiveX/RxJava/issues/3908#issuecomment-217999009

24
задан ConcernedOfTunbridgeWells 8 December 2008 в 13:20
поделиться

8 ответов

Триггеры.

Мы записали GUI (внутренне названный Красный Матрица: Перезагрузка ) для разрешения легкого создания/управления триггеров входа аудита.

Вот некоторый DDL используемого материала:

<час>

таблица

CREATE TABLE [AuditLog] (
    [AuditLogID] [int] IDENTITY (1, 1) NOT NULL ,
    [ChangeDate] [datetime] NOT NULL CONSTRAINT [DF_AuditLog_ChangeDate] DEFAULT (getdate()),
    [RowGUID] [uniqueidentifier] NOT NULL ,
    [ChangeType] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [TableName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [FieldName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [OldValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [NewValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [Username] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [Hostname] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
    [AppName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [UserGUID] [uniqueidentifier] NULL ,
    [TagGUID] [uniqueidentifier] NULL ,
    [Tag] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL 
)
AuditLog <час>

Триггер для входа вставляет

CREATE TRIGGER LogInsert_Nodes ON dbo.Nodes
FOR INSERT
AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
    SET @SavedUserGUID = NULL
END

    /*We dont' log individual field changes Old/New because the row is new.
    So we only have one record - INSERTED*/

    INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue, NewValue)

    SELECT
        getdate(), --ChangeDate
        i.NodeGUID, --RowGUID
        'INSERTED', --ChangeType
        USER_NAME(), HOST_NAME(), APP_NAME(), 
        @SavedUserGUID, --UserGUID
        'Nodes', --TableName
        '', --FieldName
        i.ParentNodeGUID, --TagGUID
        i.Caption, --Tag
        null, --OldValue
        null --NewValue
    FROM Inserted i
<час>

Триггер для входа Обновлений

CREATE TRIGGER LogUpdate_Nodes ON dbo.Nodes
FOR UPDATE AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
    SET @SavedUserGUID = NULL
END

    /* ParentNodeGUID uniqueidentifier */
    IF UPDATE (ParentNodeGUID)
    BEGIN
        INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue, NewValue)
        SELECT 
            getdate(), --ChangeDate
            i.NodeGUID, --RowGUID
            'UPDATED', --ChangeType
            USER_NAME(), HOST_NAME(), APP_NAME(), 
            @SavedUserGUID, --UserGUID
            'Nodes', --TableName
            'ParentNodeGUID', --FieldName
            i.ParentNodeGUID, --TagGUID
            i.Caption, --Tag
            d.ParentNodeGUID, --OldValue
            i.ParentNodeGUID --NewValue
        FROM Inserted i
            INNER JOIN Deleted d
            ON i.NodeGUID = d.NodeGUID
        WHERE (d.ParentNodeGUID IS NULL AND i.ParentNodeGUID IS NOT NULL)
        OR (d.ParentNodeGUID IS NOT NULL AND i.ParentNodeGUID IS NULL)
        OR (d.ParentNodeGUID <> i.ParentNodeGUID)
    END

    /* Caption varchar(255) */
    IF UPDATE (Caption)
    BEGIN
        INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue, NewValue)
        SELECT 
            getdate(), --ChangeDate
            i.NodeGUID, --RowGUID
            'UPDATED', --ChangeType
            USER_NAME(), HOST_NAME(), APP_NAME(), 
            @SavedUserGUID, --UserGUID
            'Nodes', --TableName
            'Caption', --FieldName
            i.ParentNodeGUID, --TagGUID
            i.Caption, --Tag
            d.Caption, --OldValue
            i.Caption --NewValue
        FROM Inserted i
            INNER JOIN Deleted d
            ON i.NodeGUID = d.NodeGUID
        WHERE (d.Caption IS NULL AND i.Caption IS NOT NULL)
        OR (d.Caption IS NOT NULL AND i.Caption IS NULL)
        OR (d.Caption <> i.Caption)
    END

...

/* ImageGUID uniqueidentifier */
IF UPDATE (ImageGUID)
BEGIN
    INSERT INTO AuditLog(
        ChangeDate, RowGUID, ChangeType, 
        Username, HostName, AppName,
        UserGUID, 
        TableName, FieldName, 
        TagGUID, Tag, 
        OldValue, NewValue)
    SELECT 
        getdate(), --ChangeDate
        i.NodeGUID, --RowGUID
        'UPDATED', --ChangeType
        USER_NAME(), HOST_NAME(), APP_NAME(), 
        @SavedUserGUID, --UserGUID
        'Nodes', --TableName
        'ImageGUID', --FieldName
        i.ParentNodeGUID, --TagGUID
        i.Caption, --Tag
        (SELECT Caption FROM Nodes WHERE NodeGUID = d.ImageGUID), --OldValue
        (SELECT Caption FROM Nodes WHERE NodeGUID = i.ImageGUID) --New Value
    FROM Inserted i
        INNER JOIN Deleted d
        ON i.NodeGUID = d.NodeGUID
    WHERE (d.ImageGUID IS NULL AND i.ImageGUID IS NOT NULL)
    OR (d.ImageGUID IS NOT NULL AND i.ImageGUID IS NULL)
    OR (d.ImageGUID <> i.ImageGUID)
END
<час>

, Триггер для входа Удаляет

CREATE TRIGGER LogDelete_Nodes ON dbo.Nodes
FOR DELETE
AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
    SET @SavedUserGUID = NULL
END

    /*We dont' log individual field changes Old/New because the row is new.
    So we only have one record - DELETED*/

    INSERT INTO AuditLog(
            ChangeDate, RowGUID, ChangeType, 
            Username, HostName, AppName,
            UserGUID, 
            TableName, FieldName, 
            TagGUID, Tag, 
            OldValue,NewValue)

    SELECT
        getdate(), --ChangeDate
        d.NodeGUID, --RowGUID
        'DELETED', --ChangeType
        USER_NAME(), HOST_NAME(), APP_NAME(), 
        @SavedUserGUID, --UserGUID
        'Nodes', --TableName
        '', --FieldName
        d.ParentNodeGUID, --TagGUID
        d.Caption, --Tag
        null, --OldValue
        null --NewValue
    FROM Deleted d
<час>

И для знания, какой пользователь в программном обеспечении сделал обновление, каждое соединение "регистрирует себя на SQL Server" путем вызова хранимой процедуры:

CREATE PROCEDURE dbo.SaveContextUserGUID @UserGUID uniqueidentifier AS

/* Saves the given UserGUID as the session's "Context Information" */
IF @UserGUID IS NULL
BEGIN
    PRINT 'Emptying CONTEXT_INFO because of null @UserGUID'
    DECLARE @BinVar varbinary(128)
    SET @BinVar = CAST( REPLICATE( 0x00, 128 ) AS varbinary(128) )
    SET CONTEXT_INFO @BinVar
    RETURN 0
END

DECLARE @UserGUIDBinary binary(16) --a guid is 16 bytes
SELECT @UserGUIDBinary = CAST(@UserGUID as binary(16))
SET CONTEXT_INFO @UserGUIDBinary


/* To load the guid back 
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

select @SavedUserGUID AS UserGUID
*/
<час>

Примечания

  • формат кода Stackoverflow удаляет наиболее пустые строки - настолько форматирующий, сосет
  • , Мы используем таблицу пользователей, не интегрированную защиту
  • , Этот код обеспечивается как convience - никакая критика нашего позволенного выбора дизайна. Пуристы могли бы настоять, чтобы весь код входа был сделан в бизнес-слое - они могут приехать сюда и писать/поддерживать его для нас.
  • блобы не могут быть зарегистрированы с помощью триггеров в SQL Server (существует не "перед" версией блоба - существует только, что). Текст и nText являются блобами - который делает примечания или unloggable, или делает их varchar (2000).
  • столбец Tag используется в качестве произвольного текста для идентификации строки (например, если клиент был удален, тег покажет "General Motors Северная Америка" в таблице контрольного журнала.
  • TagGUID используется для указания на "родителя" строки. Например, вход InvoiceLineItems указывает назад на InvoiceHeader. Таким образом, любой ищущий записи контрольного журнала, связанные для определенного счета, найдет удаленные "позиции" TagGUID позиции в журнале аудита.
  • иногда значения "OldValue" и "NewValue" записаны как подвыбор - для получения значимой строки. т.е. "

    OldValue: {233-й-ad34234..} NewValue: {883-sdf34...}

менее полезно в журнале аудита, чем:

OldValue: Daimler Chrysler
NewValue: Cerberus Capital Management

Заключительное примечание : Не стесняйтесь не делать то, что мы делаем. Здорово для нас, но все остальные свободны не использовать его.

41
ответ дан Jaxidian 16 October 2019 в 07:40
поделиться

в SQL-сервере 2008 новая возможность под названием CDC (Сбор данных изменения) CDC на MSDN может помочь. CDC является способностью записать изменения в данных таблицы в другую таблицу, не пишущий триггеры или некоторый другой механизм, записи сбора данных Изменения, которые любят изменения, вставляют, обновляют и удаляют к таблице в SQL-сервере, таким образом делающем детали изменений, доступных в реляционном формате.

видео Channel9

17
ответ дан ashish jaiman 16 October 2019 в 07:40
поделиться

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

Что касается быстрее? Определение, что быстро в базе данных, является тяжелой проблемой с большим количеством переменных. За исключением "попытки это выдерживают сравнение оба пути и", Вы не собираетесь получать полезный ответ, к которому метод быстрее. Переменные включают размер включенных таблиц, нормальный шаблон обновлений, скорость дисков в сервере, объеме памяти, объем памяти, посвященный кэшированию, и т.д. Этот список бесконечен и каждая переменная влияние, быстрее ли триггеры, чем пользовательский SQL в SP.

Хороший. Быстро. Дешевый. Выберите два. Триггеры являются Хорошими с точки зрения целостности и вероятно Дешевыми с точки зрения обслуживания. Возможно они также Быстры в этом, как только они работают, Вы сделаны с ними. SPS является проблемой обслуживания, и продвигающий материал в обслуживание может быть Быстрым, но никогда не является Хорошим или Дешевым.

Удачи.

4
ответ дан jmucchiello 16 October 2019 в 07:40
поделиться

Рекомендуемый подход зависит от Ваших требований. Если таблица истории там для [1 110] журнал аудита , необходимо получить каждую операцию. Если таблица истории только для [1 111] производительность причины, то запланированное задание передачи данных SQL Agent должно быть достаточно.

Для получения каждой операции используют ИЛИ ПОСЛЕ ТРИГГЕРОВ или после Сбора данных Изменения.

После того, как триггеры предоставляют Вам две временных таблицы для работы с внутренней частью триггером:

  • ВСТАВИЛ после того, как ВСТАВЛЯЮТ или ОБНОВЛЯЮТ
  • , УДАЛИЛ после того, как УДАЛЯЮТ

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

Сбор данных изменения (CDC) разработан для того, чтобы составить таблицу дельты, которую можно использовать в качестве источника для загрузки данных в хранилище данных (или таблица истории). В отличие от триггеров, CDC является асинхронной, и можно использовать любой метод и планирующий для заполнения места назначения (sprocs, SSIS).

можно получить доступ и к исходным данным и к изменениям с CDC. Отслеживание изменений (CT) только обнаруживает измененные строки. Возможно создать полный журнал аудита с CDC, но не с CT. CDC и CT и только доступны в Выпусках Предприятия и Разработчика 2008 года MSSQL.

3
ответ дан mika 16 October 2019 в 07:40
поделиться

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

вряд ли будет какая-то конкретная разность оборотов в любом для этого типа операции, как время выполнения будет во власти ввода-вывода.

2
ответ дан ConcernedOfTunbridgeWells 16 October 2019 в 07:40
поделиться

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

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

журнал аудита обычно требует, в минимуме, идентификаторе пользователя, метке времени и коде операции - и вероятно некоторая деталь об операции. Пример - изменяет заказанное количество на позиции на заказе на поставку.

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

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

2
ответ дан dkretz 16 October 2019 в 07:40
поделиться

Триггеры. Прямо сейчас Вы могли бы быть в состоянии сказать, что единственный способ, которым обновляются данные, через Ваш SPS, но вещи могут измениться, или Вы, возможно, должны были бы сделать, масса вставляет/обновляет то использование SPS, будет слишком громоздким для. Пойдите с триггерами.

0
ответ дан xando 16 October 2019 в 07:40
поделиться

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

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

В этом случае все это зависит от индексов таблицы. Мы используем SQL-сервер 2000 для 24/7 приложения, которое обрабатывает по 100K финансовым транзакциям в день. Самая большая/основная таблица имеет более чем 100 миллионов строк, и 15 индексов (масса удаляет, не довольно возможны, если время работы желаемо). Даже при том, что весь SQL сделан в Хранимых процедурах, мы не используем триггеры или внешние ключи из-за хита производительности.

0
ответ дан Jared Knipp 16 October 2019 в 07:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: