【问题标题】:Weird MySQL query plan: why is this query using temporary & filesort? How to optimize it?奇怪的 MySQL 查询计划:为什么这个查询使用临时和文件排序?如何优化它?
【发布时间】:2011-03-05 19:06:44
【问题描述】:

我有一个问题:

SELECT *
FROM amp_ads,amp_c,amp_c_countries    
WHERE 
(amp_c.zone = '24' OR amp_c.zone = '25') AND 
amp_ads.ad_complete = '1' AND 
amp_ads.ad_type = '17' AND 
amp_ads.accept = '1' AND 
amp_ads.en_w = '1' AND 
amp_c.en_u = '1' AND 
amp_c.en_w = '1' AND 
(amp_c.i_nu>'0' OR amp_c.c_nu>'0' OR amp_c.d_valid_by>'1299341823' OR amp_c.unlimit='1') AND 
(amp_c.i_d_max='0' OR amp_c.i_d_nu>'0') AND 
(amp_c.c_d_max='0' OR  amp_c.c_d_nu>'0') AND 
amp_c.t1<'1299341823' AND 
amp_c.t2>'1299341823' AND 
amp_c.d7 = '1'  AND 
(amp_c.some_countr = '0' OR (amp_c_countries.country = 'ES' AND amp_c.n = amp_c_countries.ad AND amp_c.camp = amp_c_countries.c))  AND 
amp_c.n = amp_ads.n AND 
amp_ads.def = 0       
ORDER BY amp_c.price_c desc LIMIT 1 

(实际上不是SELECT *,但我简化了SELECT 子句以使其不那么混乱。)

上述查询EXPLAIN的输出为:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: amp_c
         type: ref
possible_keys: work,n_index,zone_price
          key: zone_price
      key_len: 4
          ref: const
         rows: 79
        Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: amp_ads
         type: eq_ref
possible_keys: n,work
          key: n
      key_len: 4
          ref: advertis_admpro.amp_c.n
         rows: 1
        Extra: Using where
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: amp_c_countries
         type: index
possible_keys: work
          key: work
      key_len: 12
          ref: NULL
         rows: 4083
        Extra: Using where; Using index; Using join buffer

1) 为什么第一张桌子是Using temporaryUsing filesortEXPLAIN 表明它正在使用索引zone_price,它由两列组成:(zone, price_c)。所以在使用索引根据zone值选择行之后,所有的结果行都按照price_c的顺序。由于查询是 ORDER BY price_c,因此根本不需要 Using temporaryUsing filesort。我错过了什么?

2) 对于第三张表,它应该使用索引work。但是 ref 是NULL。这意味着什么? work(ad,c,country) 列组成。那么当使用WHERE 子句(amp_c_countries.country = 'ES' AND amp_c.n = amp_c_countries.ad AND amp_c.camp = amp_c_countries.c)amp_c_countries 中选择行时,不应该只是简单的索引查找吗? EXPLAIN 中的 rows 值为 4083,amp_c_countries 根据SHOW TABLE STATUS 有 4113 行。这是否意味着 MySQL 正在执行全索引扫描而不是查找?

3) 关于如何解决上述两个问题的任何想法? amp_ads 包含 TEXT 列,因此正在创建大量磁盘临时表:

| Created_tmp_disk_tables               | 906952      |
| Created_tmp_files                     | 11          |
| Created_tmp_tables                    | 912227      |

show processlist 也显示很多进程处于Copying to tmp table 的状态。

谢谢。感谢您的帮助。

编辑

SHOW CREATE TABLE 的输出:

mysql> SHOW CREATE TABLE `advertis_admpro`.`amp_c`\G
*************************** 1. row ***************************
       Table: amp_c
