Таблица истории SQL Server - заполнить через SP или Trigger? - PullRequest
24 голосов
/ 08 декабря 2008

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

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

Если у меня есть выбор между хранимыми процедурами и триггерами, что лучше? Что быстрее?

Ответы [ 11 ]

40 голосов
/ 12 декабря 2008

Триггеры.

Мы написали графический интерфейс (внутренне называемый Red Matrix Reloaded ), чтобы упростить создание / управление триггерами ведения журнала аудита.

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


Таблица AuditLog

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 
)

Триггер для записи вставок

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 удаляет большинство пустых строк - поэтому форматирование отстой
  • Мы используем таблицу пользователей, не интегрированную безопасность
  • Этот код предоставлен для удобства - критика нашего выбора дизайна не допускается. Пуристы могут настаивать на том, что весь код регистрации должен выполняться на бизнес-уровне - они могут прийти сюда и написать / поддержать его для нас.
  • BLOB-объектов нельзя регистрировать с помощью триггеров в SQL Server (нет версии «до» для BLOB-объектов - есть только то, что есть). Text и nText являются BLOB-объектами, что делает записи либо не блокируемыми, либо делает их varchar (2000).
  • столбец тегов используется в качестве произвольного текста для идентификации строки (например, если клиент был удален, тег отобразит «General Motors North America» в таблице журнала аудита.
  • TagGUID используется для указания на «родителя» строки. Например, ведение журнала InvoiceLineItems указывает на InvoiceHeader . Таким образом, любой, кто ищет записи журнала аудита, связанные с конкретной накладной, найдет удаленные «позиции» по TagGUID позиции в журнале аудита.
  • иногда значения «OldValue» и «NewValue» записываются как дополнительный выбор - чтобы получить значимую строку. то есть. "

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

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

OldValue: Daimler Chrysler
NewValue: Cerberus Capital Management

Последнее замечание : Не стесняйтесь делать то, что мы делаем. Это здорово для нас, но все остальные могут не использовать его.

17 голосов
/ 08 декабря 2008

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

Channel9 video

15 голосов
/ 30 августа 2013

У нас есть сторонний инструмент ApexSQL Audit , который мы использовали для генерации триггеров.

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

Таблица 1 - содержит данные транзакции (кто, когда, приложение, имя хоста и т. Д.)

CREATE TABLE [dbo].[AUDIT_LOG_TRANSACTIONS](
    [AUDIT_LOG_TRANSACTION_ID] [int] IDENTITY(1,1) NOT NULL,
    [DATABASE] [nvarchar](128) NOT NULL,
    [TABLE_NAME] [nvarchar](261) NOT NULL,
    [TABLE_SCHEMA] [nvarchar](261) NOT NULL,
    [AUDIT_ACTION_ID] [tinyint] NOT NULL,
    [HOST_NAME] [varchar](128) NOT NULL,
    [APP_NAME] [varchar](128) NOT NULL,
    [MODIFIED_BY] [varchar](128) NOT NULL,
    [MODIFIED_DATE] [datetime] NOT NULL,
    [AFFECTED_ROWS] [int] NOT NULL,
    [SYSOBJ_ID]  AS (object_id([TABLE_NAME])),
  PRIMARY KEY CLUSTERED 
  (
       [AUDIT_LOG_TRANSACTION_ID] ASC
  )
)

Таблица 2 - содержит значения до / после.

CREATE TABLE [dbo].[AUDIT_LOG_DATA](
   [AUDIT_LOG_DATA_ID] [int] IDENTITY(1,1) NOT NULL,
   [AUDIT_LOG_TRANSACTION_ID] [int] NOT NULL,
   [PRIMARY_KEY_DATA] [nvarchar](1500) NOT NULL,
   [COL_NAME] [nvarchar](128) NOT NULL,
   [OLD_VALUE_LONG] [ntext] NULL,
   [NEW_VALUE_LONG] [ntext] NULL,
   [NEW_VALUE_BLOB] [image] NULL,
   [NEW_VALUE]  AS (isnull(CONVERT([varchar](8000),      [NEW_VALUE_LONG],0),CONVERT([varchar](8000),CONVERT([varbinary](8000),substring([NEW_VALUE_BLOB],(1),(8000)),0),0))),
   [OLD_VALUE]  AS (CONVERT([varchar](8000),[OLD_VALUE_LONG],0)),
   [PRIMARY_KEY]  AS ([PRIMARY_KEY_DATA]),
   [DATA_TYPE] [char](1) NOT NULL,
   [KEY1] [nvarchar](500) NULL,
   [KEY2] [nvarchar](500) NULL,
   [KEY3] [nvarchar](500) NULL,
   [KEY4] [nvarchar](500) NULL,
PRIMARY KEY CLUSTERED 
 (
    [AUDIT_LOG_DATA_ID] ASC
)
)

Вставить триггер

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

CREATE TRIGGER [dbo].[tr_i_AUDIT_Audited_Table]
ON [dbo].[Audited_Table]
FOR INSERT
NOT FOR REPLICATION
As
BEGIN
DECLARE 
    @IDENTITY_SAVE              varchar(50),
    @AUDIT_LOG_TRANSACTION_ID       Int,
    @PRIM_KEY               nvarchar(4000),
    @ROWS_COUNT             int

SET NOCOUNT ON
Select @ROWS_COUNT=count(*) from inserted
Set @IDENTITY_SAVE = CAST(IsNull(@@IDENTITY,1) AS varchar(50))

INSERT
INTO dbo.AUDIT_LOG_TRANSACTIONS
(
    TABLE_NAME,
    TABLE_SCHEMA,
    AUDIT_ACTION_ID,
    HOST_NAME,
    APP_NAME,
    MODIFIED_BY,
    MODIFIED_DATE,
    AFFECTED_ROWS,
    [DATABASE]
)
values(
    'Audited_Table',
    'dbo',
    2,  --  ACTION ID For INSERT
    CASE 
      WHEN LEN(HOST_NAME()) < 1 THEN ' '
      ELSE HOST_NAME()
    END,
    CASE 
      WHEN LEN(APP_NAME()) < 1 THEN ' '
      ELSE APP_NAME()
    END,
    SUSER_SNAME(),
    GETDATE(),
    @ROWS_COUNT,
    'Database_Name'
)

Set @AUDIT_LOG_TRANSACTION_ID = SCOPE_IDENTITY()    

--This INSERT INTO code is repeated for each columns that is audited. 
--Below are examples for only two columns
INSERT INTO dbo.AUDIT_LOG_DATA
(
    AUDIT_LOG_TRANSACTION_ID,
    PRIMARY_KEY_DATA,
    COL_NAME,
    NEW_VALUE_LONG,
    DATA_TYPE
    , KEY1
)
SELECT
    @AUDIT_LOG_TRANSACTION_ID,
    convert(nvarchar(1500), IsNull('[PK_Column]='+CONVERT(nvarchar(4000), NEW.[PK_Column], 0), '[PK_Column] Is Null')),
    'Column1',
    CONVERT(nvarchar(4000), NEW.[Column1], 0),
    'A'
    , CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[PK_Column], 0))
