2012-03-01 49 views
9

我有一个查询给我的问题,我不明白为什么MySQL的查询优化器的行为是这样的。这里是背景信息:为什么MySQL不能优化这个查询?

我有3个表格。两个比较小,一个比较大。

表1(非常小,727行):

CREATE TABLE ipa
ipa_id INT(11)NOT NULL AUTO_INCREMENT,
ipa_code INT(11)DEFAULT NULL,
ipa_name VARCHAR( 100)DEFAULT NULL,
payorcode VARCHAR(2)DEFAULT NULL,
compid INT(11)DEFAULT '2'
PRIMARY KEY(ipa_id),
KEY ipa_codeipa_code)) ENGINE = MyISAM的

表2(短小,59455行):

CREATE TABLE assign_ipa
assignid INT(11 )NOT NULL AUTO_INCREMENT,
ipa_id int(11)NOT NULL,
userid int(11)NOT NUL L,
username VARCHAR(20)DEFAULT NULL,
compid INT(11)DEFAULT NULL,
PayorCode CHAR(10)DEFAULT NULL
PRIMARY KEY(assignid),
UNIQUE KEY assignidassignidipa_id),
KEY ipa_idipa_id
)ENGINE = MyISAM的

表3(大,24711730行):

CREATE TABLE master_final
IPA INT(11)DEFAULT NULL,
MbrCt SMALLINT(6)DEFAULT '0',
PayorCode VARCHAR(4)DEFAULT 'WC',
KEY idx_IPAIPA
)ENGINE = DEFAULT的MyISAM

现在进行查询。我正在做一个3路连接,使用前两个较小的表来实质上将大表的索引值之一子集。基本上,我得到一个用户的ID列表,SJOnes和查询大文件的这些ID。

mysql> explain
SELECT master_final。PayorCode,总和(master_final.Mbrct)AS MbrCt
FROM master_final
INNER JOIN IPA ON ipa.ipa_code = master_final.IPA
INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id
WHERE assign_ipa.username = 'SJones'
GROUP BY master_final.PayorCode,master_final.ipa \ G;
* ** * ** * ** * ** * 1排* ** * ** * ** * * * *
id:1
select_type:SIMPLE
表:master_final
类型:ALL
possible_keys:idx_IPA
键:NULL
key_len:NULL
REF:NULL
行:
额外:使用临时;使用文件排序
* ** * ** * ** * ** * 2.排* ** * ** * ** * ** *
id:1
select_type:SIMPLE
表:IPA
类型:REF
possible_keys:PRIMARY,ipa_code
键:ipa_code
key_len:5
REF:wc_test.master_final.IPA
行:1
额外:使用其中
* ** * ** * ** * ** * 3。行* ** * ** * ** * ** *
ID:1个
SELECT_TYPE:SIMPLE
表:assign_ipa
类型:REF
possible_keys: ipa_id
key:ipa_id
key_len:4
ref:wc_test.ipa .ipa_id
行:37
附加:(!好像是30分钟),用其中一套(0.00秒)
3行

这个查询需要永远。解释声明告诉我为什么,即使有一个完美的索引,它仍然在大桌面上进行全表扫描。它没有使用它。我不明白这一点。我可以查看查询并查看它只需要从大表中查询几个ID。如果我能做到,为什么MySQL的优化器无法做到这一点?

为了说明,这里是用 'SJones' 相关联的ID:

的MySQL>选择的用户名,从assign_ipa其中username = 'SJones' ipa_id;
+ ---------- + -------- +
|用户名| ipa_id |
+ ---------- + -------- +
| SJones | 688 |
| SJones | 689 |
+ ---------- + -------- +
2排在组(0.02秒)

现在,我可以重写查询替换ipa_id值用于where子句中的用户名。对我来说这相当于原始查询。 MySQL认为它不同。如果我这样做,优化器将利用大表上的索引。

的MySQL>解释
SELECT master_final.PayorCode,总和(master_final.Mbrct)AS MbrCt
FROM master_final
INNER JOIN IPA ON ipa.ipa_code = master_final.IPA
INNER JOIN ON ipa.ipa_id assign_ipa = assign_ipa.ipa_id
* WHERE中( '688', '689')assign_ipa.ipa_id *
GROUP BY master_final.PayorCode,master_final.ipa \ G;
* ** * ** * ** * ** * 1。行* ** * ** * ** * ** *
ID:1个
SELECT_TYPE:SIMPLE
表:IPA
类型:范围
possible_keys: PRIMARY,ipa_code
key:PRIMARY
key_len:4
ref:NULL
行数:2
额外:使用where;使用临时;使用文件排序
* ** * ** * ** * ** * 2.排* ** * ** * ** * ** *
id:1
select_type:SIMPLE
表:assign_ipa
类型:REF
possible_keys:ipa_id
键:ipa_id
key_len:4
REF:wc_test.ipa.ipa_id
行:37
额外:使用其中
* ** * ** * ** * ** * 3.行* ** * ** * ** * ** *
ID:1个
SELECT_TYPE:SIMPLE
表:master_final
类型:REF
possible_keys:idx_IPA
key:idx_IPA
key_len:5
REF:wc_test.ipa.ipa_code
行:
额外:使用哪里
3行中集(0.00秒)

