【问题标题】:Why can't MySQL optimize this query?为什么 MySQL 不能优化这个查询?
【发布时间】:2012-03-20 07:01:59
【问题描述】:

我有一个查询给我带来了问题,我不明白为什么 MySQL 的查询优化器会这样。这是背景信息:

我有 3 张桌子。两个比较小,一个比较大。

表 1(非常小,727 行):

创建表 ipa (
ipa_id int(11) NOT NULL AUTO_INCREMENT,
ipa_code int(11) 默认为 NULL,
ipa_name varchar(100) 默认为 NULL,payorcode varchar(2) 默认为 NULL,
compid int(11) 默认为 '2'
主键 (ipa_id),
ipa_code (ipa_code) ) 引擎=MyISAM

表 2(较小,59455 行):

创建表assign_ipa (
assignid int(11) NOT NULL AUTO_INCREMENT,
ipa_id int(11) NOT NULL,
userid int(11) NOT NULL,username varchar(20) 默认空值,
compid int(11) 默认空值,
PayorCode char(10) 默认空值
主键 (assignid),
唯一键 assignid (assignid,ipa_id),
ipa_id (ipa_id)
) 引擎=MyISAM

表 3(大,24,711,730 行):

创建表 master_final (
IPA int(11) 默认为 NULL,
MbrCt smallint(6) 默认为 '0',
PayorCode varchar(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 传递给查询。照写的,这是不可能的?

  1. 谁能解释为什么会这样?
  2. 如何重写查询以避免全表扫描?

谢谢你一直陪着我。我知道这是一个很长的问题。

【问题讨论】:

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

标签: mysql


【解决方案1】:

不太确定我是否正确,但我认为这里正在发生以下情况。这个:

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,然后使用相应的索引进行连接。

【讨论】:

  • 我在assign_ipa.username 上添加了一个索引,果然解决了这个问题。杰出的!我永远不会想到这一点。感谢您的帮助!
  • +1 但对不起,我一无所知:为什么会创建一个临时表?要存储扫描期间发现的 assign_ipa 的 ID?这是否真的在一个临时表中完成了少量匹配,这是我在其他数据库(Oracle、SQL)中需要担心的事情吗?谢谢!
  • 我不太了解 Oracle 和 MS SQL,但我认为它们在使用表大小来估算查询成本方面要好得多。
【解决方案2】:

这可能不是您问题的直接答案,但您可以做以下几件事:

  1. 运行 ANALYZE_TABLE ...它将更新表统计信息,这对优化器将决定执行的操作有很大影响。

  2. 1234563来自here: "STRAIGHT_JOIN 强制优化器按照它们在 FROM 子句中列出的顺序加入表。如果优化器以非最优顺序加入表,您可以使用它来加快查询速度"
  3. 对我来说,将“where part”正确地加入 join 有时会产生影响并加快速度。例如,你可以写:

...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 命令可以帮助您欺骗它以您想要的方式运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-12
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多