Почему автоинкремент MySQL увеличивается при неудачных вставках? - PullRequest
39 голосов
/ 07 мая 2010

Сотрудник только что заставил меня осознать очень странное поведение MySQL.

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

Например, когда наша последняя запись выглядит так ...

ID: 10
Username: myname

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

ID: 16
Username: mynewname

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

"Поведение механизма автоинкремента не определено, если [...] значение становится больше максимального целого числа, которое может быть сохранено в указанном целочисленном типе."

Это ожидаемое поведение?

Ответы [ 4 ]

31 голосов
/ 07 мая 2010

InnoDB - это транзакционный механизм.

Это означает, что в следующем сценарии:

  1. Session A вставляет запись 1
  2. Session B вставляет запись 2
  3. Session A откат

, существует вероятность пробела или session B заблокируется, пока session A не будет зафиксирован или откат.

InnoDB разработчики (как и большинство других разработчиков транзакционных механизмов) решили разрешить пробелы.

Из документации :

При доступе к счетчику автоинкремента InnoDB использует специальную блокировку на уровне таблицы AUTO-INC, которая сохраняется до конца текущего оператора SQL, а не до конца транзакции.Специальная стратегия освобождения блокировки была введена для улучшения параллелизма вставок в таблицу, содержащую столбец AUTO_INCREMENT

InnoDB, использующий счетчик автоинкремента в памяти, покасервер работает.Когда сервер останавливается и перезапускается, InnoDB повторно инициализирует счетчик для каждой таблицы для первой INSERT таблицы, как описано ранее.

Если вы боитесь обворачивания столбца id, сделайте его BIGINT (длиной 8 байт).

5 голосов
/ 07 мая 2010

Не зная точных внутренних данных, я бы сказал, что да, автоинкремент ДОЛЖЕН учитывать пропущенные значения для вставок с ошибками.Допустим, вы делаете банковскую транзакцию или другую, когда вся транзакция и несколько записей идут как все или ничего.Если вы попробуете вставить, получите идентификатор, затем отметьте все последующие данные этим идентификатором транзакции и вставьте подробные записи, вам необходимо убедиться, что ваша квалификация уникальна.Если у вас есть несколько человек, захлопывающих базу данных, они также должны будут убедиться, что они получат свой собственный идентификатор транзакции, чтобы не конфликтовать с вашим, когда их транзакция будет зафиксирована.Если что-то не получится в первой транзакции, не будет причинено никакого вреда и нет висячих элементов вниз по течению.

2 голосов
/ 05 июня 2018

Старый пост, но это может помочь людям, Возможно, вам придется установить innodb_autoinc_lock_mode в 0 или 2 .

Системные переменные, которые принимают числовое значение, могут быть указаны как --var_name=value в командной строке или как var_name=value в файлах параметров.

Формат параметра командной строки:

--innodb-autoinc-lock-mode=0 

OR Откройте свой mysql.ini и добавьте следующую строку:

innodb_autoinc_lock_mode=0
0 голосов
/ 24 июня 2014

Я знаю, что это старая статья, но так как я также не смог найти правильный ответ, я действительно нашел способ сделать это. Вы должны заключить ваш запрос в оператор if. Обычно это запрос вставки или вставки, а также повторяющиеся запросы, которые портят организованный порядок автоматического увеличения, поэтому для регулярных вставок используйте:

$check_email_address = //select query here\\

if ( $check_email_address == false ) {
    your query inside of here
}

и вместо INSERT AND ON DUPLICATE использовать UPDATE SET WHERE QUERY внутри или вне оператора if не имеет значения и ЗАМЕНА В ЗАПРОС , похоже, тоже работает

...