数据库中历史表的SUM,以显示用户的总信用(信誉)

时间:2013-06-30 16:53:55

标签: php mysql

  

说明:

  • 我有一个脚本,显示每个用户的总信用(信誉),并且在数据库中有一个历史表,用于获取所有用户的信用

以下是我的历史数据库表的示例:

 +----------------------------------------------+
 | DATE     ID     USERNAME       CREDITS       |
 +----------------------------------------------+
 | ...      1         X              12         |
 | ...      2         E               2         |
 | ...      3         X               1         |
 | ...      4         X              -7         |
 | ...      5         O               4         |
 +----------------------------------------------+
  • 我的脚本使用SELECT SUM FROM表WHERE username ='X'并回显它,所以在这种情况下对于用户X(12 + 1 - 7)它回应6

  

问题:

  1. 我想知道不是这个( 所有历史记录的SELECT SUM 来向用户显示 INSTEAD 如果历史表如此庞大,那么用户的总信用额度会有不同的表 )会出现问题吗? (几年后我们说+100,000,000条记录)

  2. 大多数专业程序员都这样做吗? (如果没有,那是什么)

  3. 历史记录部分怎么样,如果用户想要查看积分历史记录,我们应该在* SELECT 记录时将其限制为 LIMIT 100 记录em> * ing或no(性能)

  4. 这应该在每次页面刷新或每页更改时运行吗? (如果1000个用户在线并且每次刷新都应用此SELECT查询,则不会降低服务器速度)

  5.   

    编辑回答后:

    但是如果我们必须将总数保存在不同的表中并自动更新它们,则存在两个问题:

    1. 如果我们确切地说,当用户收到一些积分时,用户不可能在同一时间收到两个不同的积分(这是可能的),因为我们不能将自动增量放在总计中表(因为每个用户只有1条记录)我们可能会错过1个信用,或者如果有这个问题的解决方案,我不知道

    2. 如果我们设置一个Cron-Job来经常这样做,那么在cron作业刷新总计表之前,用户信用不是最新的

7 个答案:

答案 0 :(得分:7)

  

如果我们完全在用户收到一些信用时这样做,用户可能会在同一时间(非常可能)收到两个不同的信用,并且因为我们无法将自动增量放在总计表中(因为每个用户只有有1条记录)我们可能会错过1个积分并且不会将它添加到总计表中,或者如果有这个问题的解决方案,我不知道,我现在才应该在这些情况下使用AI

我们不会错过。检查以下SQL语句:

INSERT INTO history SET username = 'X', credits = 2;
UPDATE users SET credits_sum = (SELECT SUM(credits) FROM `history` WHERE username = 'X') WHERE username = 'X';

即使存在两个添加信用的事件被激发的情况,我们的credits_sum也会是最新的,因为它是从存储在数据库中的数据更新的(不是在应用程序中 - 在这种情况下,有时可能会有一些差异)。

当然应该使用users表中的主键而不是username = 'X'

答案 1 :(得分:6)

要在数据库中的条目数增加时使其可扩展,您可以考虑以下事项:

创建两个表:一个,“历史总计”,包含每个用户在今天00:00:00之前的总计;第二个可以是(今天的学分)的(相对)小表。

当您需要当前状态时,将“历史表”中的查找添加到“新信用”(小表,因此很快)。在午夜,您将所有当天的积分添加到总计中,然后(在延迟之后)从“今天”表中删除相应的元素。您需要延迟,因此在查询时不会从“当前”表中删除元素。为确保您始终得到正确的答案,您必须使用“计算的最新/时间”字段标记“历史”数据;并且在您更新了总计之后,然后从“当前”数据库中删除“到目前为止的所有信息”。如果您首先检查总计数据库中的总数和&时间戳,然后从当前数据库计算“总和”,应该没有错误的可能性。这就是更新总计和从当前数据库中删除项目之间出现延迟的原因。

