【发布时间】:2012-03-20 07:01:59
【问题描述】:
我有一个查询给我带来了问题,我不明白为什么 MySQL 的查询优化器会这样。这是背景信息:
我有 3 张桌子。两个比较小,一个比较大。
表 1(非常小,727 行):
创建表
ipa(ipa_idint(11) NOT NULL AUTO_INCREMENT,ipa_codeint(11) 默认为 NULL,ipa_namevarchar(100) 默认为 NULL,payorcodevarchar(2) 默认为 NULL,compidint(11) 默认为 '2'
主键 (ipa_id),
键ipa_code(ipa_code) ) 引擎=MyISAM
表 2(较小,59455 行):
创建表
assign_ipa(assignidint(11) NOT NULL AUTO_INCREMENT,ipa_idint(11) NOT NULL,useridint(11) NOT NULL,usernamevarchar(20) 默认空值,compidint(11) 默认空值,PayorCodechar(10) 默认空值
主键 (assignid),
唯一键assignid(assignid,ipa_id),
键ipa_id(ipa_id)
) 引擎=MyISAM
表 3(大,24,711,730 行):
创建表
master_final(IPAint(11) 默认为 NULL,MbrCtsmallint(6) 默认为 '0',PayorCodevarchar(4) 默认为 'WC ',
键idx_IPA(IPA)
) ENGINE=MyISAM 默认
现在进行查询。我正在使用前两个较小的表进行 3 路连接,以在其中一个索引值上对大表进行子集化。基本上,我得到一个用户 SJOnes 的 ID 列表,并在大文件中查询这些 ID。
mysql>解释
SELECT master_final.PayorCode, sum(master_final.Mbrct) AS MbrCt
来自 master_final
内连接 ipa ON ipa.ipa_code = master_final.IPA
内连接 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. 行 ******* ******
编号:1
选择类型:简单
表:master_final
类型:全部
可能的键:idx_IPA
键:NULL
key_len: NULL
参考:空
行数:24711730
额外:使用临时;使用文件排序
************* 2. 行 ********** ***
编号:1
选择类型:简单
表:ipa
类型:参考
可能的键:PRIMARY,ipa_code
键:ipa_code
key_len: 5
参考: wc_test.master_final.IPA
行数:1
额外:使用 where
************ * 3. 行 *************
编号:1
选择类型:简单
表:assign_ipa
类型:参考
可能的键:ipa_id
键:ipa_id
key_len: 4
参考:wc_test.ipa.ipa_id
行数:37
额外:使用 where
3 行一组(0.00 秒)
这个查询需要很长时间(比如 30 分钟!)。解释语句告诉我为什么,即使有一个非常好的索引,它也会对大表进行全表扫描。它没有使用它。我不明白这一点。我可以查看查询,发现它只需要从大表中查询几个 ID。如果我能做到,为什么 MySQL 的优化器做不到呢?
为了说明,以下是与“SJones”相关的 ID:
mysql> select username, ipa_id from assign_ipa where username='SJones';
+---------+--------+
|用户名 | ipa_id |
+---------+--------+
|琼斯 |第688章|
|琼斯 |第689章|
+---------+--------+
2 行(0.02 秒)
现在,我可以重写查询,用 ipa_id 值替换 where 子句中的用户名。对我来说,这相当于原始查询。 MySQL 有不同的看法。如果我这样做,优化器会使用大表上的索引。
mysql>解释
SELECT master_final.PayorCode, sum(master_final.Mbrct) AS MbrCt
来自 master_final
内连接 ipa ON ipa.ipa_code = master_final.IPA
内连接 assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id
*WHERE assign_ipa.ipa_id in ('688','689')*
GROUP BY master_final.PayorCode, master_final.ipa\G;
************* 1. 行 ******* ******
编号:1
选择类型:简单
表:ipa
类型:范围
可能的键:PRIMARY,ipa_code
键:初级
key_len: 4
参考:空
行数:2
额外:使用where;使用临时的;使用文件排序
************* 2. 行 ********** ***
编号:1
选择类型:简单
表:assign_ipa
类型:参考
可能的键:ipa_id
键:ipa_id
key_len: 4
参考:wc_test.ipa.ipa_id
行数:37
额外:使用 where
************ * 3. 行 *************
编号:1
选择类型:简单
表:master_final
类型:参考
可能的键:idx_IPA
键:idx_IPA
key_len: 5
参考: wc_test.ipa.ipa_code
行数:34953
额外:使用 where
3 行一组(0.00 秒)
我唯一改变的是一个 where 子句,它甚至不直接命中大表。然而,优化器在大表上使用索引“idx_IPA”,不再使用全表扫描。像这样重写时的查询非常快。
好的,这是很多背景知识。现在我的问题。为什么 where 子句对优化器很重要?任何一个 where 子句都会从较小的表中返回相同的结果集,但根据我使用的结果,我会得到截然不同的结果。显然,我想使用包含用户名的 where 子句,而不是尝试将所有关联的 ID 传递给查询。照写的,这是不可能的?
- 谁能解释为什么会这样?
- 如何重写查询以避免全表扫描?
谢谢你一直陪着我。我知道这是一个很长的问题。
【问题讨论】:
-
我读到了一位 MySQL 开发人员的文章(前段时间),优化器仍在进行中 - 然后他们被 Oracle 吸收了。您是否尝试过使用“提示”或将“assign_ipa.username = 'SJones'”移动到 JOIN?
标签: mysql