Удаление значений из одной таблицы с помощью другой. Супер медленно

#mysql #sql

#mysql #sql

Вопрос:

В той же базе данных у меня есть таблица messages , столбцы которой: id , title , text я хочу. Я хочу, чтобы в таблице были только те записи, в которых title нет записей lastlogon , которые затем называются эквивалентом username заголовка.

Я использовал эту команду SQL в PHP, обычно для ее запуска требовалось 2-3 секунды:

SELECT DISTINCT * FROM messages WHERE title NOT IN (SELECT username FROM lastlogon) LIMIT 1000

Все было хорошо, пока в таблице lastlogon не появилось около 80% таблицы значений messages . В сообщениях около 8000 записей, в lastlogon около 7000. Теперь для его прохождения требуется от минуты до 2 минут. MySQL разгоняется до очень высокой загрузки процессора.

Я попробовал следующее, но не повезло сократить время:

SELECT id,title,text FROM messages a LEFT OUTER JOIN lastlogon b ON (a.title = b.username) LIMIT 1000

Почему вдруг для такого небольшого количества записей требуется так много времени? Я несколько раз пытался перезапустить mysql и apache. Я использую debian linux.

Редактировать: вот структуры

 --
-- Table structure for table `lastlogon`
--

CREATE TABLE IF NOT EXISTS `lastlogon` (
  `username` varchar(25) NOT NULL,
  `lastlogon` date NOT NULL,
  `datechecked` date NOT NULL,
  PRIMARY KEY (`username`),
  KEY `username` (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

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

--
-- Table structure for table `messages`
--

CREATE TABLE IF NOT EXISTS `messages` (
  `id` smallint(9) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `email` varchar(50) NOT NULL,
  `text` mediumtext,
  `folder` tinyint(2) NOT NULL,
  `read` smallint(5) unsigned NOT NULL,
  `dateline` int(10) unsigned NOT NULL,
  `ip` varchar(15) NOT NULL,
  `attachment` varchar(255) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `username` varchar(300) NOT NULL,
  `error` varchar(500) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `title` (`title`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=9010 ;
  

Редактировать 2

Отредактированная структура с новыми индексами. После размещения индекса для обоих messages.title и lastlogon.username я получил следующие результаты:

Отображение строк 0-29 (всего 623, запрос занял 74,4938 сек)

Комментарии:

1. У вас есть индекс lastlogon.username ?

2. Можете ли вы включить план объяснения? Я ожидаю, что он ответит на вопрос Клайва, сказав, что индекса нет, и выполняется полное сканирование таблицы.

3. uses lastlogon.username — это первичный ключ. при messages этом у него есть отдельный первичный ключ id .

4. @BHare: Насколько я понимаю, созданный индекс будет по двум первичным ключам, а не по одному для каждого столбца (хотя я вполне могу ошибаться), попробуйте добавить индекс специально для username столбца

Ответ №1:

Во-первых: замените ключ в title на составной ключ в title id

 ALTER TABLE messages DROP INDEX title;
ALTER TABLE messages ADD INDEX title (title, id);
  

Теперь измените select на:

 SELECT m.* FROM messages m
LEFT JOIN lastlogon l ON (l.username = m.title)
WHERE l.username IS NULL
-- GROUP BY m.id DESC -- faster replacement for distinct. I don't think you need this.
LIMIT 1000;
  

Или

 SELECT m.* FROM messages m
WHERE m.title NOT IN (SELECT l.username FROM lastlogon l)
-- GROUP BY m.id DESC -- faster than distinct, I don't think you need it though.
LIMIT 1000;
  

Еще одна проблема с медлительностью — это SELECT m.* часть.
Выбирая все столбцы, вы заставляете MySQL выполнять дополнительную работу.
Выберите только те столбцы, которые вам нужны:

 SELECT m.title, m.name, m.email, ......
  

Это также ускорит запрос.

Есть еще один трюк, который вы можете использовать:
Замените ограничение 1000 датой отсечения.

Шаг 1: Добавьте индекс в метку времени (или любое другое поле, которое вы хотите использовать для отсечения).

 SELECT m.* FROM messages m
LEFT JOIN lastlogon l ON (l.username = m.title)
WHERE (m.id > (SELECT MIN(M2.ID) FROM messages m2 WHERE m2.timestamp >= '2011-09-01'))
  AND l.username IS NULL
-- GROUP BY m.id DESC -- faster replacement for distinct. I don't think you need this.
  

Ответ №2:

Я предлагаю вам добавить индекс messages.title . Затем попробуйте снова запустить запрос и проверить производительность.

Комментарии:

1. На самом деле не помогло. Есть еще идеи?

2. @BHare Вы имеете в виду, что вы получаете ту же скорость или что улучшение не так хорошо, как вы ищете?

3. Включение плана объяснения в ваш запрос очень поможет.

4. @BHare Dems прав. Кроме того, я вижу вашу правку и понимаю, что запрос улучшен на 35-40%, что не является незначительным. В любом случае, я думаю, что также, если вы можете выполнить другую оптимизацию, вы никогда не вернетесь к 2-3 секундам или чему-то подобному. На мой взгляд, лучший способ, которым вы можете следовать, — это использовать систему кэширования.