【问题标题】:Optimizing MySQL table for selecting many rows in date range优化 MySQL 表以在日期范围内选择多行
【发布时间】:2020-01-11 16:22:41
【问题描述】:

我在 MySQL 中有一个 InnoDB 表,我必须在其中选择和汇总日期范围内的大量数据。似乎我无法达到它对用例运行得足够快的程度。

表格如下:
user_id: 整数
日期:日期
金额:整数

该表有几亿行。
一个日期范围最多可以返回 1000 万行。
金额为1-10

我在所有三列上都有一个复合索引,顺序为:user_id、日期、金额。

我用于选择的查询是:

SELECT   
    SUM(amount)  
FROM table  
WHERE user_id = ?  
AND request_date <= ?  
AND request_date >= ?

我将日期硬编码到查询中。

我还能做些什么来加快这个查询的速度?我应该能够每秒执行大约 20 次查询。

它在 DI 上运行,具有 8gb RAM 和 4 个 CPU(非专用)。

更新
EXPLAIN 的输出是:

select_type: SIMPLE  
type: range
possible_keys: composite  
key: composite  
key_len: 7 
ref: null 
rows: 14994440  
Extra: Using where; Using index

【问题讨论】:

  • 能否请您发布 EXAPLIN 的输出? (在上面的查询前面加上 EXPLAIN 这个词)
  • 添加了 EXPLAIN 输出的更新
  • 对表进行分区。您搜索的日期范围是否跨越数年、数十年?什么?如果需要,您可以按用户和年份进行分区。这样一来,数亿行就少得多了。
  • 范围为 30 天。但是从当前日期开始运行 30 天。所以我无法划分特定月份。在 30 个周期内,每个用户仍然可以有多达 1000 万行,乘以几百个用户。
  • @xQbert - 他仍然必须触摸该用户在​​该日期范围内的所有行。所以分区没有好处。

标签: mysql database database-design innodb


【解决方案1】:

我过去曾使用过各种技术来做类似的事情。

  • 您应该考虑对表进行分区。这涉及创建一个包含分区标识符的列,可以是日期或年月

  • 通过拆分日期和时间部分,我获得了一些性能提升。优点是您可以通过查看date 字段快速获取某个日期的所有数据,甚至无需考虑时间部分。

  • 如果您知道您将请求什么样的数据,并且您可以允许一些延迟,您可以预先计算。看起来您正在使用日志数据,所以我假设任何比今天更早的查询结果都不会改变。您应该利用它,例如通过使用包含聚合数据的单独表。如果您只需要计算“今天”,事情会快得多。或者接受数字有点旧,你可以定期预先计算。

我所说的表格可能是这样的:

CREATE table aggregated_requests AS
SELECT user_id, request_date, SUM(amount) as amount
FROM table  

之后,像这样重写上面的查询,我会非常快:

SELECT SUM(amount)  
FROM aggregated_requests  
WHERE user_id = ?  
AND request_date <= ?  
AND request_date >= ?

【讨论】:

  • 谢谢!你是对的 - 它正在记录,用于速率限制。我在使用您的一些解决方案时遇到的问题是,我不是在查询特定月份,而是在运行 30 天。这并不意味着不可能,只是更具挑战性。
  • 因此,如果您按年和月进行分区,您查看的记录会从数亿减少到数十万甚至数百万。如果查看 30 天的移动范围,在最坏的情况下您会查看 2 个分区。
  • 我试试看。在这里问这个问题的原因是我不确定最大的问题是数据总量还是我总结的行数。
  • @stromgen:这里最大的改进机会是利用这样一个事实,即使你不断地写,大部分数据都是静态的。可以这样想:如果表中有 100 天的数据,那么在任何时候只有 1%(“今天”)是动态的。较早的日子总是有相同的金额。因此,只需计算一次,并在需要时调用结果,而不是一遍又一遍地查询整个数据。
  • PARTITIONing 的那些感知好处是虚假的。请记住,选择分区有点像 BTree 的一层降级——一次清洗。
【解决方案2】:

A 计划:INDEX(user_id, request_date, amount)——最适合 WHERE,也是“覆盖”。好的,你有那个;所以,开始 B 计划:

B 计划(甚至更好):建立和维护一个汇总表,例如每日小计。然后改为查询该表。更多:http://mysql.rjweb.org/doc.php/summarytables

分区不太可能比一个好的索引提供更多帮助(如在计划 A 中)。

更多关于 B

如果您需要最新的总计,有多种方法可以使用汇总表来实现,而无需等到第二天。

  • 在插入行数据的同时(可能在触发器中)针对汇总表的 IODKU。这样可以使汇总表保持最新状态,但开销不小。
  • 混合。访问汇总表一整天,然后从原始数据中汇总“今天”并添加。
  • 按小时而不是按天进行汇总。这要么只为您提供每小时解决方案,要么您可以结合“混合”来加快运行速度。

(我的博客给出了这 3 个,再加上 3 个。)

其他

"Amount is 1-10" -- 我希望你使用的是 1 字节的 TINYINT,而不是 4 字节的 INT。那是300MB的差异。也许user_id 可能小于INT

【讨论】:

    猜你喜欢
    • 2013-04-02
    • 2019-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多