在SQL Server中自动更新冗余/非规范化数据

时间:2011-01-25 01:22:36

标签: sql-server sql-server-2005 denormalization

在我的数据库设计中使用高级冗余非规范化数据来提高性能。我经常会存储通常需要加入或计算的数据。例如,如果我有用户表和任务表,我会冗余地存储用户名 UserDisplayName 在每个任务记录中。另一个例子是存储聚合,例如将 TaskCount 存储在用户表中。

  • 用户
    • 用户ID
    • 用户名
    • UserDisplayName
    • TaskCount
  • 任务
    • 的TaskID
    • TASKNAME
    • 用户ID
    • 用户名
    • UserDisplayName

这非常适合性能,因为应用程序具有比插入,更新或删除操作更多的读取,并且因为某些值(如用户名)很少更改。然而,最大的缺点是必须通过应用程序代码或触发器强制执行完整性。更新可能非常麻烦。

我的问题是,这可以在SQL Server 2005/2010中自动完成...可能通过持久/永久视图。有人会推荐另一种可能的解决方案或技术我听说基于文档的数据库(如CouchDB和MongoDB)可以更有效地处理非规范化数据。

1 个答案:

答案 0 :(得分:10)

您可能希望在转移到NoSQL解决方案之前先尝试索引视图:

http://msdn.microsoft.com/en-us/library/ms187864.aspx

http://msdn.microsoft.com/en-us/library/ms191432.aspx

使用索引视图可以让您将基础数据保存在正确规范化的表中,并保持数据完整性,同时为您提供该数据的非规范化“视图”。我不建议将此用于高度事务性的表,但是你说它在读取时比写入更重,所以你可能想看看它是否适合你。

根据您的两个示例表,一个选项是:

1)在User表中添加一列定义为:

TaskCount INT NOT NULL DEFAULT (0)

2)在Task表上添加一个Trigger,定义为:

CREATE TRIGGER UpdateUserTaskCount
ON dbo.Task
AFTER INSERT, DELETE
AS

;WITH added AS
(
    SELECT  ins.UserID, COUNT(*) AS [NumTasks]
    FROM    INSERTED ins
    GROUP BY    ins.UserID
)
UPDATE  usr
SET     usr.TaskCount = (usr.TaskCount + added.NumTasks)
FROM    dbo.[User] usr
INNER JOIN  added
        ON  added.UserID = usr.UserID


;WITH removed AS
(
    SELECT  del.UserID, COUNT(*) AS [NumTasks]
    FROM    DELETED del
    GROUP BY    del.UserID
)
UPDATE  usr
SET     usr.TaskCount = (usr.TaskCount - removed.NumTasks)
FROM    dbo.[User] usr
INNER JOIN  removed
        ON  removed.UserID = usr.UserID
GO

3)然后做一个具有以下内容的视图:

SELECT   u.UserID,
         u.Username,
         u.UserDisplayName,
         u.TaskCount,
         t.TaskID,
         t.TaskName
FROM     User u
INNER JOIN   Task t
        ON   t.UserID = u.UserID

然后按照上面链接中的建议(WITH SCHEMABINDING,Unique Clustered Index等)使其“持久化”。虽然如上所示在SELECT中的子查询中进行聚合是低效的,但是这种特定情况旨在在具有比写入更高的读取的情况下进行非规范化。因此,执行索引视图将保持整个结构(包括聚合)的物理存储,以便每次读取都不会重新计算它。

现在,如果某些用户没有任何任务需要LEFT JOIN,那么由于创建它们的5000限制,索引视图将无法工作。在这种情况下,您可以创建一个真正的表(UserTask),它是您的非规范化结构,并通过用户表上的触发器填充它(假设您执行上面显示的触发器,它根据更改的用户表更新用户表)任务表)或者您可以跳过用户表中的TaskCount字段,并在两个表上都有触发器来填充UserTask表。最后,这基本上就是索引视图所做的事情,而不必编写同步触发器。