Create Table: CREATE TABLE `amp_c` (
  `n` int(10) unsigned NOT NULL DEFAULT '0',
  `camp` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `zone` int(11) NOT NULL DEFAULT '0',
  `javascript` tinyint(1) NOT NULL DEFAULT '0',
  `banner_target` varchar(50) NOT NULL DEFAULT '',
  `accept` tinyint(1) NOT NULL DEFAULT '0',
  `en_u` tinyint(1) NOT NULL DEFAULT '0',
  `en_w` tinyint(1) NOT NULL DEFAULT '0',
  `i_got` int(10) unsigned NOT NULL DEFAULT '0',
  `c_got` int(10) unsigned NOT NULL DEFAULT '0',
  `r` double(4,2) unsigned NOT NULL DEFAULT '0.00',
  `price_i` double(10,6) unsigned NOT NULL,
  `price_c` double(10,3) unsigned NOT NULL,
  `i_nu` int(11) NOT NULL DEFAULT '0',
  `c_nu` int(11) NOT NULL DEFAULT '0',
  `unlimit` tinyint(1) NOT NULL DEFAULT '0',
  `d_total` int(10) unsigned NOT NULL DEFAULT '0',
  `d_valid_by` int(10) unsigned NOT NULL DEFAULT '0',
  `t1` int(10) unsigned NOT NULL DEFAULT '0',
  `t2` int(10) unsigned NOT NULL DEFAULT '0',
  `d1` tinyint(1) NOT NULL DEFAULT '0',
  `d2` tinyint(1) NOT NULL DEFAULT '0',
  `d3` tinyint(1) NOT NULL DEFAULT '0',
  `d4` tinyint(1) NOT NULL DEFAULT '0',
  `d5` tinyint(1) NOT NULL DEFAULT '0',
  `d6` tinyint(1) NOT NULL DEFAULT '0',
  `d7` tinyint(1) NOT NULL DEFAULT '0',
  `tz1` tinyint(1) NOT NULL DEFAULT '0',
  `tz2` tinyint(1) NOT NULL DEFAULT '0',
  `tz3` tinyint(1) NOT NULL DEFAULT '0',
  `tz4` tinyint(1) NOT NULL DEFAULT '0',
  `tz5` tinyint(1) NOT NULL DEFAULT '0',
  `some_countr` tinyint(1) NOT NULL DEFAULT '0',
  `i_d_max` int(10) unsigned NOT NULL DEFAULT '0',
  `c_d_max` int(10) unsigned NOT NULL DEFAULT '0',
  `i_d_nu` int(10) unsigned NOT NULL DEFAULT '0',
  `c_d_nu` int(10) unsigned NOT NULL DEFAULT '0',
  `last` int(10) unsigned NOT NULL DEFAULT '0',
  `user` int(10) unsigned NOT NULL DEFAULT '0',
  `username` varchar(15) NOT NULL DEFAULT '',
  `emailed` int(10) unsigned NOT NULL DEFAULT '0',
  KEY `work` (`en_u`,`en_w`,`i_nu`,`c_nu`,`d_valid_by`,`unlimit`,`i_d_max`,`c_d_max`,`i_d_nu`,`c_d_nu`,`t1`,`t2`,`n`),
  KEY `n_index` (`n`,`camp`),
  KEY `zone_price` (`zone`,`price_c`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> SHOW CREATE TABLE `advertis_admpro`.`amp_ads`\G
*************************** 1. row ***************************
       Table: amp_ads
Create Table: CREATE TABLE `amp_ads` (
  `n` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL DEFAULT '',
  `ad_type` int(10) unsigned NOT NULL DEFAULT '0',
  `accept` tinyint(1) NOT NULL DEFAULT '0',
  `en_w` tinyint(1) NOT NULL DEFAULT '0',
  `weight` tinyint(1) NOT NULL DEFAULT '0',
  `w` smallint(5) unsigned NOT NULL DEFAULT '0',
  `h` smallint(5) unsigned NOT NULL DEFAULT '0',
  `norepeat` int(10) unsigned NOT NULL DEFAULT '0',
  `campaigns` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `zones` text NOT NULL,
  `keywords` text NOT NULL,
  `banner` varchar(255) NOT NULL DEFAULT '',
  `url` varchar(255) NOT NULL DEFAULT '',
  `alt` varchar(255) NOT NULL DEFAULT '',
  `raw` text NOT NULL,
  `kind` varchar(40) NOT NULL DEFAULT '',
  `javascript` tinyint(1) NOT NULL DEFAULT '0',
  `ad_complete` tinyint(1) NOT NULL DEFAULT '0',
  `url1` text NOT NULL,
  `url2` text NOT NULL,
  `url3` text NOT NULL,
  `text1` text NOT NULL,
  `text2` text NOT NULL,
  `text3` text NOT NULL,
  `text4` text NOT NULL,
  `text5` text NOT NULL,
  `text6` text NOT NULL,
  `text7` text NOT NULL,
  `text8` text NOT NULL,
  `text9` text NOT NULL,
  `text10` text NOT NULL,
  `picture1` varchar(255) NOT NULL DEFAULT '',
  `picture2` varchar(255) NOT NULL DEFAULT '',
  `picture3` varchar(255) NOT NULL DEFAULT '',
  `picture4` varchar(255) NOT NULL DEFAULT '',
  `picture5` varchar(255) NOT NULL DEFAULT '',
  `created` int(10) unsigned NOT NULL DEFAULT '0',
  `user` int(11) NOT NULL DEFAULT '0',
  `username` varchar(15) NOT NULL DEFAULT '',
  `preview` text NOT NULL,
  `def` tinyint(1) NOT NULL DEFAULT '0',
  UNIQUE KEY `n` (`n`),
  KEY `work` (`ad_type`,`accept`,`en_w`,`norepeat`,`ad_complete`,`def`)
) ENGINE=InnoDB AUTO_INCREMENT=1532 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> SHOW CREATE TABLE `advertis_admpro`.`amp_c_countries`\G
*************************** 1. row ***************************
       Table: amp_c_countries
Create Table: CREATE TABLE `amp_c_countries` (
  `ad` int(10) unsigned NOT NULL DEFAULT '0',
  `c` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `country` varchar(5) NOT NULL DEFAULT '',
  KEY `work` (`ad`,`c`,`country`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

【问题讨论】:

  • 对结果排序需要文件排序。不过,这并不意味着将使用实际的磁盘文件。
  • 排序结果并不总是需要文件排序,尤其是当行已经在索引中排序时,就像这里的情况一样。正如我发布的 Created_tmp_disk_tables 值所示,此处还使用了磁盘表。
  • 文件排序操作是预期的。它需要满足 ORDER BY。请注意,优化器 (zone_price) 选择的索引没有 price_c 作为前导列,查询可能会返回具有不同 `zone` 值的行。因此,返回的行不会按 `price_c` 排序(升序或降序)。
  • @spencer7593 - 您对返回具有不同“区域”值的行的查询是正确的。我忘记了。谢谢。但是当我将查询更改为“区域”的单个值时(请参阅我的 cmets 到 @Galz 的答案),我仍然得到“使用临时,使用文件排序”。此外,即使需要文件排序,什么时候需要使用临时表?我不明白。

标签: mysql sql query-optimization


【解决方案1】:

为了避免排序的需要,在根据索引的第二部分排序时,第一部分必须保持不变。

在你的情况下,第一部分的条件是amp_c.zone = '24' OR amp_c.zone = '25',这可能不够好。

尝试仅将条件更改为amp_c.zone = '24',看看这是否会改变解释(显然你不会得到你需要的所有结果,但这样做是为了验证我的猜测)...

如果它有效并且说明没有再次显示 using filesort,那么您有 2 个选项:

  1. 按索引的所有部分排序:ORDER BY amp_c.zone, amp_c.price_c
  2. 在 zone 列上只有一个条件,并与另一个类似的查询合并为第二个条件,例如:

(SELECT  ...  WHERE zone = 24 ... ORDER BY price_c)
UNION
(SELECT  ...  WHERE zone = 25 ... ORDER BY price_c)
ORDER BY price_c

【讨论】:

  • 我只尝试了amp_c.zone = '24'。不幸的是EXPLAIN 仍然显示Using temporary; Using filesort。有什么建议么?谢谢。
  • @Continuation - 刚刚找到关于我们主题的文档:dev.mysql.com/doc/refman/5.0/en/order-by-optimization.html。还有一个可能相关的问题,尽管我不确定如何更改查询以修复它:“您正在连接许多表,并且 ORDER BY 中的列并非全部来自使用的第一个非常量表检索行。(这是 EXPLAIN 输出中第一个没有 const 连接类型的表。)"
  • @Continuation - 还尝试将区域添加到 ORDER BY 子句(在价格之前)。再说一遍,这不是您要找的东西,但可能会让我们更接近...
  • 我在 price_c 之前将区域添加到 ORDER BY 但仍然是相同的 EXPLAIN 结果,使用临时和文件排序。关于文档中的引用,第一个非常量表是 amp_c,如 EXPLAIN 所示。我在 ORDER BY 中的专栏 -- price_c -- 来自 amp_c。所以它应该根据文档工作。这就是为什么我很困惑。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-27
  • 2014-02-13
  • 1970-01-01
  • 2012-04-17
  • 2014-01-04
  • 2010-11-08
相关资源
最近更新 更多