FROM inserted NEW
WHERE NEW.[Column1] Is Not Null

 --value is inserted for each column that is selected for auditin
INSERT INTO dbo.AUDIT_LOG_DATA
(
    AUDIT_LOG_TRANSACTION_ID,
    PRIMARY_KEY_DATA,
    COL_NAME,
    NEW_VALUE_LONG,
    DATA_TYPE
    , KEY1
)
SELECT
    @AUDIT_LOG_TRANSACTION_ID,
    convert(nvarchar(1500), IsNull('[PK_Column]='+CONVERT(nvarchar(4000), NEW.[PK_Column], 0), '[PK_Column] Is Null')),
    'Column2',
    CONVERT(nvarchar(4000), NEW.[Column2], 0),
    'A'
    , CONVERT(nvarchar(500), CONVERT(nvarchar(4000), NEW.[PK_Column], 0))
    FROM inserted NEW
    WHERE NEW.[Column2] Is Not Null
End

Отказ от ответственности: Я никоим образом не связан с Apex, но использую их инструменты в своей текущей работе.

4 голосов
/ 08 декабря 2008

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

Как быстрее? Определение быстрого в базе данных является сложной проблемой с большим количеством переменных. Если не считать «попробуйте оба способа и сравнить», вы не получите полезного ответа на вопрос, какой метод быстрее. Переменные включают размер задействованных таблиц, нормальный порядок обновлений, скорость дисков на сервере, объем памяти, объем памяти, выделенный для кэширования и т. Д. Этот список бесконечен, и каждая переменная влияет на то, запускаются ли триггеры. быстрее, чем пользовательский SQL внутри SP.

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

