MySQL AUTO_INCREMENT не откатывается

Я использую поле MySQL AUTO_INCREMENT и InnoDB для поддержки транзакций. Заметил, что при откате транзакции поле AUTO_INCREMENT не откатывается? Я узнал, что он был разработан таким образом, но есть ли какие-то обходные пути?


person codegy    schedule 16.01.2009    source источник
comment
Просто примечание: но значения auto_increment будут сброшены на max + 1 столбца после сброса сервера.   -  person J.D. Fitz.Gerald    schedule 24.03.2009
comment
Это не относится к mysql, Postgres ведет себя точно так же. Объяснения имеют смысл.   -  person Nils    schedule 07.05.2012


Ответы (11)


Позвольте мне отметить кое-что очень важное:

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

То есть, кроме сравнения их на равенство (=) или неравенство (‹›), вам больше ничего не следует делать. Никаких операторов отношения (‹,›), сортировки по индексам и т. Д. Если вам нужно отсортировать по «дате добавления», сделайте столбец «дата добавления».

Относитесь к ним как к яблокам и апельсинам. Имеет ли смысл спрашивать, совпадает ли яблоко с апельсином? да. Есть ли смысл спрашивать, больше ли яблоко апельсина? Нет (на самом деле, это так, но вы меня поняли.)

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

person Tamas Czinege    schedule 16.01.2009
comment
Можете ли вы немного уточнить это? Я имею в виду, зачем добавлять целый другой столбец вместе со всеми накладными расходами, которые это влечет за собой (управление индексами, использование диска, дополнительный ввод-вывод и т. Д. И т. Д.), Когда уже есть совершенно хорошее значение для использования в столбце autoinc? По определению, он предлагает уникальное значение, которое никогда не повторяется и всегда увеличивается по мере добавления записей. Единственное, что он не является непрерывным, но пока вы предполагаете пробелы в последовательности, я не вижу проблем с его использованием для таких вещей, как сортировка набора записей по порядку их вставки. На самом деле, я бы сказал, что это лучший способ с точки зрения производительности и ясности. - person mr. w; 21.08.2012
comment
Это совет, а не ответ - person Joaquín L. Robles; 24.03.2014
comment
Добавление даты происходит во время фиксации или во время вставки. Может ли это случиться, если в таблицу вставляются несколько потоков, более поздняя временная метка видна раньше более ранней из-за задержки времени выполнения? - person user2505915; 01.06.2016
comment
Будучи разработчиком в течение многих лет, после прочтения этого ответа я чувствую, что стал просветленным! - person evilReiko; 28.09.2017
comment
@ JoaquínL.Robles, совет настолько хорош, что я забыл об ответе - person peterchaula; 07.02.2018

Так не может быть. Учитывать:

  • В первой программе вы открываете транзакцию и вставляете в таблицу FOO, которая имеет первичный ключ autoinc (произвольно, мы говорим, что он получает значение ключа 557).
  • Программа 2 запускается, она открывает транзакцию и вставляет в таблицу FOO, получая 558.
  • Запрограммируйте две вставки в таблицу BAR, в которой есть столбец, являющийся внешним ключом для FOO. Итак, теперь 558 находится как в FOO, так и в BAR.
  • Программа 2 теперь фиксируется.
  • Программа 3 запускается и генерирует отчет из таблицы FOO. Распечатывается запись 558.
  • После этого программный откат.

Как база данных восстанавливает значение 557? Переходит ли он в FOO и уменьшает все остальные первичные ключи больше 557? Как исправить БАР? Как он стирает 558, напечатанный в отчете программы три вывода?

Порядковые номера Oracle также не зависят от транзакций по той же причине.

Если вы можете решить эту проблему за постоянное время, я уверен, что вы можете заработать много денег в области базы данных.

