MySQL - Как эффективно получить строку с самым низким идентификатором? - PullRequest
5 голосов
/ 08 сентября 2010

Существует ли более быстрый способ обновления самой старой строки таблицы 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 для обработки нашей почтовой очереди. Но в то же время у меня есть вопрос о том, как мы можем эффективно пометить самый старый элемент очереди для обработки, a.k.a. Строка с самым низким ID. Этот запрос выполняет работу:

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, чтобы остановить массовую сортировку всей таблицы. т.е. * * 1 048

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;

Ответы [ 5 ]

3 голосов
/ 08 сентября 2010
1 голос
/ 17 августа 2018

Этот вопрос старый, но для справки для тех, кто в конечном итоге здесь:

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

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

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

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

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

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

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

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

WHERE processingID = 0 

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

WHERE id = 0 

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

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

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

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

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

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

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

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

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

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