答案 2 :(得分:3)

  1. 是的,它会的。我建议将(子)总计保存在不同的表中,并让存储过程自动更新它们。
  2. 大规模你必须开始非规范化,所以保留一笔钱,这样你就不必经常重新计算它。
  3. 对于性能和可用性而言,分页是一个好主意,看到成千上万行无法提供可读性。不过,我建议按范围过滤(即id BETWEEN x AND y而不是LIMIT 100 OFFSET 500
  4. 是的,它会的。如果有什么不经常改变的话。缓存它。例如......在Redis或Memcached中。

答案 3 :(得分:3)

我建议使用一个单独的表来跟踪每个用户的总积分,然后使用触发器使该表保持最新状态。

假设跟踪总积分的表看起来像这样:

CREATE TABLE reputation (
  username varchar(20) primary key,
  total int
)

然后触发器将如下所示:

CREATE TRIGGER historyInsert AFTER INSERT ON history
FOR EACH ROW BEGIN
  INSERT INTO reputation (username,total)
  VALUES (NEW.username,NEW.credits)
  ON DUPLICATE KEY UPDATE total = total + NEW.credits;
END

当您的历史记录表中插入了任何内容时,它会触发此触发器。对于每个插入的行,触发器会将新值插入信誉表,或者如果用户已存在则更新 total 值。

请注意,INSERT ... ON DUPLICATE KEY UPDATE是MySQL中的原子操作,因此您不必担心同时发生两次更新。

SQL Fiddle demo

作为创建单独的信誉表的替代方法,如果您已经拥有某种表单的用户表,则可以始终为每个用户存储总信用额。假设每个用户已经有一个条目,因此触发器不必担心创建新条目 - 它只是更新它们。

然后触发代码变得更加简单:

CREATE TRIGGER historyInsert AFTER INSERT ON history
FOR EACH ROW BEGIN
  UPDATE users SET total = total + NEW.credits
  WHERE username = NEW.username
END

同样,这个UPDATE查询是原子的。它只是增加总计字段,所以如果同时发生两次更新,它们就不会相互覆盖 - 这两个数量都将被添加到总数中。

这比每次插入新值时在整个历史记录中计算SUM更有效。

答案 4 :(得分:2)

  1. 和其他人一样,我主张分为用户信用的“实时”和“历史”表格。您可以每晚(或每周或任何其他)将记录从实时迁移到历史记录。如果你可以保持“实时”表足够紧凑(并且它的支持索引)主要在内存中,性能应该不是问题。您可能希望在用于维护历史表的任何工作结束时添加第三个“总信用额”表:这样,查看信用总计(不包括今天的)是单个索引读取。

  2. 据推测,一旦添加,学分就是不可变的。因此,如果他们不改变,强迫你的程序重新添加它们,反复添加就没有什么意义了。如果您不需要历史信用的交易详细信息,请按月汇总。

  3. 该限制可以帮助一些,但突出了设计缺陷:不存储您不会引用的记录:它们继续使用磁盘空间,索引空间和内存。你必须对你真正需要的东西保持相当理性(和冷血)。看看您的商业模式:您为什么希望用户能够查看他们的信用记录?如果你切断了他们可以在任意限制下查看的内容,你会疏远他们吗?您必须能够自己确定策略,因为您了解自己的业务和用户。但要使技术成为政策的仆人,而不是相反。

  4. 这些问题涉及整体架构:如果这些查询很昂贵,肯定会在网络会话过程中缓存查询结果。这取决于您的整体架构和您正在使用的技术堆栈。

  5. ---第二组问题

    1. 在日间边界将信用转移到历史记录中。即使在“实时”表格中,也可以使用当前日期作为选择条件的一部分。这样,你就不会无意中掉落(或重复计算)积分。

    2. 不确定我理解。积分将在获得的确切时刻插入“实时”表格,然后复制到日期边界的历史表格中。 “实时”表格总是是当天最新的,历史表格始终是最新的超过一天的东西。

    3. 我希望你的项目进展顺利......

答案 5 :(得分:1)

我要说的是跟踪您现在的历史数据,但也将最终结果缓存到信用表或用户表的属性中。

在伪代码中:

 function postCreditTransaction($username, integer $credit){
      $db->insert("credit_history", array("USERNAME"=>$username, "CREDIT"=>$credit));
      $db->update("update user_table set credit = credit + $credit where username = ".$db->quote($username));
 }

这将为您提供信用记录提供的详细信息,但对总数的访问权限较低。

要确保所有内容都在步骤中,您可以针对缓存字段中的缓存值执行credit_history表的定期审核。

答案 6 :(得分:1)

好的,让我们从短暂的简历开始:

  1. 是的,您需要存储预先计算的性能声誉 目的。
  2. 如果有包含用户信息的表 - 添加字段“reputation_sum”(没有意义分隔这些数据),如果没有 - 制作特殊表格。
  3. 当声誉发生变化时,您会知道差异,请将该差异添加到“reputation_sum”。
  4. 我的意思是 - 不要使用“所有历史的SELECT SUM ...”来计算“reputation_sum”的新值。 当您从“历史”表中添加/更新/删除记录时,计算total_reputation_change_value并更新“reputation_sum”而不重新计算“历史”表的所有记录上的总和。 INSERT操作的total_reputation_change_value将是 - “credits”字段的值; DELETE也一样,但是一元减去; UPDATE的旧值和新值之间的差异。 如果声誉经常变化,这将提供更多的请求/ s。 这也会更多地违反数据完整性。如果你害怕这一点 - 制作特殊的cron作业,它将通过定期记录历史记录来刷新“reputation_sum”数据。但在大多数情况下(具有正确的工作流程),没有必要这样做。

    另外,我建议您不要将USERNAME用作外键(如果您有“users”表,这是外键)。最好使整数USERID。它将在历史表中更快地搜索。

    现在让我回答你的问题。

      

    我想知道是不是这个(所有历史记录的SELECT SUM显示用户信用INSTEAD有一个不同的表用于用户的总信用)如果历史表如此巨大会产生问题? (几年后我们说+100,000,000条记录)

    是的,如果每次从表中计算出声誉,其中“在几年之后就会说+100,000,000条记录”,由于计算量的原因,这将是非常低效的。如果你有足够的服务器,也许没有滞后,但我相信他们会这样做)

      

    这是大多数专业程序员的工作吗? (如果没有,那是什么)。

    这是常见的解决方案,在大多数情况下都可以正常使用。 也许它不适合你,但我们没有足够的信息来提供更好的建议。 在这种情况下,专业程序员可以使用一堆方法,取决于项目的具体情况。

    此类问题的良好解决方案是缓存数据。但它有一些不同的需求。您应确保用户发出复杂但相同的请求,并且不经常更改数据。

    如果数据不经常更改,那么其他优秀的优化技巧 - making index

      

    历史部分怎么样,如果用户想要查看积分历史记录,我们应该使用LIMIT 100记录来限制它* * * *或*(性能)

    当然你应该。在大多数情况下,用户无法同时看到所有100(200,300)个项目。他们也会查找所有记录(据我所知,他们将在本节中有很多记录)并非每次都有。 即使用户将看到所有记录,无论如何,这将花费一些时间在几秒或几分钟。对单个请求使用限制将随时间分配负载并降低负载峰值。这将提高用户的平均表现。

    因此,为了获得性能优势,您应该为大量内容提供部分加载功能。

      

    这应该在每次页面刷新或每个页面更改时运行吗? (如果1000个用户在线,并且每次刷新都应用此SELECT查询,则不会降低服务器速度)

    用户的任何活动都会降低服务器的速度,这是不可能解决的问题:)但在这里我们讨论使用不同方法的有效性,以获得所需的功能。 至于我,我不知道“如果1000个用户在线并且每次刷新都应用了这个SELECT查询”意味着什么。这是一个论坛,您可以在其中看到很多具有声誉的用户记录吗?或者它可能只是一个声誉的个人资料页面?或者您可能希望看到1000名在线用户的声誉,而不是离线?

      

    如果我们确实在用户收到一些积分时这样做,那么用户就不可能在同一时间收到两个不同的积分(这是可能的),因为我们不能将自动递增放在总计表中(因为我们可能会错过1个积分,或者如果有解决此问题的解决方案,我不知道这个

    您不应该关心事务完整性,因为它是DBMS问题。您应该只在每次声誉更改时将更改带到“reputation_sum”字段。我的意思是 - 只做SQL请求。

      

    如果我们设置一个Cron-Job来经常这样做,那么在cron job刷新总计表之前,用户信用不是最新的

    不要使用cron。或者,如果您愿意,也可以仅用于数据实现。