Не забудьте воспользоваться булевой оценкой короткого замыкания :
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