【问题标题】:Updating redundant/denormalized data automatically in SQL Server在 SQL Server 中自动更新冗余/非规范化数据
【发布时间】:2011-06-14 21:59:32
【问题描述】:

在我的数据库设计中使用高级别的冗余、非规范化数据来提高性能。我会经常存储通常需要连接或计算的数据。例如,如果我有一个 User 表和一个 Task 表,我将冗余存储 UsernameUserDisplayName在每个 Task 记录中。另一个例子是存储聚合,例如将 TaskCount 存储在 User 表中。

  • 用户
    • 用户 ID
    • 用户名
    • 用户显示名称
    • 任务计数
  • 任务
    • 任务 ID
    • 任务名称
    • 用户 ID
    • 用户名
    • 用户显示名称

这对性能很有好处,因为应用程序的读取次数比插入、更新或删除操作多得多,而且像 Username 这样的一些值很少更改。然而,最大的缺点是必须通过应用程序代码或触发器来强制执行完整性。这对于更新来说可能非常麻烦。

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

【问题讨论】:

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


    【解决方案1】:

    在迁移到 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、唯一聚集索引等)使其“持久化”。虽然如上所示在 SELECT 中的子查询中进行聚合是低效的,但这种特定情况旨在在读取高于写入的情况下进行非规范化。因此,索引视图将保持整个结构(包括聚合)物理存储,因此每次读取都不会重新计算它。

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

    【讨论】:

    • 持久计算列在这里也可能是一个很好的策略。由于很少有 DML 语句存在问题,因此它们不需要经常更新,据我所知,它们与表上的物理记录一样好。
    • @Jeremy:如果聚合是索引视图的一部分,那么它自然应该与其余的物化字段一起物理存储,因此如果解决方案不需要显式的持久计算列是使用索引视图。但是,如果不使用索引视图,那么我同意(你是对的),持久计算列是值得研究的,因为它是物理存储的,而不是每次读取行时计算的。
    • 计算列听起来确实是一个不错的选择,但是否可以创建一个从另一个表(User.DisplayName --> Task.UserDisplayName)计算的计算列,并在另一个表时自动更新该列表/记录更新了吗?
    • @Jonathan:否和是,然后是但是。不,可用于计算机列的字段需要来自表本身。是的,您“可以”创建一个标量 UDF 以接受来自 Row 的值(例如 UserID),然后 JOIN 到另一个表以返回单个值(例如 DisplayName)。但是,在计算列中执行 UDF 会出现性能问题(我不确定是否可以将其标记为 PERSISTED),并且在您的情况下,具有 CLUSTERED 索引的视图中两个表之间的简单 JOIN 将解决所有问题的问题,您将可以访问两个表中的所有字段。
    • @srutzky,在这种情况下,计算列似乎不适用于非规范化。我将尝试索引视图方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-23
    • 2013-11-21
    • 2016-10-22
    • 2017-06-17
    • 2023-03-13
    • 2011-09-24
    相关资源
    最近更新 更多