Теперь, если у вас есть требование, чтобы в вашем поле автоматического увеличения никогда не было пробелов (например, для целей аудита). Тогда вы не сможете откатить свои транзакции. Вместо этого вам нужно иметь флаг статуса в ваших записях. При первой вставке статус записи - «Незавершенная», затем вы запускаете транзакцию, выполняете свою работу и обновляете статус до «завершена» (или что вам нужно). Затем, когда вы делаете коммит, запись становится активной. Если транзакция откатывается, неполная запись все еще присутствует для аудита. Это вызовет у вас много других головных болей, но это один из способов справиться с контрольными журналами.

person jmucchiello    schedule 16.01.2009

У меня был клиент, который нуждался в идентификаторе для отката в таблице счетов-фактур, где заказ должен быть последовательным

Мое решение в MySQL заключалось в том, чтобы удалить AUTO-INCREMENT и вытащить последний идентификатор из таблицы, добавить один (+1), а затем вставить его вручную.

Если таблица названа «ТаблицаA», а столбец «Автоинкремент» - «Id»

INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
VALUES (
(SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
Col2_Val, Col3_Val, Col4_Val, ...)
person Michael Corrigan    schedule 09.06.2011
comment
Может, так будет лучше (SELECT MAX(id) AS maxval FROM table_name;)+1 - person vinsa; 25.12.2014
comment
Это не будет атомарная операция, определенно будет небольшое окно, когда несколько запросов получат одинаковый maxID. - person Ouroboros; 22.08.2015
comment
@ P.Prasad - Не могли бы вы использовать LOCK TABLES, чтобы предотвратить это? - person Michael Corrigan; 24.08.2015
comment
Это должно быть указано в ответе. - person Ouroboros; 24.08.2015
comment
@MichaelCorrigan, почему мы блокируем таблицу для этой очень глупой вещи, потому что приложению может потребоваться много таблиц, требующих непрерывных auto_increments. Теперь, если мы начнем блокировку, один экземпляр приложения можно использовать один раз - person Ravinder Payal; 18.04.2016
comment
Пахнет неприятностями. Почему бы не создать дополнительное поле рядом с id, например fakeId, для обработки этого? Вам удастся сделать это последовательно - person Tebe; 01.12.2016
comment
Вы можете использовать LOCK TABLES, но это означает, что только одна транзакция одновременно может иметь доступ к вашей таблице. Во многих приложениях существуют десятки или сотни одновременных сеансов вставки или обновления строк в таблице. Автоинкремент не удерживает блокировку, пока вы не зафиксируете транзакцию, она блокируется только на короткое время, достаточно долго, чтобы увеличить идентификатор. (это старый ответ, но он только что появился в моей ленте сегодня) - person Bill Karwin; 14.05.2019

Почему вас волнует откат? Ключевые поля AUTO_INCREMENT не должны иметь никакого значения, поэтому вам действительно не нужно заботиться о том, какое значение используется.

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

person Paul Lefebvre    schedule 16.01.2009

Я не знаю, как это сделать. Согласно документации MySQL, это является ожидаемым поведением и будет происходить со всеми режимами блокировки innodb_autoinc_lock_mode. Конкретный текст:

Во всех режимах блокировки (0, 1 и 2), если транзакция, которая генерировала значения автоинкремента, откатывается, эти значения автоинкремента «теряются». После того, как значение сгенерировано для столбца с автоматическим приращением, его нельзя откатить, независимо от того, завершен ли оператор, подобный INSERT, и независимо от того, откатывается ли содержащая транзакция. Такие утраченные ценности не используются повторно. Таким образом, могут быть пробелы в значениях, хранящихся в столбце AUTO_INCREMENT таблицы.

person gpojd    schedule 16.01.2009

Если вы установите auto_increment в 1 после отката или удаления, при следующей вставке MySQL увидит, что 1 уже используется, и вместо этого получит значение MAX() и прибавит к нему 1.

Это гарантирует, что в случае удаления строки с последним значением (или отката вставки) она будет повторно использована.

Чтобы установить auto_increment равным 1, сделайте что-то вроде этого:

ALTER TABLE tbl auto_increment = 1

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

Имейте в виду, что это не предотвращает пропуски из записей, удаленных в середине, или если другая вставка должна произойти до того, как вы установите auto_increment обратно в 1.

person Marcus Adams    schedule 16.02.2012
comment
Также обратите внимание, что если вы это сделаете, вы должны вызвать ANALYZE TABLE tbl после, иначе старое значение AUTO_INCREMENT все равно будет отображаться в information_schema.TABLES, пока не истечет срок его действия. - person hackel; 09.04.2020

INSERT INTO prueba(id) 
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))

