【问题标题】:Best practice to create a Summary Table for an ever-growing table in MySQL为 MySQL 中不断增长的表创建汇总表的最佳实践
【发布时间】:2017-05-08 11:27:43
【问题描述】:

我有一个名为 transactions 的表,其中包含大约 2000 万条记录。该表每秒都在增长。

我计算用户当前余额:

SELECT sum(`amount`) FROM `transactions` WHERE `user_id` = 1000;

我在我的网络应用程序的顶部栏中显示用户当前的余额,用户可以看到他/她有多少余额!

显然每次用户浏览我的网页应用页面时,都必须执行上述查询来计算当前的用户余额!

我想创建一个汇总表来获取当前用户余额,而无需查询具有约 2000 万条记录的 transactions 表!

请注意,在我们的工作流程中,用户可能同时进行多项交易非常常见(用户甚至可能在一秒钟内完成多项交易)。

我认为我们在这里有两种方法:

第一种方法

创建一个具有一对一关系的汇总表,如下所示:

ID  |  user_id  |  current_balance
1   |  1000     |      8590
2   |  1001     |      235
3   |  1002     |      3780
... |  ...      |      ...

每次将新记录插入transactions 表时,我们都会触发一个存储过程来更新摘要表中的用户current_balance

我不知道这种方法是否会破坏 MySQL 的一致性!

第二种方法

创建具有一对多关系的汇总表,如下所示:

ID  |  user_id  |  amount
1   |  1000     |   8590    <--- it's the initial user balance
2   |  1001     |   235     <--- it's the initial user balance
3   |  1002     |   3780    <--- it's the initial user balance
4   |  1000     |   50
5   |  1000     |   -30
6   |  1001     |   10
7   |  1002     |   60
8   |  1000     |   -45

我们每晚清除汇总表(例如00:00 AM),并从transactions 表中重新计算所有用户的当前余额,并将它们插入汇总表。要确定用户的当前余额,我们只需要这样做:

SELECT sum(`amount`) FROM `users_balance` WHERE `user_id` = 1000;

但我对这种方法有些担心。如果某些用户恰好在我们重新计算用户当前余额并将其放入汇总表的时间进行交易怎么办! (正好在00:00 AM

这种方法会破坏一致性吗?


如果您知道此工作流程的更好做法,请告诉我。

附言

我们的网络应用程序是一个短信面板,用户可以通过它发送/接收/等。通过面板直接发送短信或通过 API。我们有一些用户一天发送 100 万条或更多短信!

每次发送 SMS 时,都必须在 transactions 表中插入一条新记录。

我知道 2000 万条记录没什么大不了的,我们可以通过索引实现良好的性能,但正如我上面提到的,它是一个不断增长的表。我很确定明年我们将在transactions 表中拥有数亿条记录。

【问题讨论】:

  • 1.在顶部栏中向用户提供稍微不准确(稍微过时)的余额会产生什么后果?你的申请允许吗? 2. 您是否在(userid, amount) 上创建了复合索引并测量了您的SUM() 查询的性能?如果索引正确,二十兆行并不是很多。您实际上可能不需要汇总表,
  • 我认为这需要专业的帮助。
  • @O.Jones 查看更新
  • @O.Jones 如果我们显示的余额有点不准确,用户可以在余额不足时发送短信!因为在用户发送短信之前,我们会检查用户余额。
  • @HamedKamrava - 没关系;使用 OJoine 的 transactionsbalances 表;它可能会更好。

标签: mysql sql database database-design


【解决方案1】:

正如您所解释的,您正在为每个用户保持平衡。

最好的办法是编写执行两个查询的应用程序代码,可能在一个事务中,但可能不是。

一个查询:

      UPDATE balances
         SET current_balance = current_balance - 1 
       WHERE user_id = 1000

该查询本身无需任何事务即可保持一致性。

(Edit) 它查找balances 表中带有user_id=1000 的行,然后从该行中current_balance 的值中减去1,读取,修改,然后写入排。您可以根据需要对INSERTUPDATE 查询中的列值进行此类运算。

另一个查询

      INSERT INTO transactions (columns) VALUES (values)

您解释应用程序的方式听起来像是您的业务的完整性取决于我在第一个查询中调用balances 的表。 transactions 表是用户活动的日志,用于解释客户余额是如何形成的。因此,如果您让您的应用程序按顺序执行我建议的两个查询,您将拥有出色的余额值和足够好的日志记录。这是构建事务数据库的好方法。

为什么您的balances 应该与您的事务日志分开维护?如果您想给客户 100 条免费消息怎么办?如果您想在一天中的某个时间开始对消息收取额外费用怎么办?如果客户要求对她认为处理不正确的一批消息进行退款怎么办?如果您从交易表中获取余额,您将不得不将各种奇怪的东西放入该表中以处理您不断变化的业务规则。

如果我是你,我会将balances 表的更新隐藏在触发器中吗?我不会。我会把它作为你申请的一部分。更易于查看、更易于调试等。

【讨论】:

  • 如果用户发送了一批消息,我们只需在transactions表中插入一条记录,当然我们设置一个标志来查看哪个是批处理,哪个不是。
  • 我不太明白你的第一个问题"current_balance = current_balance - 1 " 你说的current_balance - 1 是什么意思?
  • 查看我的编辑。它从表中的current_balance 值中减去一。恕我直言,如果您正在使用 gigarow 表编写应用程序,那么您最好阅读有关 SQL 的书籍。 use-the-index-luke.com 是我的最爱。
【解决方案2】:

在我看来,您的第一种方法看起来很酷,但我认为您只需要根据交易计算新值(例如,如果它是贷记/借记)。

但是为什么您每次都访问数据库而不是您可以使用会话变量来保持相同并使用同步,同时使用金额进行任何交易并在汇总表中更新相同的内容。

您还应该使用数据库索引、[在应用程序中]绑定变量来提高性能。

(如果您不想做所有这些事情,一个非常简单的解决方案是分区。您可以使用索引和绑定变量 [在应用程序中] 在事务表上创建按日期分区,这将完成您想通过以上两种方法实现相同的目标。)

您的第二种方法 有许多问题,例如重新计算的数据库开销,这可能会降低/减慢您的应用程序的速度,并且再次以某种方式进行计算。

【讨论】:

  • 谢谢。但是你说bind variable 是什么意思?你的意思是我在应用程序中创建了一个会话变量?
  • 绑定变量是一种数据库功能,可帮助您在数据库级别跳过查询编译并提高应用程序性能。更多详情:akadia.com/services/ora_bind_variables.html
  • 你为什么不去分区?
  • 是的,分区可以是另一种解决方案。我正在尝试找出我可以有多少种方法!
  • 您也可以使用内存数据库来提高性能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-29
  • 2015-08-21
  • 2017-01-17
  • 2015-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多