Помоги мне превратить SUBQUERY в JOIN - PullRequest
2 голосов
/ 23 июня 2010

Две таблицы.

идентификатор электронной почты (int10) |владение (int10)

сообщений по электронной почте (int10) проиндексировано |message (mediumtext)

Подзапрос (который ужасен в mysql).

SELECT COUNT (*) ИЗ сообщений, ГДЕ сообщение КАК '% word%' И emailid IN (ВЫБЕРИТЕ ИД ИЗ ПИСАНИЙ, ГДЕ НАХОДИТСЯ= 32)


Использование здесь заключается в том, что я запускаю поиск по электронной почте (который, очевидно, упрощен в приведенном выше примере), который генерирует список, скажем, 3000 идентификаторов электронной почты.Затем я хочу выполнить поиск по сообщениям, потому что мне нужно выполнить сопоставление текста - только из этих 3000 электронных писем с сообщением.

Запрос к сообщениям дорогой (сообщение не проиндексировано), но это хорошо, потому чтоон будет проверять только несколько строк.

Идеи:

i) Объединение.Мои попытки сделать это до сих пор не сработали и привели к полному сканированию таблицы сообщений (т. Е. Индекс emailid не используется) ii) временной таблицы.Это может сработать, я думаю.iii) кэшировать идентификаторы в клиенте и выполнить 2 запроса.Это работает.Не элегантноiv) подзапрос.Подзапросы mySQL запускают 2-й запрос каждый раз, так что это не работает.может быть исправлено в mysql 6.

Хорошо, вот что у меня так далеко.Это действительные имена полей (я немного упростил вопрос).

Запрос:

SELECT COUNT(*) FROM ticket LEFT JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) 
AND ticket_subject.subject LIKE "%about%"

Результаты:

1   SIMPLE  ticket  ref     PRIMARY,category    category    4   const   28874    
1   SIMPLE  ticket_subject  eq_ref  PRIMARY     PRIMARY     4   deskpro.ticket.id   1   Using where

Это занимает 0,41 секунды.и возвращает число (*) 113.

Запуск:

SELECT COUNT (*) FROM ticket WHERE category IN (1)

Занимает 0,01 секунды и находит 33 000 результатов.

Запуск

SELECT COUNT (*) FROM ticket_subject WHERE subject LIKE "%about%"

Занимает 0,14 секунды и находит 1300 результатов.

И таблица заявок, и таблица ticket_subject имеют 300 000 строк.

Существует индекс для ticket_subject.ticketid и ticket.category.

Теперь я понимаю, что использование синтаксиса LIKE было ошибкой - так как это было немного красной сельди о FULLTEXT.Это не проблема.Проблема:

1) Таблица A - очень быстрый запрос, выполняется по индексу.0,001 секунды 2) Таблица B - запрос от умеренного до медленного, без индекса - полное сканирование таблицы0,1 секунды.

Оба эти результата в порядке.Проблема в том, что я должен присоединиться к ним, и поиск занимает 0,3 секунды;что для меня не имеет смысла, потому что медленные аспекты комбинированного запроса в таблице B должны быть быстрее, потому что мы теперь ищем только часть этой таблицы - т.е. он не должен делать полное сканирование таблицы, потому что поле, к которому присоединяется JOINEDвключен в индекс.

Ответы [ 4 ]

8 голосов
/ 23 июня 2010

Не забудьте воспользоваться булевой оценкой короткого замыкания :

SELECT COUNT(*) 
FROM messages 
join emails ON emails.id = messages.emailid
WHERE ownership = 32 AND message LIKE '%word%'

Этот фильтр фильтрует по ownership перед тем, как оценивать предикат LIKE.Всегда ставьте свои более дешевые выражения слева.

Кроме того, я согласен с @Martin Smith и @MJB, что вам следует рассмотреть возможность использования индексации FULLTEXT MySQL для ускорения этого процесса.


Ваш комментарий и дополнительную информацию, вот некоторый анализ:

explain SELECT COUNT(*) FROM ticket WHERE category IN (1)\G

           id: 1
  select_type: SIMPLE
        table: ticket
         type: ref
possible_keys: category
          key: category
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index

