【问题标题】:Optimize SQL to fetch 1 day data优化 SQL 获取 1 天数据
【发布时间】:2018-08-23 12:31:50
【问题描述】:

我需要经常获取最近 24 小时的数据,并且此查询经常运行。 由于这会扫描很多行,因此经常使用它会影响数据库性能。

MySql 执行策略在 created_at 上选择索引并返回大约 1,00,000 行。并且这些行被一一扫描以过滤customer_id = 10,我的最终结果有20000行。

如何优化这个查询?

explain SELECT  *
FROM    `order`
WHERE    customer_id = 10
and `created_at` >= NOW() - INTERVAL 1 DAY;

id : 1
select_type : SIMPLE
table : order
partitions : NULL
type : range
possible_keys : idx_customer_id, idx_order_created_at
key : idx_order_created_at
key_len : 5
ref : NULL
rows : 103357
filtered : 1.22
Extra : Using index condition; Using where

【问题讨论】:

  • 您正在选择*。你真的需要所有的列吗?
  • 不,原始查询中没有 *

标签: mysql sql optimization query-optimization


【解决方案1】:

我要做的第一个优化是对表的访问:

create index ix1 on `order` (customer_id, created_at);

然后,如果查询仍然很慢,我会尝试将您选择的列附加到索引中。例如,如果您选择 order_idamountstatus 列:

create index ix1 on `order` (customer_id, created_at, 
  order_id, amount, status);

第二种策略可能是有益的,但您需要对其进行测试,以了解它在您的特定情况下带来了哪些性能改进。

第二个策略的重大改进是它只遍历二级索引,避免走回表的主聚集索引(这可能很耗时)。

【讨论】:

  • 为什么追加仅出现在 select 子句中的列会有影响?
  • 是的,就是这样。相应地进行了编辑。
  • 是的,想过做同样的事情,但是表上还有其他日期列,例如 updated_at、completion_time 等等……而其他查询使用带有 customer_id 的列。如果我们决定使用复合索引,我们需要创建像(customer_id,created_at),(customer_id,updated_at),(customer_id,completion_time)这样的索引,这会影响查询的插入时间
  • 是的,它会影响对表的插入、删除和更新。您需要注意向表中添加了多少索引。我的 [非常] 个人经验法则是将索引数限制为最多 10 个。
  • 我不认为您的第二个策略是一种改进。您只是试图实现覆盖索引,但最后您可能会多次复制整个聚集索引。考虑到这是一项频繁的任务,它不是解决方案。
【解决方案2】:

不是在 ID 和 Created 上创建两个单一索引,而是在 (customer_id, created_at) 上创建一个复合索引。这样,索引引擎可以使用 where 子句的两个部分,而不仅仅是希望得到一个。直接跳转到客户 ID,然后直接跳转到所需日期,然后给出结果。它应该非常快。

额外的跟进。 我听到您关于拥有多个索引的评论,但将它们添加到主要索引中,就在

之后

(customer_id, created_at, updated_at, completion_time)

然后,在您的查询中,总是可以在 where 子句中包含一些关于索引的帮助。例如,我不知道您的具体数据。在某个给定点创建记录。更新和完成时间总是在那之后。从创建到完成需要多长时间(最坏情况)... 2 天、10 天、90 天?

where
       customerID = ?
   AND created_at >= date - 10 days
   AND updated_at >= date -1

同样,这只是一个例子,但如果一个人有 1000 个订单并且周转时间相对较快,您可以跳转到最近的订单,然后找到在该时间段内更新的订单。同样,这只是一个选项单个索引与 3、4 或更多索引。

【讨论】:

  • 只有当一列/两列都具有高基数时才会有所帮助。
  • 是的,想过做同样的事情,但是表上还有其他日期列,例如 updated_at、completion_time 等等……而其他查询使用带有 customer_id 的列。如果我们决定使用复合索引,我们需要创建像(customer_id,created_at),(customer_id,updated_at),(customer_id,completion_time)这样的索引,这会影响查询的插入时间
  • date - 1 不适用于本月的第一天。
【解决方案3】:

似乎您正在处理一个增长非常快的表,我应该考虑将此频繁查询移至冷表或副本。

还有一点是您是否考虑过按 customer_id 进行分区。查询customer_id = 10背后的业务逻辑我不是很明白。如果是多租户应用,试试partition。

【讨论】:

  • 是的,所以我的疑问是,如果我在 customer_id 上创建了分区,mysql 将如何创建执行策略?将仅扫描与日期匹配的行还是将扫描所有行并根据日期进行过滤?考虑到我已经在 created_at 列上有索引
  • 分区适用于表的所有数据和索引。所以 MySQL 会扫描这个分区的 created_at 索引。
  • 为了澄清,它不会扫描所有行,因为将使用 created_at 索引。
  • 如果您使用PARTITION BY RANGE(customer_id),请(作为一般规则)将该列放在任何索引中的第一位。
【解决方案4】:

对于这个查询:

SELECT o.*
FROM `order` o
WHERE o.customer_id = 10 AND
      created_at >= NOW() - INTERVAL 1 DAY;

我的第一个倾向是在(customer_id, created_at) 上创建一个综合索引——正如其他人所建议的那样。

但是,您似乎每天有很多数据和许多插入。这表明分区加上索引。适当的分区将在created_at 上,可能是每天一次,以及user_id 的索引。

典型的查询会访问最近的两个分区。由于您的查询侧重于最近的数据,因此这也减少了索引占用的内存,这可能是一个整体优势。

【讨论】:

  • 如果我们考虑使用分区,那么更好的策略是什么?我应该根据customer_id 还是根据created_at 创建分区?基于日期创建分区将解决最近使用并落入最后 1 或 2 个分区的数据的问题,但是如果有一天我需要对使用 20-30 个分区的数据分析进行查询,mysql 将如何表现?这样的查询不会经常使用,我们可能会在一段时间内受到性能影响,我唯一关心的是,性能影响应该尽可能低
  • 如果我的分区数量更多,数据库将如何表现?
  • @prranay 。 . .通常,您会在created_at 上按时间创建分区。据推测,您正在查看查询中的不同用户,而不是不同的时间范围。如果是这样,那么只有一两个分区被加载到内存中。
【解决方案5】:

这种技术应该比所有其他答案都好,尽管可能只是一小部分:

而不是 orders 被索引:

PRIMARY KEY(order_id)   -- AUTO_INCREMENT
INDEX(customer_id, ...)  -- created_at, and possibly others

这样做可以将行“聚集”在一起:

PRIMARY KEY(customer_id, order_id)
INDEX (order_id)   -- to keep AUTO_INCREMENT happy

然后您可以根据需要选择更多以customer_id 开头的索引。或者不。

另一个问题——你将如何处理 20K 行?这对客户来说是很多东西,尤其是人类。如果你然后咀嚼它,你不能做一个更复杂的查询,做更多的工作,并返回更少的行吗? 可能会更快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-05
    • 1970-01-01
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多