12

解释:

  • 我有一个脚本,它显示每个用户的总积分(声誉),它在数据库中有一个历史表,用于记录所有用户获得的积分

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

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

问题:

  1. 我想知道,如果历史记录表如此庞大,这(选择所有历史记录的总和以显示用户信用INSTEAD一个不同的用户总信用记录表)是否会产生问题?(假设几年后+100,000,000 条记录)

  2. 这是大多数专业程序员所做的吗?(如果不是,是什么)

  3. 那么历史部分呢,如果用户想要查看信用历史,我们应该在 * SELECT * 或否时使用LIMIT 100条记录来限制它(为了性能)

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

编辑回答后:

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

  1. 如果我们恰好在用户收到一些积分时这样做,那么用户是否有可能同时收到两个不同的积分(这是可能的),并且我们不能将自动增量放入 Totals 表中(因为每个用户只有 1 条记录)我们可能会错过 1 个学分,或者如果有解决此问题的方法,我不知道

  2. 如果我们将 Cron-Job 设置为频繁执行,那么在 cron 作业刷新总计表之前,用户积分不会是最新的

4

7 回答 7

7

如果我们恰好在用户收到一些积分时执行此操作,那么用户可能会同时收到两个不同的积分(很有可能),并且我们不能将 Auto Increment 放入 Totals 表中(因为每个用户只有 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'.

于 2013-06-30T23:36:28.940 回答
6

为了使其随着数据库中条目数量的增长而可扩展,您可以考虑以下事项:

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

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

于 2013-07-01T18:00:05.240 回答
3
  1. 是的,它会的。我建议将(子)总计保存在不同的表中,并让存储过程自动更新它们。
  2. 对于大规模,您必须开始非规范化,因此请保留一个总和,这样您就不必不断地重新计算它。
  3. 分页对于性能和可用性都是一个好主意,看到数千行并不能提高可读性。但是,我建议按范围过滤(即id BETWEEN x AND y,而不是LIMIT 100 OFFSET 500
  4. 是的,它会的。如果有什么东西不会经常改变。缓存它。例如……在 Redis 或 Memcached 中。
于 2013-06-30T17:16:33.037 回答
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

当任何东西被插入到你的历史表中时,它就会触发这个触发器。对于每个插入的行,触发器将新值插入到信誉表中,或者如果用户已经存在,则更新总值。

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

SQL Fiddle 演示

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

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

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 更有效。

于 2013-07-04T00:35:27.617 回答
2
  1. 像这里的其他人一样,我主张将用户积分分成“实时”和“历史”表。您可以让每晚(或每周或其他)的工作将记录从实时迁移到历史记录。如果您可以保持“实时”表足够紧凑,以使其(以及它支持的索引)大部分都在内存中,那么性能应该不是问题。您可能希望在用于维护历史表的任何作业的末尾添加第三个“总学分”表:这样,查看总学分(不包括今天的)是一个单一的索引读取。

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

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

  4. 这些问题涉及到整体架构:当然有办法在 Web 会话过程中缓存查询结果,如果这些查询很昂贵的话。这取决于您的整体架构和您使用的技术堆栈。

--- 第二组问题

  1. 将学分移动到日边界的历史记录中。即使在“实时”表中,也可以使用当前日期作为选择标准的一部分。这样,您将永远不会无意中丢弃(或重复计算)学分。

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

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

于 2013-07-05T23:44:02.150 回答
1

我想说的是像现在一样跟踪您的历史数据,但还将最终结果缓存在 credits 表或 user 表的属性中。

在伪代码中:

 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 表进行定期审核。

于 2013-07-08T04:00:59.413 回答
1

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

  1. 是的,出于性能目的,您需要存储预先计算的声誉。
  2. 如果有包含用户信息的表格 - 添加字段“reputation_sum”(没有意义分隔此数据),如果没有 - 制作特殊表格。
  3. 当声誉发生变化时,您会知道差异,将差异添加到“reputation_sum”中。

我的意思是 - 不要使用“SELECT SUM of all history ...”来计算“reputation_sum”的新值。当您从“历史”表中添加/更新/删除记录时,计算 total_reputation_change_value 并更新“reputation_sum”而不重新计算“历史”表的所有记录的总和。插入操作的 total_reputation_change_value 将是 - “credits”字段的值;与 DELETE 相同,但带有一元减号;UPDATE 的新旧值之间的差异。如果声誉经常变化,这将提供更多的请求/秒。这也会更多地违反数据完整性。如果你害怕这一点 - 做一个特殊的 cron 作业,它会通过定期汇总历史记录来刷新“reputation_sum”数据。

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

现在让我来回答你的问题。

我想知道,如果历史记录表如此庞大,这(选择所有历史记录的总和以显示用户信用 INSTEAD 为用户的总信用记录提供不同的表)是否会产生问题?(假设几年后+100,000,000 条记录)

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

这是大多数专业程序员所做的吗?(如果不是,是什么)。

这是常见的解决方案,在大多数情况下都能正常工作。也许这对您来说不是最佳选择,但我们没有足够的信息来提供更好的建议。在这种情况下,专业程序员可以使用多种方法,具体取决于项目的具体情况。

像这样的问题也很好的解决方案是缓存数据。但它服务于一些不同的需求。您应该确保用户提出复杂但相同的请求,并且数据不会经常更改。

如果数据更改不是很频繁,其他很好的优化技巧就是制作索引

历史部分呢,如果用户想要查看信用历史记录,我们应该在 *SELECT*ing 或否时使用 LIMIT 100 条记录来限制它(为了性能)

你当然应该。在大多数情况下,用户无法同时查看全部 100 (200, 300) 个项目。他们也不会每次都查找所有记录(据我了解,他们将在本节中有很多记录)。即使用户将看到所有记录,这也将花费一些时间(以秒或几分钟为单位)。对单个请求使用限制将随着时间的推移分配负载并减少负载峰值。这将提高用户的平均性能。

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

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

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

如果我们恰好在用户收到一些积分时这样做,那么用户是否可能同时收到两个不同的积分(这是可能的),并且我们不能将自动增量放入 Totals 表中(因为每个用户只有 1 条记录)我们可能会错过 1 个学分,或者如果有解决此问题的方法,我不知道

您不应该关心事务完整性,因为这是 DBMS 问题。每次声誉发生变化时,您应该只对“reputation_sum”字段进行更改。我的意思是 - 只是做 SQL 请求。

如果我们将 Cron-Job 设置为频繁执行,那么在 cron 作业刷新总计表之前,用户积分不会是最新的

不要使用 cron。或者,如果需要,仅用于数据实现。

于 2013-07-08T10:48:12.073 回答