Удачи.

3 голосов
/ 12 декабря 2008

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

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

После триггеров предоставьте вам две временные таблицы для работы внутри триггера:

  • INSERTED после INSERT или UPDATE
  • УДАЛЕНО после УДАЛЕНИЯ

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

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

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

2 голосов
/ 12 декабря 2008

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

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

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

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

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

2 голосов
/ 08 декабря 2008

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

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

1 голос
/ 05 сентября 2015

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

CREATE TRIGGER [dbo].[tr_Employee_rev]
ON [dbo].[Employee]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
    IF EXISTS(SELECT * FROM INSERTED) AND EXISTS (SELECT * FROM DELETED)
    BEGIN
        INSERT INTO [EmployeeRev](EmployeeID,Firstname,Initial,Surname,Birthdate,operation, updated, updatedby) SELECT inserted.ID, inserted.Firstname,inserted.Initial,inserted.Surname,inserted.Birthdate,'u', GetDate(), SYSTEM_USER FROM INSERTED
    END 

    IF EXISTS (SELECT * FROM INSERTED) AND NOT EXISTS(SELECT * FROM DELETED)
    BEGIN
        INSERT INTO [EmployeeRev](EmployeeID,Firstname,Initial,Surname,Birthdate,operation, updated, updatedby) SELECT inserted.ID, inserted.Firstname,inserted.Initial,inserted.Surname,inserted.Birthdate,'i', GetDate(), SYSTEM_USER FROM INSERTED
    END

    IF EXISTS(SELECT * FROM DELETED) AND NOT EXISTS(SELECT * FROM INSERTED)
    BEGIN
        INSERT INTO [EmployeeRev](EmployeeID,Firstname,Initial,Surname,Birthdate,operation, updated, updatedby) SELECT deleted.ID, deleted.Firstname,deleted.Initial,deleted.Surname,deleted.Birthdate,'d', GetDate(), SYSTEM_USER FROM DELETED 
    END
END

Я использую SQLServer для генерации SQL для таблиц ревизий вместо ручного кодирования. Этот код доступен на https://github.com/newdigate/sqlserver-revision-tables

0 голосов
/ 02 декабря 2010

Триггеры. Вот мой подход:

  1. Создайте по одной таблице аудита для каждой критической таблицы, для которой требуется проверка
  2. Таблица аудита будет включать все столбцы из исходной таблицы + данные записи аудита столбцов, например, кто, когда и действие
  3. Триггер только для ОБНОВЛЕНИЯ и УДАЛЕНИЯ, операция INSERT будет иметь первичную запись в самой исходной таблице
  4. Перед обновлением или удалением скопируйте исходную запись + данные аудита в таблицу аудита
  5. (Необязательно - только для ОБНОВЛЕНИЯ :) Чтобы узнать, какой столбец был обновлен, используйте встроенную функцию SQL UPDATE (ColumnName) или COLUMNS_UPDATED () для определения уязвимых столбцов

Аудит таким способом сохраняет текущий статус в исходной таблице и всю историю в таблице аудита и легко идентифицируется по ключевым столбцам.

0 голосов
/ 12 декабря 2008

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...