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 temporary
和Distinct
,但查询返回850000行并需要3秒。
问题2 :为什么查询现在要慢得多?为什么根据Explain不再使用范围?为什么用新的复合键过滤掉到了33.33%?我希望复合键能够再次过滤100%。
这一切似乎都是非常基本和微不足道的,但它耗费了我几个小时的时间,而且我仍然不明白幕后真的会发生什么。
答案 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}}。因此,有关您的数据的知识将在选择更好(也可能是最佳)策略方面发挥重要作用。
您可以用同样的方式重写第二个查询。不幸的是,这种优化并不总是很容易找到,并且通常特定于特定的查询和情况。如果您有不同的发行版(如上所述)或略微更改您的查询并添加结束日期,该方法将不再起作用,您必须提出另一个想法。