Замечание «Использование индекса» - это хорошая вещь, потому что она может удовлетворить запрос, просто прочитавиндексировать структуру данных, даже не касаясь данных таблицы.Это наверняка очень быстро.

explain SELECT COUNT(*) FROM ticket_subject WHERE subject LIKE '%about%'\G

           id: 1
  select_type: SIMPLE
        table: ticket_subject
         type: ALL
possible_keys: NULL        <---- no possible keys
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra: Using where

Это показывает, что нет никаких возможных ключей, которые могли бы использовать предикат подстановочного знака LIKE.Он использует условие в предложении WHERE, но должен оценить его, запустив сканирование таблицы.

explain SELECT COUNT(*) FROM ticket LEFT JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) 
AND ticket_subject.subject LIKE '%about%'\G

           id: 1
  select_type: SIMPLE
        table: ticket
         type: ref
possible_keys: PRIMARY,category
          key: category
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index

           id: 1
  select_type: SIMPLE
        table: ticket_subject
         type: ref
possible_keys: ticketid
          key: ticketid
      key_len: 4
          ref: test.ticket.id
         rows: 1
        Extra: Using where

Аналогично, доступ к таблице заявок является быстрым, но он испорчен сканированием таблицы, вызваннымусловие LIKE.

ALTER TABLE ticket_subject ENGINE=MyISAM;

CREATE FULLTEXT INDEX ticket_subject_fulltext ON ticket_subject(subject);

explain SELECT COUNT(*) FROM ticket JOIN ticket_subject  
ON (ticket_subject.ticketid = ticket.id)  
WHERE category IN (1)  AND MATCH(ticket_subject.subject) AGAINST('about')

           id: 1
  select_type: SIMPLE
        table: ticket
         type: ref
possible_keys: PRIMARY,category
          key: category
      key_len: 4
          ref: const
         rows: 1
        Extra: Using index

           id: 1
  select_type: SIMPLE
        table: ticket_subject
         type: fulltext
possible_keys: ticketid,ticket_subject_fulltext
          key: ticket_subject_fulltext          <---- now it uses an index
      key_len: 0
          ref: 
         rows: 1
        Extra: Using where

Вы никогда не заставите LIKE работать хорошо.Смотрите мою презентацию Практический полнотекстовый поиск в MySQL .


Ваш комментарий: Хорошо, я провел несколько экспериментов с набором данных аналогичного размера (таблицы Users и Badges).в дампе переполнения стека :-).Вот что я нашел:

select count(*) from users
where reputation > 50000

+----------+
| count(*) |
+----------+
|       37 |
+----------+
1 row in set (0.00 sec)

Это действительно быстро, потому что у меня есть индекс для столбца репутации.

           id: 1
  select_type: SIMPLE
        table: users
         type: range
possible_keys: users_reputation_userid_displayname
          key: users_reputation_userid_displayname
      key_len: 4
          ref: NULL
         rows: 37
        Extra: Using where; Using index

select count(*) from badges
where badges.creationdate like '%06-24%'

+----------+
| count(*) |
+----------+
|     1319 |
+----------+
1 row in set, 1 warning (0.63 sec)

Это, как и ожидалось, поскольку таблица имеет 700 тыс. Строк, и этодолжен сделать сканирование таблицы.Теперь давайте сделаем объединение:

select count(*) from users join badges using (userid)
where users.reputation > 50000 and badges.creationdate like '%06-24%'

+----------+
| count(*) |
+----------+
|       19 |
+----------+
1 row in set, 1 warning (0.03 sec)

Это не так уж плохо.Вот отчет об объяснении:

           id: 1
  select_type: SIMPLE
        table: users
         type: range
possible_keys: PRIMARY,users_reputation_userid_displayname
          key: users_reputation_userid_displayname
      key_len: 4
          ref: NULL
         rows: 37
        Extra: Using where; Using index

           id: 1
  select_type: SIMPLE
        table: badges
         type: ref
possible_keys: badges_userid
          key: badges_userid
      key_len: 8
          ref: testpattern.users.UserId
         rows: 1
        Extra: Using where

