审计日志的数据库设计

jbo*_*chi 143 audit logging database-design

每次我需要设计一个新的数据库时,我都会花一些时间考虑如何设置数据库模式来保存更改的审核日志.

这里已经提出了一些问题,但我不同意所有场景都有一个最好的方法:

我还偶然发现了这篇关于维护数据库更改日志的有趣文章,该文章试图列出每种方法的优缺点.它写得很好并且有很多有趣的信息,但它使我的决定更加困难.

我的问题是:是否有一个我可以使用的参考,可能是一本书或类似决策树的东西,我可以参考决定我应该根据一些输入变量去哪种方式,例如:

  • 数据库模式的成熟度
  • 如何查询日志
  • 需要重新创建记录的概率
  • 更重要的是:写或读性能
  • 正在记录的值的性质(字符串,数字,blob)
  • 存储空间可用

我知道的方法是:

1.为创建和修改的日期和用户添加列

表格示例:

  • ID
  • _1
  • _2
  • VALUE_3
  • 创建日期
  • 修改日期
  • 由...制作
  • modified_by

主要缺点:我们失去了修改的历史.提交后无法回滚.

2.仅插入表格

表格示例:

  • ID
  • _1
  • _2
  • VALUE_3
  • 删除(布尔值)
  • 用户

主要缺点:如何保持外键最新?需要巨大的空间

3.为每个表创建单独的历史记录表

历史表示例:

  • ID
  • _1
  • _2
  • VALUE_3
  • VALUE_4
  • 用户
  • 删除(布尔值)
  • 时间戳

主要缺点:需要复制所有审计表.如果架构发生更改,则还需要迁移所有日志.

4.为所有表创建合并历史记录表

历史表示例:

  • TABLE_NAME
  • 领域
  • 用户
  • NEW_VALUE
  • 删除(布尔值)
  • 时间戳

主要缺点:如果需要,我能否轻松重建记录(回滚)?new_value列需要是一个巨大的字符串,因此它可以支持所有不同的列类型.

Jos*_*son 82

一些wiki平台使用的一种方法是分离识别数据和您正在审核的内容.它增加了复杂性,但你最终的完整记录,即进行了编辑,你接下来要混搭给用户的旧纪录是什么样子的想法领域的不只是上市的审计线索.

例如,如果您有一个名为Opportunities的表来跟踪销售交易,您实际上会创建两个单独的表:

机会机会
_内容(或类似的东西)

机遇表会让你原本是用来唯一标识的记录,将能容纳你会为你的外键关系引用的主键信息.该Opportunities_Content表将持有你的用户可以更改所有字段,并且您想为其保留审计线索.内容表中的每条记录都包含自己的PK以及修改日期和修改日期数据.在机遇表将包括在最初创建的主记录以及由谁来到当前版本的参考和信息.

这是一个简单的例子:

CREATE TABLE dbo.Page(  
    ID int PRIMARY KEY,  
    Name nvarchar(200) NOT NULL,  
    CreatedByName nvarchar(100) NOT NULL, 
    CurrentRevision int NOT NULL, 
    CreatedDateTime datetime NOT NULL
Run Code Online (Sandbox Code Playgroud)

内容如下:

CREATE TABLE dbo.PageContent(
    PageID int NOT NULL,
    Revision int NOT NULL,
    Title nvarchar(200) NOT NULL,
    User nvarchar(100) NOT NULL,
    LastModified datetime NOT NULL,
    Comment nvarchar(300) NULL,
    Content nvarchar(max) NOT NULL,
    Description nvarchar(200) NULL
Run Code Online (Sandbox Code Playgroud)

我可能会使内容表的PK成为PageID和Revision提供的多列密钥,并且Revision是一个标识类型.您可以使用"修订"列作为FK.然后通过JOINing拉出合并记录,如下所示:

SELECT * FROM Page
JOIN PageContent ON CurrentRevision = Revision AND ID = PageID
Run Code Online (Sandbox Code Playgroud)

那里可能有一些错误......这是我的头脑.不过,它应该让你了解另一种模式.

  • 如果为每个表编写和维护脚本是打算管理审计数据库的组织的一个问题,我自然会建议他们聘请经验丰富的DBA或高度灵活且经验丰富的软件工程师,并具有创建审计数据库的足够经验. (11认同)
  • 就审计良好方法而言,对于生产而言,将花费大量时间为数据库中的每个表开发单独的审计表,为每个表编写触发器以捕获更改并将其写入审计表.此外,在为所有表格制定单一审计报告时面临巨大挑战,因为每个审计表的结构都不同. (9认同)
  • `PageContent.PageID` 是 FK 到`Page.ID` 并且`Page.CurrentRevision` 是 FK 到`PageContent.Revision` 是否正确?这种依赖真的是循环的吗? (2认同)
  • 我投了票,因为它没有解决所提到的替代方案.它提供了另一种选择,它是非常具体的解决方案,用于非常具体的用例.但我确实看到了建议设计的优点 (2认同)

Ran*_*der 13

如果您使用的是SQL Server 2008,则可能应考虑更改数据捕获.这是2008年的新功能,可以为您节省大量的工作.

  • @Chris你自己用过吗?事实上,它跟踪所有事情......但是能够从中获取有用信息是另一回事.我的自行车不能使用拖拉机轮. (4认同)
  • 值得一提的是,更改数据捕获不允许您传递基本信息,例如 *WHO* 进行了一些更改。您必须创建一个触发器来捕获该信息。在我看来,由于您必须依赖触发器,因此拥有自己的审核触发器更有意义,它可以在任何 SQL Server 版本中工作。 (2认同)

wal*_*lyk 6

我不知道有任何参考,但我确信有人写了一些东西.

但是,如果目的只是记录发生的事情 - 最常见的审计日志使用 - 那么为什么不简单地保留所有内容:

timestamp
username
ip_address
procedureName (if called from a stored procedure)
database
table
field
accesstype (insert, delete, modify)
oldvalue
newvalue
Run Code Online (Sandbox Code Playgroud)

据推测,这是由触发器维持的.

  • 在我看来,这与原始问题中显示的第四个选项具有相同的设计模式. (5认同)

小智 5

我们将为博客应用程序创建一个小型示例数据库。需要两张表:

blog: 存储唯一的帖子 ID、标题、内容和已删除标志。 audit:存储一组基本的历史更改,包括记录 ID、博客文章 ID、更改类型(NEW、EDIT 或 DELETE)以及更改的日期/时间。以下 SQL 创建blog删除的列并为其编制索引:

CREATE TABLE `blog` (
    `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
    `title` text,
    `content` text,
    `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`),
    KEY `ix_deleted` (`deleted`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='Blog posts';
Run Code Online (Sandbox Code Playgroud)

以下 SQL 创建audit表。所有列都被索引,并且为引用 blog.id 的 audit.blog_id 定义了一个外键。因此,当我们以物理方式删除博客条目时,它的完整审核历史记录也会被删除。

CREATE TABLE `audit` (
    `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
    `blog_id` mediumint(8) unsigned NOT NULL,
    `changetype` enum('NEW','EDIT','DELETE') NOT NULL,
    `changetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `ix_blog_id` (`blog_id`),
    KEY `ix_changetype` (`changetype`),
    KEY `ix_changetime` (`changetime`),
    CONSTRAINT `FK_audit_blog_id` FOREIGN KEY (`blog_id`) REFERENCES `blog` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Run Code Online (Sandbox Code Playgroud)