使用带复合键的范围

时间:2016-12-08 15:43:32

标签: mysql range query-optimization composite-key

MySQL表包含以下两个表格表(简化):

(~13000)            (~7000000 rows)     
---------------     --------------------
| packages    |     | packages_prices  |
---------------     --------------------
| id (int)    |<- ->| package_id (int) |
| state (int) |     | variant_id (int) |
 - - - - - - -      | for_date (date)  |
                    | price (float)    |
                     - - - - - - - - -  

每个package_id / for_date组合只有少数(平均3个)变体。 state为0(非活动)或1(活动)。 13000人中约有4000人活跃。

首先,我只是想知道哪些包具有价格集(无论变化如何),因此我添加了一个复合键,包括(1)for_date和(2)pid,我查询:< / p>

select distinct package_id from packages_prices where for_date > date(now())

此查询需要1秒才能返回3500行,这太多了。解释告诉我复合键与key_len 3一起使用,并检查2000000行,100%使用类型范围进行过滤。 Using where; Using index; Using temporary。 distinct将其恢复为3500行。

如果我取出distinct,则不再提及Using temporary,但查询会返回1000000行,但仍需要1秒。

问题1 :为什么此查询速度如此之慢?如何在不添加或更改表格中的列的情况下加快速度?我希望,给定复合键,此查询的成本应该低于0,01。

现在我想知道哪些有效套餐有价格设置。

所以我在state上添加了一个键,我就像上面一样添加了一个新的复合键,但顺序相反。我写这样的查询:

select distinct packages.id from packages
inner join packages_prices on id = package_id and for_date > date(now())
where state = 1

查询现在需要2秒钟。解释告诉我,packages表格state上的键与key_len 4一起使用,检查4000行并过滤100%类型类型ref。 Using index; Using temporary。对于packages_prices表,新的复合键与key_len 4一起使用,检查1000行并使用类型ref过滤33.33%。 Using where; Using index; Distinct。 distinct将其恢复为3000行。

如果我取出distinct,则不再提及Using temporaryDistinct,但查询返回850000行并需要3秒。

问题2 :为什么查询现在要慢得多?为什么根据Explain不再使用范围?为什么用新的复合键过滤掉到了33.33%?我希望复合键能够再次过滤100%。

这一切似乎都是非常基本和微不足道的,但它耗费了我几个小时的时间,而且我仍然不明白幕后真的会发生什么。

1 个答案:

答案 0 :(得分:1)

您的观察结果与MySQL的工作方式一致。对于您的第一个查询,使用索引(for_date, package_id),MySQL将在指定的日期开始(使用索引来查找该位置),但是必须转到索引的末尾,因为每个下一个条目都可以显示但未知package_id。具体的package_id可以是例如刚刚用于最新的for_date。该搜索将累计到2000000个已检查的行。从索引中检索相关数据,但仍需要时间。

该怎么办?

通过一些创意重写,您可以将查询转换为以下代码:

select package_id from packages_prices 
group by package_id
having max(for_date) > date(now());

它会为您提供与第一个查询相同的结果:如果至少有一个for_date > date(now())(这将使其成为结果集的一部分),那么max(for_date)也是如此。但是,这只需要检查每行package_id一行(max(for_date)的行),可以跳过for_date > date(now())的所有其他行。

MySQL将通过using index for group-by - 优化(该文本应显示在explain中)来实现。它将需要索引(package_id, for_date)(您已经拥有)并且只需要检查13000行:由于列表是有序的,因此MySQL可以直接跳转到每个package_id的最后一个条目,它将具有max(for_date)的值;然后继续下一个package_id

实际上,MySQL可以使用这种方法来优化distinct(并且如果你删除for_date上的条件,可能会这样做),但并不总能找到方法;一个非常聪明的优化器可能会像我一样重写你的查询,但我们还没有。

根据您的数据分布情况,该方法可能不是一个好主意:如果您有例如7000000 package_id,但将来只检查其中的20个,检查每个package_id的最大for_date将比检查您可以通过{上的索引轻松找到的20行要慢得多{1}}。因此,有关您的数据的知识将在选择更好(也可能是最佳)策略方面发挥重要作用。

您可以用同样的方式重写第二个查询。不幸的是,这种优化并不总是很容易找到,并且通常特定于特定的查询和情况。如果您有不同的发行版(如上所述)或略微更改您的查询并添加结束日期,该方法将不再起作用,您必须提出另一个想法。