【问题标题】:Query time suddenly increased查询时间突然增加
【发布时间】:2018-02-13 20:45:10
【问题描述】:

我有 MariaDB 10.1.14,很长一段时间我都在毫无问题地进行以下查询(大约需要 3 秒):

SELECT 
    sum(transaction_total) as sum_total, 
    count(*) as count_all, 
    transaction_currency 
FROM 
    transactions 
WHERE 
    DATE(transactions.created_at) = DATE(CURRENT_DATE) 
    AND transaction_type = 1 
    AND transaction_status = 2 
GROUP BY 
    transaction_currency

突然之间,我不确定为什么,这个查询大约需要 13 秒。
这是解释:

这些是事务表的所有索引:

查询时间突然增加的原因是什么?以及如何减少它?

【问题讨论】:

  • transactions.created_at 的日期函数需要时间,如果列包含在函数中,mysql 将不会使用索引
  • 我会说有些事情肯定发生了变化。那么你改变了什么?
  • @RiggsFolly 我想过,但唯一可能改变的是,如果 AWS 升级 Maria 次要版本,但它看起来并没有更新。

标签: mysql datetime indexing mariadb query-performance


【解决方案1】:

+1 来自@JuanCarlosOropeza 的回答,但您可以使用索引更进一步。

ALTER TABLE transactions ADD INDEX (
  transaction_type,
  transaction_status,
  created_at,
  transaction_currency,
  transaction_total
);

正如@RickJames 在 cmets 中提到的,列的顺序很重要。

  • 首先,相等比较中的列
  • 接下来,您可以索引 一个 列,该列用于范围比较(除相等之外的任何内容)或 GROUP BY 或 ORDER BY。您有范围比较和 GROUP BY,但您只能获取索引来帮助其中之一。
  • 最后,查询所需的其他列,如果您认为可以获得覆盖索引。

我在演示文稿How to Design Indexes, Really(视频:https://www.youtube.com/watch?v=ELR7-RdU9XU)中描述了有关索引设计的更多细节。

您可能会被“使用临时”所困扰,因为您有一个范围条件以及一个 GROUP BY 引用不同的列。但是你至少可以通过这个技巧消除“使用文件排序”:

...
GROUP BY 
    transaction_currency
ORDER BY NULL

假设查询结果返回的行的顺序对您来说并不重要。

【讨论】:

  • 非常感谢!索引实际上有帮助(不到一秒!!!),但是..这不是太具体的索引吗?我试图为每一列创建索引,但它给了我一点改进,为什么这个查询需要这么长时间?感谢您提供精彩的幻灯片和视频!
  • 只为一个查询创建索引是否常见/可以?
  • @Michael 通常答案是肯定的,因为您想提高响应时间,但取决于查询。请记住,创建索引占用空间并影响该表的插入和更新。权衡也是如此。是否值得由您决定。硬盘空间非常便宜,所以不是一个真正的问题。
  • 胡安是正确的。创建所有索引以支持特定查询。希望这是您经常运行的查询,因此您可以从创建索引中获得很多价值。否则,如果不是频繁查询,则需要在运行时快速运行。所有优化都是权衡取舍,因此由您决定是否值得您的应用使用。
【解决方案2】:

如果您向表中添加更多数据,查询时间将会增加。

但是您可以做一些事情来提高性能。

  • ( transaction_type, transaction_status, created_at)创建复合索引
  • 从您的字段中删除DATE() 函数(或任何函数),因为这不允许引擎使用索引。 CURRENT_DATE 是一个常数,所以没关系,但不是必需的,因为已经返回 DATE
    • 如果created_at不是日期,你可以使用
    • created_at >= CURRENT_DATE and created_at < CURRENT_DATE + 1
    • 或创建其他字段以仅保存日期部分。

【讨论】:

  • 您对DATE() 的问题的回答比我的更清楚。解释得很好。
  • 但是索引是倒退的。 “范围”部分 (created_at) 需要放在最后。
  • @RickJames 你确定吗?在我看来还可以。你有什么建议?
  • AND created_at = CURDATE() 将与您的索引一起使用。 created_at >= CURDATE() and created_at < CURDATE + INTERVAL 1 DAY 将仅在索引中使用 created_at。因此,我建议将它移到最后。
  • @RickJames 我不得不说不是 MySql 专家,但在 postgresql 中工作正常。索引不适用于范围是否有原因?另请记住,我们可能无法使用created_at = CURDATE(),因为可能还有时间组件
【解决方案3】:

我不知道是什么让您的查询变慢了。更多数据?碎片化?新的数据库版本?

但是,令我惊讶的是,没有真正支持查询的索引。您应该有一个从具有最高基数的列开始的复合索引(日期?好吧,您可以尝试不同的列顺序并查看 DBMS 为查询选择哪个索引)。

create index idx1 on transactions(created_at, transaction_type, transaction_status);

如果 created_at 包含日期部分,那么您可能希望创建一个计算列 created_on 只包含日期和索引。

您甚至可以将此索引扩展到覆盖索引(where 子句字段后跟 group by 子句字段,然后是 select 子句字段):

create index idx2 on transactions(created_at, transaction_type, transaction_status,
                                  transaction_currency, transaction_total);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-16
    • 2018-03-25
    • 1970-01-01
    相关资源
    最近更新 更多