Странный план запросов MySQL: почему этот запрос использует временную и файловую сортировку?Как это оптимизировать? - PullRequest
5 голосов
/ 05 марта 2011

У меня есть запрос:

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) Почему 1-я таблица Using temporary и Using filesort?EXPLAIN показывает, что он использует индекс zone_price, который состоит из 2 столбцов: (zone, price_c).Таким образом, после того как индекс используется для выбора строк на основе значения zone, все результирующие строки имеют порядок Price_c.И так как запрос ORDER BY price_c, не должно быть необходимости в Using temporary и Using filesort.Чего мне не хватает?

2) Для 3-й таблицы предполагается использование индекса work.Но все же ref это NULL.Что это значит?work состоит из столбцов (ad,c,country).Так что, когда строки выбираются из amp_c_countries с использованием предложения WHERE (amp_c_countries.country = 'ES' AND amp_c.n = amp_c_countries.ad AND amp_c.camp = amp_c_countries.c), не должен ли это быть простой просмотр индекса?Значение rows в EXPLAIN равно 4083, amp_c_countries имеет 4113 строк в соответствии с SHOW TABLE STATUS.Означает ли это, что MySQL выполняет полное сканирование индекса вместо поиска?

3) Есть какие-нибудь идеи о том, как решить вышеуказанные 2 проблемы?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)

1 Ответ

2 голосов
/ 06 марта 2011

Чтобы исключить необходимость сортировки, при сортировке по второй части индекса, первая часть должна оставаться постоянной.

В вашем случае условие для первой частиamp_c.zone = '24' OR amp_c.zone = '25', что может быть недостаточно.

Попробуйте изменить условие только на amp_c.zone = '24', посмотрите, не изменит ли это объяснение (Очевидно, вы не получите все необходимые результаты, но сделайте это, чтобыподтвердите мое предположение) ...

Если это работает, а объяснение не показывает , снова используя файловую сортировку , у вас есть 2 варианта:

  1. Сортировать по все части индекса: ORDER BY amp_c.zone, amp_c.price_c.
  2. Имеют только одно условие для столбца зоны и объединяются с другим аналогичным запросом для второго условия, что-то вроде:

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