为什么MySQL无法优化此查询?

时间:2012-03-01 18:44:56

标签: mysql

我的查询给了我一些问题,我无法理解MySQL查询优化器的行为方式。这是背景信息:

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

表1(非常小,727行):

  

创建表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行):

  

创建表assign_ipa
  assignid int(11)NOT NULL AUTO_INCREMENT,
    ipa_id int(11)NOT NULL,
    userid int(11)NOT NULL,
    username varchar(20)DEFAULT NULL,
    compid int(11)DEFAULT NULL,
    PayorCode char(10)DEFAULT NULL
    PRIMARY KEY(assignid),
    独特的钥匙assignidassignidipa_id),
    KEY ipa_idipa_id
  )ENGINE = MyISAM

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

  

创建表master_final
    IPA int(11)DEFAULT NULL,
    MbrCt smallint(6)DEFAULT' 0',
    PayorCode varchar(4)DEFAULT' WC',
    KEY idx_IPAIPA
  )ENGINE = MyISAM DEFAULT

现在进行查询。我正在使用前两个较小的表进行三向连接,以便在其中一个索引值上对大表进行子集化。基本上,我得到一个用户的ID列表,SJOnes并查询大文件中的那些ID。

  

的MySQL>解释
          SELECT master_final.PayorCode,sum(master_final.Mbrct)AS MbrCt
          来自master_final
          INNER JOIN ipa ON ipa.ipa_code = master_final.IPA
          INNER JOIN assign_ipa ON ipa.ipa_id = assign_ipa.ipa_id
          在哪里assign_ipa.username =' SJones'
          GROUP BY master_final.PayorCode,master_final.ipa \ G;
  的 * ** * ** * ** * ** * 1.行 * ** * ** * ** * * * *
             id:1
    select_type:SIMPLE
          table:master_final
           类型:所有
  possible_keys:idx_IPA
            key:NULL
        key_len:NULL
            ref:NULL
           行: 24711730
          额外:使用临时;使用filesort
  的 * ** * ** * ** * ** * 2.行 * ** * ** * ** * * * *
             id:1
    select_type:SIMPLE
          表:ipa
           类型:参考
  possible_keys:PRIMARY,ipa_code
            key:ipa_code
        key_len:5
            ref:wc_test.master_final.IPA
           行:1
          额外:使用在哪里   的 * ** * ** * ** * ** * 3.行 * ** * ** * ** * * * *
             id:1
    select_type:SIMPLE
          表:assign_ipa
           类型:参考
  possible_keys:ipa_id
            key:ipa_id
        key_len:4
            ref:wc_test.ipa.ipa_id
           行:37
          额外:使用在哪里   3行(0.00秒)

此查询需要永远(如30分钟!)。解释说明告诉我为什么,即使有一个非常好的索引,它也会在大表上进行全表扫描。它没有使用它。我不明白这一点。我可以查看查询,看到它只需要查询大表中的几个ID。如果我能做到,为什么MySQL的优化器能做到呢?

为了说明,以下是与' SJones':

相关联的ID
  

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

现在,我可以重写查询,用if子句中的用户名替换ipa_id值。对我来说,这相当于原始查询。 MySQL看待它的方式不同。如果我这样做,优化器会使用大表上的索引。

  

的MySQL>解释
         SELECT master_final.PayorCode,sum(master_final.Mbrct)AS MbrCt
         来自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.ipa_id in(' 688',' 689')*
         GROUP BY master_final.PayorCode,master_final.ipa \ G;
  的 * ** * ** * ** * ** * 1.行 * ** * ** * ** * * * *
             id:1
    select_type:SIMPLE
          表:ipa
           类型:范围
  possible_keys:PRIMARY,ipa_code
            关键:主要
        key_len:4
            ref:NULL
           行:2
          额外:使用在哪里;使用临时;使用filesort
  的 * ** * ** * ** * ** * 2.行 * ** * ** * ** * * * *
             id:1
    select_type:SIMPLE
          表:assign_ipa
           类型:参考
  possible_keys:ipa_id
            key:ipa_id
        key_len:4
            ref:wc_test.ipa.ipa_id
           行:37
          额外:使用在哪里   的 * ** * ** * ** * ** * 3.行 * ** * ** * ** * * * *
             id:1
    select_type:SIMPLE
          table:master_final
           类型:参考
  possible_keys:idx_IPA
            key:idx_IPA
        key_len:5
            ref:wc_test.ipa.ipa_code
           行: 34953
          额外:使用在哪里   3行(0.00秒)

我唯一改变的是where子句甚至没有直接击中大桌子。然而,优化器使用索引' idx_IPA'在大桌子上,不再使用全表扫描。像这样重写的查询非常快。

好的,那是很多背景知识。现在我的问题。为什么where子句对优化器很重要? where子句将从较小的表返回相同的结果集,但是我得到的结果却大不相同,这取决于我使用的是哪一个。显然,我想使用包含用户名的where子句,而不是尝试将所有关联的ID传递给查询。如上所述,这是不可能的?

  1. 有人可以解释为什么会这样吗?
  2. 如何重写查询以避免全表扫描?
  3. 感谢您坚持下去。我知道这是一个非常长的问题。

2 个答案:

答案 0 :(得分: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,然后使用相应的索引加入。

答案 1 :(得分:2)

这可能不是您问题的直接答案,但您可以做的事情很少:

  1. 运行ANALYZE_TABLE ...它将更新表统计信息,这对优化程序决定要做的事情有很大影响。

  2. 如果您仍然认为联接不符合您的希望(在您的情况下会发生这种情况,因此优化器没有像您期望的那样使用索引),您可以使用STRAIGHT_JOIN ...来自here:“STRAIGHT_JOIN强制优化器按照FROM子句中列出的顺序连接表。如果优化器以非最佳顺序连接表,则可以使用它来加速查询”

  3. 对我而言,将“where part”加入连接有时会产生影响并加快速度。例如,您可以写:

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