Назад | Перейти на главную страницу

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

У меня вопрос:

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 value, все результирующие строки находятся в порядке price_c. А поскольку запрос ORDER BY price_c, нет необходимости в Using temporary и Using filesort вообще. Что мне не хватает?

2) Для третьей таблицы предполагается использовать индекс work. Но пока исх. 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) Есть идеи, как исправить две вышеуказанные проблемы? 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)

Индексы обычно не используются, когда значения используются в OR состояние. (amp_c.zone = '24' OR amp_c.zone = '25') может быть лучше записано как (amp_c.zone МЕЖДУ '24' И '25') `.

Индексы обычно не используются, когда они встречаются на одной стороне операционной, но не на другой. Использование amp_c.some_countr = '0' OR (...) скорее всего сделает недействительным использование index.m

Оптимизаторы лучше работают с простыми равенствами. Возможно, вам понадобятся внешние соединения, а не внутренние.