Если таблица не содержит значений или нулевых строк

добавить цель для обновления типа ошибки mysql FROM на SELECT

person danis    schedule 10.11.2011

Конкретный ответ на эту конкретную дилемму (которая была у меня тоже) заключается в следующем:

1) Создайте таблицу, содержащую разные счетчики для разных документов (счетов-фактур, квитанций, RMA и т. Д.); Вставьте запись для каждого из ваших документов и добавьте начальный счетчик к 0.

2) Перед созданием нового документа выполните следующие действия (например, для счетов-фактур):

UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'

3) Получите последнее значение, которое вы только что обновили, например:

SELECT LAST_INSERT_ID()

или просто используйте свою функцию PHP (или что-то еще) mysql_insert_id (), чтобы получить то же самое

4) Вставьте новую запись вместе с основным идентификатором, который вы только что получили из БД. Это переопределит текущий индекс автоинкремента и обеспечит отсутствие пропусков в идентификаторах между вашими записями.

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

person teomor    schedule 01.09.2014

РЕШЕНИЕ:

Давайте использовать tbl_test в качестве примера таблицы и предположим, что поле Id имеет атрибут AUTO_INCREMENT.

CREATE TABLE tbl_test (
Id int NOT NULL AUTO_INCREMENT ,
Name varchar(255) NULL ,
PRIMARY KEY (`Id`)
)
;

Предположим, что в таблицу уже вставлены сотни или тысячи строк, и вы больше не хотите использовать AUTO_INCREMENT; потому что при откате транзакции поле «Id» всегда добавляет +1 к значению AUTO_INCREMENT. Чтобы избежать этого, вы можете сделать следующее:

  • Давайте удалим значение AUTO_INCREMENT из столбца 'Id' (это не удалит ваши вставленные строки):
ALTER TABLE tbl_test MODIFY COLUMN Id  int(11) NOT NULL FIRST;
  • Наконец, мы создаем триггер BEFORE INSERT для автоматической генерации значения Id. Но использование этого способа не повлияет на значение вашего идентификатора, даже если вы откатите любую транзакцию.
CREATE TRIGGER trg_tbl_test_1
BEFORE INSERT ON tbl_test
FOR EACH ROW
  BEGIN
    SET NEW.Id= COALESCE((SELECT MAX(Id) FROM tbl_test),0) + 1;
  END;

Вот и все! Готово!

You're welcome.
person mld.oscar    schedule 18.11.2015
comment
Мне нравится ваше решение, как оно будет вести себя при одновременных запросах? Особенно случай, описанный @Ouroboros в stackoverflow.com/questions/449346/, можно ли это обойти? - person KumZ; 28.08.2019
comment
@KumZ, это может помочь в вашем ответе stackoverflow.com/a/42683736/4564384 - person mld.oscar; 14.09.2019

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

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

person tvanfosson    schedule 16.01.2009
comment
Это означает, что две транзакции, попадающие в одну и ту же таблицу одновременно, никогда не смогут обе успешно зафиксироваться. Это может быть огромным узким местом. А если у вас много внешних ключей, вы можете легко попасть в тупик, когда у них много отношений. Мой ответ по поводу обработки пробелов немного повредит. :-) - person jmucchiello; 16.01.2009

person    schedule
comment
Понятия не имею, почему это было закрыто ... это решает проблему - person Martin Zvarík; 30.04.2021