В MySQL, как объединить две очень большие таблицы, у которых есть столбцы в условии WHERE? - PullRequest
4 голосов
/ 08 сентября 2010

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

posts
 id (int)
 blog_id (int)
 published_date (datetime)
 title (varchar)
 body (text)

posts_tags 
 post_id (int)
 tag_id (int)

Со следующими индексами:

posts: [blog_id, published_date]
tags: [tag_id, post_id]

Мы хотим выбрать 10 самых последних сообщений в данном блоге, которые были помечены как "foo". Ради этого обсуждения предположим, что в блоге содержится 10 миллионов сообщений, а 1 миллион из них помечен как "foo". Каков наиболее эффективный способ запроса этих данных?

Наивный подход заключается в следующем:

 SELECT 
  id, blog_id, published_date, title, body
 FROM 
  posts p
 INNER JOIN
  posts_tags pt 
  ON pt.post_id = p.id
 WHERE
  p.blog_id = 1
  AND pt.tag_id = 1
 ORDER BY
  p.published_date DESC
 LIMIT 10

MySQL будет использовать наши индексы, но все равно будет сканировать миллионы записей. Есть ли более эффективный способ получения этих данных без денормализации схемы?

Ответы [ 4 ]

3 голосов
/ 08 сентября 2010

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

FROM 
posts p
INNER JOIN
posts_tags pt 
ON pt.post_id = p.id
    AND pt.tag_id = 1
WHERE
p.blog_id = 1
2 голосов
/ 08 сентября 2010

если производительность имеет первостепенное значение, денормализуйте, как предложено:

таблица:

create table posts_tags
(
blog_id int unsigned not null, -- denormalise
tag_id smallint unsigned not null,
post_id int unsigned not null,
primary key(blog_id, tag_id, post_id) -- clustered composite PK
)
engine=innodb;

триггер денормализации:

delimiter #

create trigger posts_tags_before_ins_trig before insert on posts_tags
for each row
proc_main:begin

declare b_id int unsigned default 0;

   select blog_id into b_id from posts where post_id = new.post_id;

   set new.blog_id = b_id;

end proc_main #

delimiter ;

запрос хранимой процедуры: (предполагается, что posts.post_id был auto_increment PK)

delimiter ;

drop procedure if exists get_latest_blog_posts_by_tag;

delimiter #

create procedure get_latest_blog_posts_by_tag
(
in p_blog_id int unsigned,
in p_tag_id smallint unsigned
)
proc_main:begin

  select
    p.*
  from
    posts p
  inner join 
  (
    select distinct
      pt.post_id
    from
      posts_tags pt
    where
      pt.blog_id = p_blog_id and pt.tag_id = p_tag_id
    order by
      pt.post_id desc limit 10
  ) rp on p.post_id = rp.post_id
  order by
    p.post_id desc;

end proc_main #

delimiter ;

call get_latest_blog_posts_by_tag(1,1);
2 голосов
/ 08 сентября 2010

Скорее всего, MySQL сначала будет использовать индекс (blog_id, published_date) для сканирования всех строк, удовлетворяющих условию blog_id = 1, начиная со строки с самым новым published_date. Для этого нужно просто отсканировать назад по указателю, начиная с нужного места. Для каждой строки он должен присоединиться к таблице posts_tags. На данный момент известны как tag_id, так и post_id, так что это просто поиск в первичном индексе, чтобы увидеть, существует ли строка. В 10% строк есть тег foo, поэтому в среднем в posts около 100 строк таблица должна быть проверена до того, как будут найдены первые 10 строк набора результатов.

Я ожидаю, что отправленный вами запрос будет выполняться довольно быстро, если тег foo является общим. Я не думаю, что он проверит миллионы строк - возможно, несколько сотен или несколько тысяч, если вам не повезет. Как только он нашел 10 подходящих строк, он может остановиться, не проверяя больше строк.

С другой стороны, если вы выберете тег, у которого менее 10 вхождений, он будет медленным, поскольку ему придется сканировать все строки в этом блоге.

Есть ли у вас показатели производительности, показывающие, что запрос выполняется особенно медленно, даже если часто встречается тег, который вы ищете? Можете ли вы опубликовать вывод EXPLAIN для запроса?

0 голосов
/ 08 сентября 2010

Если план запроса оценивает, что число соединяемых строк мало, то он может не использовать индекс. Поскольку сканирование является линейной операцией, оно работает лучше для небольшого числа строк, тогда как использование индекса работает лучше для большого количества строк. Так как другие предложили взглянуть на план запроса, чтобы увидеть, как он оценивает количество строк.

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

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

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

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