我唯一改变的是一个WHERE子句甚至没有直接击中大牌。然而,优化器使用大表上的索引'idx_IPA',并且不再使用全表扫描。这样重写时的查询速度非常快。

好的,这是很多的背景。现在我的问题。为什么where子句对优化器有影响? Where子句将从较小的表中返回相同的结果集,但根据我使用哪一个结果,我得到的结果截然不同。显然,我想使用包含用户名的where子句,而不是试图将所有关联的ID传递给查询。正如所写,这是不可能的?

  1. 有人可以解释为什么会发生这种情况吗?
  2. 如何重写我的查询以避免全表扫描?

感谢您的支持。我知道这是一个很长的问题。

+0

我读过一位MySQL开发人员(前段时间)的一篇文章,说优化器还在进行中 - 然后他们被Oracle吸收了。您是否尝试过使用“提示”或将“assign_ipa.username ='SJones'”移动到JOIN? – 2012-03-01 19:00:44

回答

4

不太清楚,如果我是对的,但我认为在这里发生以下事情。这:

WHERE assign_ipa.username = 'SJones' 

可能会创建一个临时表,因为它需要全表扫描。临时表没有索引,它们往往会使事情减慢很多。

在另一方面的第二壳体

INNER JOIN ipa ON ipa.ipa_code = master_final.IPA 
INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id 
WHERE assign_ipa.ipa_id in ('688','689') 

允许用于接合索引,这是快速。此外,它可以转换为

SELECT .... FROM master_final WHERE IDA IN (688, 689) ... 

我觉得MySQL也是这么做的。

在assign_ipa.username上创建索引可能会有帮助。

编辑

我重新考虑这个问题,现在有不同的解释。

原因当然是缺少索引。这意味着MySQL不知道查询assign_ipa的结果有多大(MySQL不存储计数),所以它首先从联接开始,它可以在键上进行中继。

这就是解释日志的第2行和第3行告诉我们的。

而在此之后,它会尝试以滤除assign_ipa.username的结果,没有密钥,在排陈述1.

只要有一个指标,它过滤assign_ipa第一,之后加入,使用相应的索引。

+0

我在assign_ipa.username上添加了一个索引,并且确实已经解决了这个问题。辉煌!我从来没有想到这一点。感谢您的帮助! – craigm 2012-03-01 20:48:30

+0

+1但对不起,我很无能:为什么会创建一个临时表?要存储扫描期间发现的assign_ipa中的ID?这是否真的做成了一个临时表,用于少量匹配,这是我需要担心的其他数据库(Oracle,SQL)?谢谢! – Rup 2012-03-02 00:10:46

+0

我不太了解Oracle和MS SQL,但我认为他们在使用表大小来估计查询成本方面要好得多。 – 2012-03-02 00:46:21

2

这可能不是直接回答你的问题,但这里有几件事情,你可以做:

  1. 运行ANALYZE_TABLE ...这将更新其对哪些优化有很大影响的表统计信息将决定这样做。

  2. 如果您仍然认为连接不是您希望它们(您的情况发生,因此优化器没有像您期望的那样使用索引),您可以使用STRAIGHT_JOIN ... from here:“STRAIGHT_JOIN强制优化加入在它们中列出FROM子句中的顺序表,您可以用它来加快查询,如果优化连接在非最佳顺序表。”

  3. 对我来说,把“哪个部分”放在一起有时会有所作为,并加快速度。例如,你可以写:的

...t1 INNER JOIN t2 ON t1.k1 = t2.k2 AND t2.k2=something...

代替

...t1 INNER JOIN t2 ON t1.k1 = t2.k2 .... WHERE t2.k2=something...

所以这绝对不是你为什么有这种行为,但只是一些提示的解释。查询优化器是一个奇怪的野兽,但幸运的是有EXPLAIN命令,它可以帮助你诱骗它以你想要的方式行事。