Оптимизация MySQL-запроса с дорогой INNER JOIN - PullRequest
6 голосов
/ 04 января 2012

Используя метод проб и ошибок, я обнаружил, что при удалении объединения из приведенного ниже запроса оно выполняется примерно в 30 раз быстрее. Может кто-нибудь объяснить, почему это так, и возможно ли оптимизировать запрос, чтобы включить дополнительное объединение без снижения производительности.

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

enter image description here

http://i.imgur.com/9VDuV.png

Это оригинальный запрос:

SELECT `comments`.`comment_id`, `comments`.`comment_html`, `comments`.`comment_time_added`, `comments`.`comment_has_attachments`, `users`.`user_name`, `users`.`user_id`, `users`.`user_comments_count`, `users`.`user_time_registered`, `users`.`user_time_last_active`, `user_profile`.`user_avatar`, `user_profile`.`user_signature_html`, `user_groups`.`user_group_icon`, `user_groups`.`user_group_name`
FROM (`comments`)
INNER JOIN `users` ON `comments`.`comment_user_id` = `users`.`user_id`
INNER JOIN `user_profile` ON `users`.`user_id` = `user_profile`.`user_id`
INNER JOIN `user_groups` ON `users`.`user_group_id` = `user_groups`.`user_group_id`
WHERE `comments`.`comment_enabled` =  1
AND `comments`.`comment_content_id` =  12
ORDER BY `comments`.`comment_time_added` ASC
LIMIT 20

Если я удаляю объединение user_groups, тогда запрос выполняется в 30 раз быстрее, как указано выше.

SELECT `comments`.`comment_id`, `comments`.`comment_html`, `comments`.`comment_time_added`, `comments`.`comment_has_attachments`, `users`.`user_name`, `users`.`user_id`, `users`.`user_comments_count`, `users`.`user_time_registered`, `users`.`user_time_last_active`, `user_profile`.`user_avatar`, `user_profile`.`user_signature_html`
FROM (`comments`)
INNER JOIN `users` ON `comments`.`comment_user_id` = `users`.`user_id`
INNER JOIN `user_profile` ON `users`.`user_id` = `user_profile`.`user_id`
WHERE `comments`.`comment_enabled` =  1
AND `comments`.`comment_content_id` =  12
ORDER BY `comments`.`comment_time_added` ASC
LIMIT 20

Мои таблицы приведены ниже. Может ли кто-нибудь рассказать, как избежать снижения производительности при включении таблицы user_groups?

--
-- Table structure for table `comments`
--

