MySQL – как эффективно получить строку с наименьшим идентификатором?

Есть ли более быстрый способ обновить самую старую строку таблицы MySQL, которая соответствует определенному условию, чем использование ORDER BY id LIMIT 1, как в следующем запросе?

UPDATE mytable SET field1 = '1' WHERE field1 = 0 ORDER BY id LIMIT 1;

Примечание:

  • Предположим, что первичный ключ — id, а также есть индекс field1.
  • Мы обновляем одну строку.
  • Мы не обновляем самую старую строку строго, мы обновляем самую старую строку, соответствующую условию.
  • Мы хотим обновить самую старую совпадающую строку, т. е. самую нижнюю id, т. е. начало очереди FIFO.

Вопросы:

  • ORDER BY id обязательно? Как MySQL упорядочивает по умолчанию?

Пример реального мира

У нас есть таблица БД, используемая для очереди электронной почты. Строки добавляются, когда мы хотим поставить электронные письма в очередь для отправки нашим пользователям. Строки удаляются заданием cron, которое запускается каждую минуту, обрабатывая как можно больше за эту минуту и ​​отправляя 1 электронное письмо на строку.

Мы планируем отказаться от этого подхода и использовать что-то вроде Gearman или Resque для обработки нашей очереди электронной почты. Но в то же время у меня есть вопрос о том, как мы можем эффективно пометить самый старый элемент очереди для обработки, также известный как строка с самым низким идентификатором. Этот запрос выполняет задание:

mysql_query("UPDATE email_queue SET processingID = '1' WHERE processingID = 0 ORDER BY id LIMIT 1");

Тем не менее, он часто появляется в медленном журнале mysql из-за проблем с масштабированием. Запрос может занять более 10 секунд, если в таблице 500 000 строк. Проблема в том, что эта таблица сильно разрослась с тех пор, как была впервые представлена, и теперь иногда имеет полмиллиона строк и накладные расходы в 133,9 МБ. Например, мы ВСТАВЛЯЕМ 6000 новых строк примерно 180 раз в день и УДАЛЯЕМ примерно столько же.

Чтобы запрос не появлялся в медленном журнале, мы удалили ORDER BY id, чтобы остановить массовую сортировку всей таблицы. то есть

mysql_query("UPDATE email_queue SET processingID = '1' WHERE processingID = 0 LIMIT 1");

... но новый запрос больше не всегда получает строку с наименьшим идентификатором (хотя это часто бывает). Есть ли более эффективный способ получить строку с наименьшим идентификатором, кроме использования ORDER BY id ?

Для справки, это структура таблицы очереди электронной почты:

CREATE TABLE IF NOT EXISTS `email_queue` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `time_queued` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Time when item was queued',
  `mem_id` int(10) NOT NULL,
  `email` varchar(150) NOT NULL,
  `processingID` int(2) NOT NULL COMMENT 'Indicate if row is being processed',
  PRIMARY KEY (`id`),
  KEY `processingID` (`processingID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

person Tom    schedule 08.09.2010    source источник


Ответы (5)


Дайте этому прочитать:

person shamittomar    schedule 08.09.2010

похоже, что у вас есть другие процессы, блокирующие таблицу, препятствующие своевременному завершению обновления - вы рассматривали возможность использования innodb?

person Jon Black    schedule 08.09.2010
comment
innodb использует блокировку на уровне строк, тогда как myisam использует блокировки на уровне таблицы. количество строк в вашей таблице довольно мало - я регулярно работаю с 1 миллиардом строк, используя innodb, и это очень эффективно !! - person Jon Black; 08.09.2010

Я думаю, что «медленная часть» происходит от

WHERE processingID = 0 

Это медленно, потому что это не проиндексировано. Но индексация этого столбца (ИМХО) тоже кажется некорректной. Идея состоит в том, чтобы изменить приведенный выше запрос на что-то вроде:

WHERE id = 0 

Что теоретически будет быстрее, так как использует index.

Как насчет создания еще одной таблицы, содержащей id строк, которые не были обработаны? Следовательно, вставка работает дважды. Первый для вставки в реальную таблицу, а второй для вставки id в «таблицу не обработанных». Обрабатывающая часть также должна удвоить свою нагрузку. Сначала нужно получить id из таблицы «не обработано», а затем удалить ее. Вторая задача обработки части — это, конечно, обработка.

Конечно, столбец id в таблице «не обработано» должен индексировать свое содержимое. Просто чтобы гарантировать, что выбор и удаление будут быстрее.

person Hendra Jaya    schedule 08.09.2010
comment
Спасибо за ваш комментарий. Но я думаю, что у меня есть индекс для processingID: KEY processingID (processingID) ? - person Tom; 08.09.2010

Этот вопрос старый, но для справки для всех, кто попадает сюда:

У вас есть условие для обработки ID (WHERE processingID = 0), и в пределах этого ограничения вы хотите упорядочить по ID.

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

Как мы можем улучшить это?

Учтите, что у вас есть индекс на processingID. Технически первичный ключ всегда добавляется (именно так индекс может «указывать» на что-либо в первую очередь). Итак, у вас действительно есть индекс processingID, id. Это означает, что заказ будет быстрым.

Измените свой заказ на: ORDER BY processingID, id

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

person Timo    schedule 17.08.2018

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

Как вы сказали, лучшее решение - использовать что-то вроде Resque или RabbitMQ & co.

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

person rtacconi    schedule 08.09.2010
comment
Спасибо, интересная информация. Порядок по умолчанию был тем, на что мы делали ставку, когда удаляли ORDER BY id. Однако на практике мы обнаружили, что MySQL не упорядочивает по умолчанию все время по идентификатору, хотя мы не проверяли это научно, поэтому моя информация не является 100% точной. - person Tom; 08.09.2010
comment
Я отметил, что MySQL не будет возвращаться по порядку с помощью PK, особенно после удаления из указанной таблицы. - person Jé Queue; 29.12.2010