Оптимизировать JOIN с LEFT JOIN - PullRequest
3 голосов
/ 26 мая 2011

У меня проблемы с оптимизацией этого запроса:

SELECT a.id
FROM a
JOIN b ON a.id=b.id
LEFT JOIN c ON a.id=c.id
WHERE
   (b.c1='12345' OR c.c1='12345')
   AND (a.c2=0 OR b.c3=1)
   AND a.c4='active'
GROUP BY a.id;

Запрос занимает 7 с, тогда как 0, когда только один из b или c СОЕДИНЯЕТСЯ. ОБЪЯСНЕНИЕ:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
         type: ref
possible_keys: PRIMARY(id),c4,c2
          key: c4
      key_len: 1
          ref: const
         rows: 80775
        Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: ref
possible_keys: id_c1_unique,id
          key: id_c1
      key_len: 4
          ref: database.a.id
         rows: 1
        Extra: Using index
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
         type: ref
possible_keys: id_c1_unique,id,c1,c3
          key: id
      key_len: 4
          ref: database.a.id
         rows: 2
        Extra: Using where

Всегда есть ровно 1 подходящая строка из b и самое большее одна подходящая строка из c. Было бы намного быстрее, если бы MySQL начинал с получения строк b и c, которые совпадают с литералом c1, затем соединял бы a на основе id, но вместо этого он начинался с a.

подробности:

  • MyISAM
  • Все столбцы имеют индексы (_уникальные УНИКАЛЬНЫЕ)
  • Все столбцы НЕ НУЛЬЫ

Что я пробовал:

  • Изменение порядка соединения
  • Перемещение условий WHERE в пункты ON
  • Подвыбирает для b.c1 и c.c1 (WHERE b.id = (ВЫБЕРИТЕ b.id ИЗ b, ГДЕ c1 = '12345'))
  • ИНДЕКС ИСПОЛЬЗОВАНИЯ для b и c

Я понимаю, что мог бы сделать это, используя два SELECT с UNION, но мне нужно избегать этого, если это вообще возможно, из-за того, как генерируется запрос.

Редактировать: Добавить CREATE TABLEs

CREATE TABLE с соответствующими столбцами.

CREATE TABLE `a` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `c2` tinyint(1) NOT NULL,
  `c4` enum('active','pending','closed') NOT NULL,
  PRIMARY KEY (`id`),
  KEY `c2` (`c2`)
  KEY `c4` (`c4`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `b` (
    `b_id` int(11) NOT NULL AUTO_INCREMENT,
    `id` int(11) NOT NULL DEFAULT '0',
    `c1` int(11) NOT NULL,
    `c3` tinyint(1) NOT NULL,
    PRIMARY KEY (`b_id`),
    UNIQUE KEY `id_c1_unique` (`id`,`c1`),
    KEY `c1` (`c1`),
    KEY `c3` (`c3`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `c` (
    `c_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `id` int(11) NOT NULL,
    `c1` int(11) NOT NULL,
    PRIMARY KEY (`c_id`),
    UNIQUE KEY `id_c1_unique` (`id`,`c1`),
    KEY `id` (`id`),
    KEY `c1` (`c1`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Ответы [ 3 ]

0 голосов
/ 26 мая 2011
select STRAIGHT_JOIN 
      distinct a.ID
   from
      a
         join b
            on a.ID = b.ID
         left join c
            on a.id = c.id
            and c.c1 = '12345'
   where
          a.C4 = 'active'
      and ( a.c2 = 0 or b.c3 = 1 )
      and ( b.c1 = '12345' or c.c1='12345' )
0 голосов
/ 29 мая 2011

ОП отвечает здесь.

Я определил, что поведение, которое я наблюдаю, когда MySQL сначала читает менее эффективную таблицу, является неотъемлемой проблемой для всех LEFT JOIN, где менее эффективная таблица находится слева. В соответствии с LEFT JOIN и RIGHT JOIN Optimization из руководства MySQL:

MySQL реализует A LEFT JOIN B join_condition следующим образом:

  • Таблица B устанавливается в зависимости от таблицы A и всех таблиц, от которых A зависит

Итак:

SELECT a.id
FROM a
LEFT JOIN c ON a.id=c.id
GROUP BY a.id;

всегда будет сначала читать a, даже если план запроса показывает, что чтение c более эффективно. Переключение таблиц заставляет MySQL сначала читать с c:

SELECT a.id
FROM c
LEFT JOIN a ON c.id=a.id
GROUP BY a.id;

В моем случае оба запроса возвращают одинаковые результаты. Очевидно, что я упускаю что-то концептуальное, что требует, чтобы левая боковая таблица всегда читалась первой при выполнении LEFT JOIN. Мне кажется, что правая сторона таблицы могла бы быть легко прочитана первой, и MySQL мог бы по-прежнему генерировать те же результаты (для определенных запросов, не обязательно для всех LEFT JOIN). Если бы это было возможно, хотя эта оптимизация, вероятно, была бы добавлена ​​давно, так что, я думаю, я просто упускаю концепцию.

В итоге переключение порядка таблиц не было для меня хорошим решением. В итоге я слил b и c в одну таблицу, что упростило приложение и с самого начала должно было быть сделано. С одной таблицей я могу выполнить JOIN вместо LEFT JOIN, полностью избежав этой проблемы.

Другим возможным решением может быть создание представления, которое включает обе таблицы, тем самым предоставляя единственное представление для JOIN из. Я не проверял это все же.

TL; DR: измените порядок таблиц, чтобы поставить наиболее эффективные на первое место (, если , набор результатов будет одинаковым независимо от порядка). Или объедините b и c в одну таблицу. Или, возможно, создать представление, которое объединяет b и c.

0 голосов
/ 26 мая 2011

Не уверен, но я уверен, что изменение порядка объединений и перемещение там, где условия к предложениям on не имеют значения.

Я не уверен, что здесь достаточно информации, чтобы знать наверняка, ноЯ думаю, что "все столбцы имеют индексы" - это ваша проблема.Для любого конкретного запроса будет использоваться только один индекс для каждой таблицы.Итак, если у вас есть индекс на a.id, и отдельный на a.c2 и третий на a.c4.Ну, это будет только один.

Вероятно, в индексах есть пара столбцов.Таким образом, вы можете объединить только две таблицы, и вы можете использовать «полезный» индекс.

Моя рекомендация - проверить ваши индексы и заставить их покрыть правильные поля, используемые этим запросом (если это возможно).

индекс id и индекс c2 & c4 b для идентификатора & c1 & c3 индекс c для идентификатора & c1

...