CREATE TABLE IF NOT EXISTS `comments` (
  `comment_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `comment_content_id` int(10) unsigned NOT NULL,
  `comment_user_id` mediumint(6) unsigned NOT NULL,
  `comment_original` text NOT NULL,
  `comment_html` text NOT NULL,
  `comment_time_added` int(10) unsigned NOT NULL,
  `comment_time_updated` int(10) unsigned NOT NULL,
  `comment_enabled` tinyint(1) NOT NULL DEFAULT '0',
  `comment_is_spam` tinyint(1) NOT NULL DEFAULT '0',
  `comment_has_attachments` tinyint(1) unsigned NOT NULL,
  `comment_has_edits` tinyint(1) NOT NULL,
  PRIMARY KEY (`comment_id`),
  KEY `comment_user_id` (`comment_user_id`),
  KEY `comment_content_id` (`comment_content_id`),
  KEY `comment_is_spam` (`comment_is_spam`),
  KEY `comment_enabled` (`comment_enabled`),
  KEY `comment_time_updated` (`comment_time_updated`),
  KEY `comment_time_added` (`comment_time_added`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=352 ;

-- --------------------------------------------------------

--
-- Table structure for table `users`
--

CREATE TABLE IF NOT EXISTS `users` (
  `user_id` mediumint(6) unsigned NOT NULL AUTO_INCREMENT,
  `user_ipb_id` int(10) unsigned DEFAULT NULL,
  `user_activated` tinyint(1) NOT NULL DEFAULT '0',
  `user_name` varchar(64) CHARACTER SET latin1 NOT NULL,
  `user_email` varchar(255) NOT NULL,
  `user_password` varchar(40) NOT NULL,
  `user_content_count` int(10) unsigned NOT NULL DEFAULT '0',
  `user_comments_count` int(10) unsigned NOT NULL DEFAULT '0',
  `user_salt` varchar(8) NOT NULL,
  `user_api_key` varchar(32) NOT NULL,
  `user_auth_key` varchar(32) DEFAULT NULL,
  `user_paypal_key` varchar(32) DEFAULT NULL,
  `user_timezone_id` smallint(3) unsigned NOT NULL,
  `user_group_id` tinyint(3) unsigned NOT NULL,
  `user_custom_permission_mask_id` tinyint(3) unsigned DEFAULT NULL,
  `user_lang_id` tinyint(2) unsigned NOT NULL,
  `user_time_registered` int(10) unsigned NOT NULL,
  `user_time_last_active` int(10) unsigned NOT NULL
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `user_email` (`user_email`),
  KEY `user_group_id` (`user_group_id`),
  KEY `user_auth_key` (`user_auth_key`),
  KEY `user_api_key` (`user_api_key`),
  KEY `user_custom_permission_mask_id` (`user_custom_permission_mask_id`),
  KEY `user_time_last_active` (`user_time_last_active`),
  KEY `user_paypal_key` (`user_paypal_key`),
  KEY `user_name` (`user_name`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=33 ;

-- --------------------------------------------------------

--
-- Table structure for table `user_groups`
--

CREATE TABLE IF NOT EXISTS `user_groups` (
  `user_group_id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
  `user_group_name` varchar(32) NOT NULL,
  `user_group_permission_mask_id` tinyint(3) unsigned NOT NULL,
  `user_group_icon` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`user_group_id`),
  KEY `user_group_permission_mask_id` (`user_group_permission_mask_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ;

-- --------------------------------------------------------

--
-- Table structure for table `user_profile`
--

CREATE TABLE IF NOT EXISTS `user_profile` (
  `user_id` mediumint(8) unsigned NOT NULL,
  `user_signature_original` text,
  `user_signature_html` text,
  `user_avatar` varchar(64) DEFAULT NULL,
  `user_steam_id` varchar(64) DEFAULT NULL,
  `user_ps_id` varchar(16) DEFAULT NULL,
  `user_xbox_id` varchar(64) DEFAULT NULL,
  `user_wii_id` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`user_id`),
  KEY `user_steam_id` (`user_steam_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Ответы [ 4 ]

7 голосов
/ 04 января 2012

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

Если вы видите непредвиденное поведение в плане запроса, вы можете заставить базу данных обновить свою статистику;в MySQL вы можете использовать Optimize Table - который делает все, включая переупорядочение самой таблицы, или Analyze Table , который обновляет только индексы.

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

Стоит измерить производительность «таблицы оптимизации» - на хорошо указанном оборудовании это может занять всего парусекунды для таблиц «нормального» размера (до миллионов записей, с несколькими индексами).Это может означать, что у вас может быть «неформальное» окно обслуживания - вы не переводите приложение в автономный режим, вы просто соглашаетесь с тем, что у некоторых пользователей будет снижена производительность во время выполнения сценариев.

2 голосов
/ 04 января 2012

MySQL имеет функцию EXPLAIN, которая поможет вам понять запрос:

$ mysql
> EXPLAIN SELECT `comments`.`comment_id`, `comments`.`comment_html`,`comments`.`comment_time_added`, `comments`.`comment_has_attachments`, `users`.`user_name`, `users`.`user_id`, `users`.`user_comments_count`, `users`.`user_time_registered`, `users`.`user_time_last_active`, `user_profile`.`user_avatar`, `user_profile`.`user_signature_html`
  FROM (`comments`)
  INNER JOIN `users` ON `comments`.`comment_user_id` = `users`.`user_id`
  INNER JOIN `user_profile` ON `users`.`user_id` = `user_profile`.`user_id`
  WHERE `comments`.`comment_enabled` =  1
  AND `comments`.`comment_content_id` =  12
  ORDER BY `comments`.`comment_time_added` ASC
  LIMIT 20

MySQL может просто отсутствовать или пропустить индекс.

Вы можете узнать больше о понимании вывода EXPLAIN здесь из документации (немного хардкорной) , или еще лучше из более простого объяснения здесь (игнорируйте тот факт, что это на сайте Java.)

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

1 голос
/ 04 января 2012

Не могли бы вы попробовать это (вы можете удалить соединение с user_group).Это может быть быстрее в случае, если запрос извлекает небольшой набор данных из таблицы comments:

SELECT 
   comments.comment_id, comments.comment_html, comments.comment_time_added, comments.comment_has_attachments, users.user_name, users.user_id, users.user_comments_count, users.user_time_registered, users.user_time_last_active, user_profile.user_avatar, user_profile.user_signature_html, user_groups.user_group_icon, user_groups.user_group_name
FROM 
   (select * from comments where comment_content_id = 12 and active = 1) comments
      INNER JOIN users u ON c.comment_user_id = users.user_id
      INNER JOIN user_profile ON users.user_id = user_profile.user_id
      INNER JOIN user_groups ON users.user_group_id = user_groups.user_group_id
ORDER BY comments.comment_time_added ASC
LIMIT 20
0 голосов
/ 11 декабря 2012

Попробуйте использовать левые соединения для ненулевых отношений.

Похоже, что поскольку внутренние объединения всегда симметричны, mysql переупорядочит объединения, чтобы сначала использовать наиболее привлекательную (обычно наименьшую) таблицу.

Поскольку левые объединения не всегда симметричны, mysql не будет их переупорядочивать, и, таким образом, вы можете использовать их для изменения порядка таблиц. Однако с ненулевым полем left и inner эквивалентны, поэтому ваши результаты не изменятся.

Порядок таблиц определяет, какие показатели используются, что может значительно повлиять на производительность.

...