Насколько плохо использовать SELECT MAX(id) в MYSQL вместо mysql insert id() в PHP?


Предыстория: Я работаю над системой, в которой разработчики, похоже, используют функцию, которая выполняет запрос MYSQL, например "SELECT MAX(id) AS id FROM TABLE", всякий раз, когда им нужно получить идентификатор ПОСЛЕДНЕЙ вставленной строки (таблица, содержащая столбец auto_increment).

Я знаю, что это ужасная практика (потому что одновременные запросы испортят записи), и я пытаюсь донести это до нетехнической/управленческой команды, на что они отвечают...

"Oh okay, we'll only face this problem when we have 
(a) a lot of users, or 
(b) it'll only happen when two people try doing something
    at _exactly_ the same time"

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

Есть какие-нибудь математические соображения по этому поводу? Опять же, я ЗНАЮ, что это ужасная практика, я просто хочу понять переменные в этой ситуации...


Обновление: Спасибо за комментарии, ребята - мы движемся в правильном направлении и получаем код исправлено!

Author: DrMHC, 2010-08-18

5 answers

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

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

Я отчасти согласен с @Mark Baker что существует некоторое соображение о производительности, но поскольку id является первичным ключом, запрос MAX будет очень быстрым. Конечно, LAST_INSERT_ID() будет быстрее (так как это просто чтение из переменной сеанса), но только на незначительную величину.

И для этого вам не нужно много пользователей. Все, что вам нужно, - это много одновременных запросов (даже не так много). Если время между началом вставки и началом выбора составляет 50 миллисекунд (при условии, что база данных безопасности транзакций движок), то вам нужно всего 20 запросов в секунду, чтобы начать последовательно решать проблему с этим. Дело в том, что окно для ошибки нетривиально. Если вы говорите 20 запросов в секунду (что на самом деле не так много), и предполагаете, что средний человек посещает одну страницу в минуту, вы говорите только о 1200 пользователях. И это для того, чтобы это происходило регулярно. Это может произойти один раз только с 2 пользователями.

И прямо из документации MySQL по этому вопросу:

You can generate sequences without calling LAST_INSERT_ID(), but the utility of 
using the function this way is that the ID value is maintained in the server as 
the last automatically generated value. It is multi-user safe because multiple 
clients can issue the UPDATE statement and get their own sequence value with the
SELECT statement (or mysql_insert_id()), without affecting or being affected by 
other clients that generate their own sequence values.
 5
Author: ircmaxell, 2010-08-18 12:45:30

Вместо того, чтобы использовать SELECT MAX(id), вы должны сделать так, как сказано в документации :

Вместо этого используйте внутреннюю функцию SQL MySQL LAST_INSERT_ID() в SQL-запросе

Несмотря на это, ни SELECT MAX(id), ни mysql_insert_id() не являются "потокобезопасными", и у вас все равно может быть состояние гонки. Лучший вариант, который у вас есть, - заблокировать таблицы до и после ваших запросов. Или еще лучше использовать транзакции.

 1
Author: Colin Hebert, 2010-08-18 12:29:29

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

 0
Author: David, 2010-08-18 12:10:34

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

По крайней мере, используя LAST_INSERT_ID(), вы получите последний идентификатор для определенного ресурса, поэтому не будет иметь значения, сколько новых записей было добавлено между ними.

 0
Author: gabe3886, 2010-08-18 12:10:50

В дополнение к риску возврата неправильного значения идентификатора, существует также дополнительная нагрузка на запрос базы данных SELECT MAX(id), и на самом деле для выполнения требуется больше PHP-кода, чем простой mysql_insert_id(). Зачем намеренно кодировать что-то, чтобы быть медленным?

 0
Author: Mark Baker, 2010-08-18 12:34:44