Ram*_*oni 123 sql database versioning database-design
我们在项目中要求存储数据库中实体的所有修订(更改历史记录).目前我们有2个设计方案:
例如,对于"员工"实体
设计1:
-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"
-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"
Run Code Online (Sandbox Code Playgroud)
设计2:
-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"
-- In this approach we have basically duplicated all the fields on Employees
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName,
LastName, DepartmentId, .., ..)"
Run Code Online (Sandbox Code Playgroud)
有没有其他办法做这件事?
"设计1"的问题在于,每次需要访问数据时我们都必须解析XML.这将减慢进程并添加一些限制,例如我们无法在修订数据字段上添加联接.
而"设计2"的问题在于我们必须复制所有实体上的每个字段(我们有大约70-80个实体,我们希望对其进行修改).
Chr*_*rts 51
我认为这里要问的关键问题是"谁/什么将使用历史"?
如果它主要用于报告/人类可读历史,我们过去实施了这个方案......
创建一个名为'AuditTrail'的表或具有以下字段的表...
[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[OldValue] [varchar](5000) NULL,
[NewValue] [varchar](5000) NULL
Run Code Online (Sandbox Code Playgroud)
然后,您可以向所有表添加"LastUpdatedByUserID"列,每次在表上执行更新/插入时都应该设置这些列.
然后,您可以为每个表添加一个触发器,以捕获发生的任何插入/更新,并在此表中为每个已更改的字段创建一个条目.由于该表还为每个更新/插入提供了"LastUpdateByUserID",因此您可以在触发器中访问此值,并在添加到审计表时使用它.
我们使用RecordID字段来存储正在更新的表的键字段的值.如果它是一个组合键,我们只是在字段之间用'〜'进行字符串连接.
我敢肯定这个系统可能有缺点 - 对于大量更新的数据库,性能可能会受到影响,但对于我的网络应用程序,我们获得的读取次数多于写入次数,而且似乎表现相当不错.我们甚至编写了一个小的VB.NET实用程序来根据表定义自动编写触发器.
只是一个想法!
Sim*_*nro 38
CREATE VIEW EmployeeHistory
AS
, FirstName, , DepartmentId
SELECT EmployeeId, RevisionXML.value('(/employee/FirstName)[1]', 'varchar(50)') AS FirstName,
RevisionXML.value('(/employee/LastName)[1]', 'varchar(100)') AS LastName,
RevisionXML.value('(/employee/DepartmentId)[1]', 'integer') AS DepartmentId,
FROM EmployeeHistories
Run Code Online (Sandbox Code Playgroud)
Mar*_*eld 19
Database Programmer中的History Tables文章博客中可能很有用 - 涵盖了此处提出的一些要点并讨论了增量的存储.
编辑
在历史表论文中,作者(Kenneth Downs)建议维护至少七列的历史表:
不应在历史表中跟踪永不更改或不需要其历史记录的列以避免膨胀.存储数值的增量可以使后续查询更容易,即使它可以从旧值和新值派生.
历史表必须是安全的,禁止非系统用户插入,更新或删除行.应仅支持定期清除以减小总体大小(如果用例允许).
TMS*_*TMS 14
避免设计1; 一旦您需要回滚到旧版本的记录 - 使用管理员控制台自动或"手动",它就不是很方便了.
我没有真正看到Design 2的缺点.我认为第二个,History表应该包含第一个记录表中的所有列.例如,在mysql中,您可以轻松地创建与另一个表(create table X like Y)具有相同结构的表.而且,当您要在实时数据库中更改记录表的结构时,您仍然必须使用alter table命令 - 并且您也无需为历史记录表运行这些命令.
笔记
RevisionId列;ModifiedBy- 创建特定修订版的用户.您可能还希望有一个字段DeletedBy来跟踪删除特定修订的人员.DateModified应该是什么意思 - 要么意味着创建了这个特定的修订版,要么意味着当这个特定版本被另一个版本替换时.前者要求字段位于记录表中,并且第一眼看上去似乎更直观; 但是,对于删除的记录(删除此特定修订版的日期),第二个解决方案似乎更实用.如果您选择第一个解决方案,您可能需要第二个字段DateDeleted(当然,只有当您需要它时).取决于你和你真正想要记录的内容.设计2中的操作非常简单:
修改如果你去设计2,那么所需的所有SQL命令都将非常简单,以及维护!也许,如果你在Records表中使用辅助列(RevisionId,DateModified)也会容易得多,以使两个表保持完全相同的结构(除了唯一键)!这将允许简单的SQL命令,它将容忍任何数据结构更改:
insert into EmployeeHistory select * from Employe where ID = XX
Run Code Online (Sandbox Code Playgroud)
别忘了使用交易!
至于扩展,这个解决方案是非常有效的,因为你不会来回转换XML中的任何数据,只是复制整个表行 - 非常简单的查询,使用索引 - 非常有效!
Kje*_*dal 13
我们已经实现了一个非常类似于Chris Roberts建议的解决方案的解决方案,这对我们来说非常有效.
唯一的区别是我们只存储新值.旧值毕竟存储在上一个历史记录行中
[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[NewValue] [varchar](5000) NULL
Run Code Online (Sandbox Code Playgroud)
假设您有一个包含20列的表格.这样,您只需存储已更改的确切列,而不必存储整行.
Con*_*lls 12
如果您必须存储历史记录,请创建一个与您正在跟踪的表格具有相同架构的影子表,以及"修订日期"和"修订版类型"列(例如"删除","更新").写入(或生成 - 见下文)一组触发器来填充审计表.
制作一个能够读取表的系统数据字典的工具并生成一个创建影子表的脚本和一组用于填充它的触发器,这是相当简单的.
不要尝试使用XML,XML存储的效率远低于此类触发器使用的本机数据库表存储.
Ramesh,我参与了基于第一种方法的系统开发.
事实证明,将修订版存储为XML会导致数据库的巨大增长并显着减慢速度.
我的方法是每个实体有一个表:
Employee (Id, Name, ... , IsActive)
Run Code Online (Sandbox Code Playgroud)
其中IsActive是最新版本的标志
如果要将一些其他信息与修订相关联,可以创建包含该信息的单独表,并使用PK\FK关系将其与实体表链接.
这样,您可以将所有版本的员工存储在一个表中.这种方法的优点:
请注意,您应该允许主键不唯一.
我过去看过这种方式的方式是有的
Employees (EmployeeId, DateModified, < Employee Fields > , boolean isCurrent );
Run Code Online (Sandbox Code Playgroud)
你永远不会在这个表上"更新"(除了更改isCurrent的有效),只需插入新行.对于任何给定的EmployeeId,只有1行可以具有isCurrent == 1.
维护它的复杂性可以通过视图和"而不是"触发器隐藏(在oracle中,我假设其他RDBMS类似的东西),如果表太大而无法通过索引处理,您甚至可以转到物化视图) .
这种方法没问题,但最终可能会出现一些复杂的查询.
就我个人而言,我非常喜欢你的Design 2方式,这也是我过去的做法.它易于理解,易于实现且易于维护.
它还为数据库和应用程序创建了很少的开销,尤其是在执行读取查询时,这很可能是99%的时间.
自动创建历史表和触发器进行维护也很容易(假设它将通过触发器完成).
| 归档时间: |
|
| 查看次数: |
33321 次 |
| 最近记录: |