用于检查 SQL 服务器实例更改的脚本?

Script to check changes on SQL Server Instance?

是否有任何 SQL 脚本来检查其他数据库管理员对 SQL Server 实例所做的更改,我收到有关该更改的电子邮件警报。如果是,请向我提供脚本和应用它的所有步骤。我正在使用 SQL Server 2012。

一个解决方案是使用 DDL trigger 来捕获所有模式更改(过程、函数、table 定义等)。这适用于所有非加密对象,当然,其他管理员不得禁用它。

可以找到有关如何编写此类触发器和持久更改的更多详细信息here

[稍后编辑]

我记得我在开发环境上创建了这样的审计,我可以根据指示的文章提供自定义版本。除了通常的信息外,我还在旧对象文本和新对象文本之间添加了一个 "distance",以便对变化幅度有一个基本的了解。

1) Table定义:

IF OBJECT_ID('dbo.DDLEvents', 'U') IS NULL
BEGIN
    CREATE TABLE dbo.DDLEvents
    (
        EventId           INT IDENTITY(1, 1) NOT NULL,
        EventDate         DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        EventType         NVARCHAR(64),
        EventXML          XML,
        DatabaseName      NVARCHAR(255),
        SchemaName        NVARCHAR(255),
        ObjectName        NVARCHAR(255),
        HostName          VARCHAR(128),
        IPAddress         VARCHAR(32),
        ProgramName       NVARCHAR(255),
        LoginName         NVARCHAR(255),
        ObjectDefinition  NVARCHAR(MAX),
        LastObjDefinition NVARCHAR(MAX),
        Diff              INT                                   -- edit distance between last and current object version (gives an idea of how much was changed in the object)
    );

    create index IDX_DDLEvents_Object ON DDLEvents (SchemaName, ObjectName)
END
go

2) 现有对象的初始文本:

IF NOT EXISTS(SELECT * FROM dbo.DDLEvents)
BEGIN
    INSERT INTO dbo.DDLEvents
    (EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
    SELECT 'CREATE_PROCEDURE', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
    FROM sys.procedures;

    INSERT INTO dbo.DDLEvents
    (EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
    SELECT 'CREATE_VIEW', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
    FROM sys.views;

    INSERT INTO dbo.DDLEvents
    (EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
    SELECT 'CREATE_FUNCTION', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
    FROM sys.objects
    -- scalar, inline table-valued, table-valued
    WHERE type IN ('FN', 'IF', 'TF')  
END
go

3)距离函数(CLR):

[Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = false)]
public static int Levenshtein(SqlString S1, SqlString S2)
{
    if (S1.IsNull)
        S1 = new SqlString("");

    if (S2.IsNull)
        S2 = new SqlString("");

    int maxLen = 4096;

    // keeping only the first part of the string (performance reasons)
    String SC1 = S1.Value.ToUpper();
    String SC2 = S2.Value.ToUpper();

    if (SC1.Length > maxLen)
        SC1 = SC1.Remove(maxLen);
    if (SC2.Length > maxLen)
        SC2 = SC2.Remove(maxLen);

    int n = SC1.Length;
    int m = SC2.Length;

    short[,] d = new short[n + 1, m + 1];
    int cost = 0;

    if (n + m == 0)
    {
        return 0;
    }
    else if (n == 0)
    {
        return 0;
    }
    else if (m == 0)
    {
        return 0;
    }

    for (short i = 0; i <= n; i++)
        d[i, 0] = i;

    for (short j = 0; j <= m; j++)
        d[0, j] = j;

    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (SC1[i - 1] == SC2[j - 1])
                cost = 0;
            else
                cost = 1;

            d[i, j] = (short) System.Math.Min(System.Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
        }
    }

    // double percentage = System.Math.Round((1.0 - ((double)d[n, m] / (double)System.Math.Max(n, m))) * 100.0, 2);
    // return percentage;
    return d[n, m];
}

4) DDL触发器定义

if not exists (select * from sys.triggers where name = 'DDL_Audit_Trigger')
    exec ('create trigger DDL_Audit_Trigger ON DATABASE FOR CREATE_PROCEDURE AS BEGIN PRINT 1; END')
GO

ALTER TRIGGER [DDL_Audit_Trigger]
ON DATABASE
FOR 
    CREATE_ASSEMBLY, ALTER_ASSEMBLY, DROP_ASSEMBLY,
    CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE, 
    CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,
    CREATE_INDEX, ALTER_INDEX, DROP_INDEX,
    CREATE_VIEW, ALTER_VIEW, DROP_VIEW, 
    CREATE_ROLE, ALTER_ROLE, DROP_ROLE,
    CREATE_SCHEMA, ALTER_SCHEMA, DROP_SCHEMA,
    CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
    CREATE_TYPE, DROP_TYPE, 
    CREATE_USER, ALTER_USER, DROP_USER,
    CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER,
    RENAME
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRY
        DECLARE @EventData XML = EVENTDATA();
        DECLARE @ip VARCHAR(32) = ( SELECT client_net_address FROM sys.dm_exec_connections WHERE session_id = @@SPID);
        DECLARE @ObjectSchema NVARCHAR(255) = @EventData.value('(/EVENT_INSTANCE/SchemaName)[1]',  'NVARCHAR(255)')
        DECLARE @ObjectName NVARCHAR(255) = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]',  'NVARCHAR(255)')
        -- DECLARE @ObjectFullName NVARCHAR(255) = @ObjectSchema + '.' + @ObjectName
        DECLARE @CommandText NVARCHAR(MAX) = @EventData.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)')

        DECLARE @LastObjectChange DATETIME = (SELECT TOP 1 EventDate FROM dbo.DDLEvents where SchemaName = @ObjectSchema and ObjectName = @ObjectName ORDER BY EventDate DESC)
        DECLARE @LastObjectDefinition NVARCHAR(MAX) = (SELECT TOP 1 ObjectDefinition FROM dbo.DDLEvents where SchemaName = @ObjectSchema and ObjectName = @ObjectName and EventDate = @LastObjectChange ORDER BY EventDate DESC)

        INSERT INTO dbo.DDLEvents
        (EventType, 
            EventXML, DatabaseName,
            SchemaName, ObjectName,
            HostName, IPAddress, ProgramName, LoginName,
            ObjectDefinition, LastObjDefinition, Diff
        )
        SELECT @EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(100)'), 
            @EventData, DB_NAME(),
            @ObjectSchema, @ObjectName,
            HOST_NAME(), @ip, PROGRAM_NAME(), SUSER_SNAME(),
            @CommandText, @LastObjectDefinition, dbo.Levenshtein(@CommandText, @LastObjectDefinition);
    END TRY
    BEGIN CATCH
        INSERT INTO dbo.DDLEventsLog (Error) 
        SELECT ERROR_MESSAGE()
    END CATCH
END
GO