Похоже, что для объединения используются интеллектуальные индексы, и мне помогает составной индекс, включающий идентификатор пользователя и репутацию.Помните, что MySQL может использовать только один индекс на таблицу, поэтому важно определить правильные составные индексы для запроса, который вам нужно сделать.


Ваш комментарий: хорошо, я пробовал это гдерепутация> 5000, а где репутация> 500 и репутация> 50. Они должны соответствовать гораздо большему числу пользователей.

select count(*) from users join badges using (userid)
where users.reputation > 5000 and badges.creationdate like '%06-24%'

+----------+
| count(*) |
+----------+
|      194 |
+----------+
1 row in set, 1 warning (0.27 sec)

select count(*) from users join badges using (userid)
where users.reputation > 500 and badges.creationdate like '%06-24%'

+----------+
| count(*) |
+----------+
|      624 |
+----------+
1 row in set, 1 warning (0.93 sec)

select count(*) from users join badges using (userid)
where users.reputation > 50 and badges.creationdate like '%06-24%'
--------------

+----------+
| count(*) |
+----------+
|     1067 |
+----------+
1 row in set, 1 warning (1.72 sec)

Отчет объяснения одинаков во всех случаях, но если запрос находитчем больше совпадающих строк в таблице «Пользователи», тем не менее, естественно, нужно оценить предикат LIKE по сравнению с гораздо большим количеством совпадающих строк в таблице «Значки».

Это правда, что выполнение соединения требует определенных затрат.Немного удивительно, что это так дорого.Но это может быть смягчено, если вы используете индексы.

Я знаю, что вы сказали, что у вас есть запрос, который не может использовать индекс, но, возможно, пришло время подумать о создании избыточного столбца с некоторой преобразованной версией данных вашего исходного столбца, чтобы вы могли индексировать его.В приведенном выше примере я мог бы создать столбец creationdate_day и заполнить его из DAYOFYEAR(creationdate).


Вот что я имею в виду:

ALTER TABLE Badges ADD COLUMN creationdate_day SMALLINT;
UPDATE Badges SET creationdate_day = DAYOFYEAR(creationdate);
CREATE INDEX badge_creationdate_day ON Badges(creationdate_day);

select count(*) from users join badges using (userid)
where users.reputation > 50 and badges.creationdate_day = dayofyear('2010-06-24')

+----------+
| count(*) |
+----------+
|     1067 |
+----------+
1 row in set, 1 warning (0.01 sec)  <---- not too shabby!

Вот отчет объяснения:

          id: 1
  select_type: SIMPLE
        table: badges
         type: ref
possible_keys: badges_userid,badge_creationdate_day
          key: badge_creationdate_day    <---- here is our new index
      key_len: 3
          ref: const
         rows: 1318
        Extra: Using where

           id: 1
  select_type: SIMPLE
        table: users
         type: eq_ref
possible_keys: PRIMARY,users_reputation_userid_displayname
          key: PRIMARY
      key_len: 8
          ref: testpattern.badges.UserId
         rows: 1
        Extra: Using where
3 голосов
/ 23 июня 2010
SELECT COUNT(*) 
FROM messages 
join emails ON emails.id = messages.emailid
WHERE message LIKE '%word%' 
AND ownership = 32

Проблема, однако, в '%word%'. Это всегда требует проверки сообщения.Возможно, вы захотите посмотреть полнотекстовый поиск , если вы используете MyISAM.

2 голосов
/ 23 июня 2010

Я думаю, это то, что вы ищете:

select count(*)
from messages m
  inner join emails e
    on e.id = m.emailid
where m.message like '%word%'
  and e.ownership = 32

Трудно сказать точно, как это будет работать. Если FTS из-за начального подстановочного знака в WORD, то выполнение этого способа не решит проблему. Но хорошая новость заключается в том, что, возможно, объединение ограничит записи в таблице сообщений, на которую вы должны смотреть.

0 голосов
/ 23 июня 2010

Возможно ли для вас перевернуть соединение? Кажется, что второй запрос является менее дорогим, и, поскольку все это простое соединение, вы хотите выполнить менее дорогой запрос, чтобы сузить набор данных, а затем выполнить соединение с более дорогим запросом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...