【问题标题】:Best practice for keeping denormalized schema up to date?保持非规范化模式最新的最佳实践?
【发布时间】:2010-10-25 17:07:38
【问题描述】:

我正在创建一个带有积分的游戏,所以我有一个这样的架构:

create table points (
  id int,
  points int,
  reason varchar(10)
)

并且要获得用户拥有的点数是微不足道的:

select sum(points) as total from points where id = ?

然而,随着积分表的扩大,性能变得越来越重要。我想做类似的事情:

create table pointtotal (
  id int,
  totalpoints int
)

使它们保持同步的最佳做法是什么?我是否尝试在每次更改时更新 pointtotal?我是否运行每日脚本?

(假设我有正确的键 - 为了简洁起见,它们被省略了)

编辑:

以下是我省略的一些特性,但应该会有所帮助:

点的插入/更新并不是那么频繁 有大量的条目,也有大量的请求 - 正如您所见,密钥非常简单。

【问题讨论】:

  • 后端语言是 Perl,不确定是否重要。

标签: mysql database-design optimization


【解决方案1】:
【解决方案2】:

您还可以创建另一个报告架构,并通过一些执行计算的进程以固定的时间间隔重新加载它。这不适用于实时信息 - 但这是一种非常标准的做事方式。

【讨论】:

    【解决方案3】:

    在这种情况下你可以选择任何一种方式,因为它不是很复杂。

    作为一般规则,我更喜欢允许数据暂时不一致,只要有足够的冗余,并有一个周期性的过程来解决不一致。但是,有一个触发机制来鼓励早期执行周期性过程并没有什么坏处。

    我有这种感觉是因为依靠基于事件的通知样式的代码来保持一致,在更复杂的情况下,会使代码变得非常复杂,并使验证变得困难。

    【讨论】:

      【解决方案4】:

      我建议创建一个用于访问和修改数据的层。您可以使用这些数据库访问函数来封装所有表中的数据维护,以保持冗余数据的同步。

      【讨论】:

        【解决方案5】:

        最佳实践是使用规范化的数据库模式。然后 DBMS 会使其保持最新状态,因此您不必这样做。

        但我理解使非规范化设计具有吸引力的权衡取舍。在这种情况下,最佳做法是在每次更改时更新总数。调查触发因素。这种做法的好处是您可以使总数与更改保持同步,因此您永远不必考虑它是否已过时。如果提交了一项更改,那么更新的总数也将提交。

        但是,这在并发更改方面存在一些弱点。如果您需要容纳对相同总数的并发更改,并且您可以容忍总数“最终一致”,那么请使用总数的定期重新计算,这样您就可以确保一次只有一个进程正在改变总数。

        另一个好的做法是在数据库之外缓存聚合总数,例如memcached 或应用程序变量中,因此您不必每次需要显示值时都访问数据库。


        查询“select sum(points) as total from points where id = ?”应该花费 2 秒,即使您有大量行和大量请求。

        如果您在(id, points) 上定义了covering index,则查询可以产生结果,而无需从表中读取数据;它可以通过读取索引本身的值来计算总数。使用 EXPLAIN 分析您的查询并在 Extra 列中查找“使用索引”注释。

        CREATE TABLE Points (
          id     INT,
          points INT,
          reason VARCHAR(10),
          KEY    id (id,points)
        );
        
        EXPLAIN SELECT SUM(points) AS total FROM Points WHERE id = 1;
        
        +----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
        | id | select_type | table  | type | possible_keys | key  | key_len | ref   | rows | Extra                    |
        +----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
        |  1 | SIMPLE      | points | ref  | id            | id   | 5       | const |    9 | Using where; Using index | 
        +----+-------------+--------+------+---------------+------+---------+-------+------+--------------------------+
        

        【讨论】:

        • 理想情况下,但请尝试说服人们等待 2 秒等待查询!
        • "从 id = 的点中选择 sum(points) 作为总和?"不应花费 2 秒。
        • 触发器可能是要走的路。我没有提到插入/更新不是那么频繁。
        • 另外,最好避免对“按总和(点)排序”类型的查询进行文件排序..
        【解决方案6】:

        还有另一种方法:缓存。即使它只缓存了几秒钟或几分钟,这也是对经常访问的值的胜利。并且可以将缓存获取与缓存更新分离。这样,一个合理的当前值总是在恒定时间内返回。棘手的一点是让 fetch 产生一个新进程来进行更新。

        【讨论】:

          【解决方案7】:

          在同一张表上有额外的totalpoints列,并为每行创建/更新创建/更新totalpoints的值。

          如果您需要某条记录的总分,您可以在不计算总分的情况下查找该值。比如你需要totalpoint的最后一个值,你可以这样得到:

          SELECT totalpoint FROM point ORDER BY id DESC LIMIT 1;
          

          【讨论】:

            【解决方案8】:

            一定要保持基础表标准化。如果您可以处理可能是一天前的数据,请每天运行一个脚本(您可以安排它),以进行汇总并填充新表。最好每晚从源表中重新创建事物,以防止两者之间出现任何不一致。

            也就是说,对于您的记录的大小,您的服务器必须非常慢,或者记录数非常多,因为这么小的记录,在 id 上带有索引字段的记录应该很快为您求和 - 但是,我我的心态是,如果您可以将用户响应时间缩短几秒钟,那么即使 DB 纯粹主义者反对,也没有理由不使用汇总表。

            【讨论】:

            • 大量记录和大量请求。
            猜你喜欢
            • 2021-07-15
            • 1970-01-01
            • 1970-01-01
            • 2011-10-31
            • 2018-04-03
            • 1970-01-01
            • 1970-01-01
            • 2013-09-30
            • 2011-12-30
            相关资源
            最近更新 更多