Автоматическое приращение MySQL не откатывается

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

11 ответов


позвольте мне указать на кое-что очень важное:

вы никогда не должны зависеть от числовых характеристик ключей автоматически.

то есть, кроме сравнения их для равенства (=) или неравномерности ( ), вы не должны делать ничего другого. Нет реляционных операторов ( ), нет сортировки по индексам и т. д. Если вам нужно отсортировать по" добавленной дате", есть столбец" добавленная дата".

относитесь к ним как к яблокам и апельсинам: имеет ли смысл спрашивать, является ли яблоко таким же, как оранжевый? Да. Имеет ли смысл спрашивать, больше ли яблоко, чем апельсин? Нет. (На самом деле, да, но вы меня поняли.)

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


Он не может работать таким образом. Подумайте:

  • program one, вы открываете транзакцию и вставляете в таблицу FOO, которая имеет первичный ключ autoinc (произвольно, мы говорим, что он получает 557 для его ключевого значения).
  • запускается программа two, она открывает транзакцию и вставляет в таблицу FOO получение 558.
  • программа две вставки в строку таблицы, которая имеет столбец, который является внешним ключом к FOO. Итак, теперь 558 расположен как в FOO, так и в BAR.
  • программа два теперь совершает.
  • программа Три запускается и генерирует отчет из таблицы FOO. Запись 558 напечатана.
  • после этого программа один откат.

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

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

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

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


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

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


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

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

если таблица называется "TableA", а столбец автоматического приращения-"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, ...)

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

во всех режимах блокировки (0, 1 и 2), Если a транзакции, которая генерируется откат значений автоматического приращения, эти автоинкрементные значения "растеряться."После создания значения для столбец auto-increment, он не может быть откат, ли Инструкция "INSERT-like" завершена, и независимо от того, содержит ли транзакция откатывается. Такие потеряли значения не используются повторно. Таким образом, может пробелы в значениях, хранящихся в Столбец AUTO_INCREMENT таблицы.


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

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

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

ALTER TABLE tbl auto_increment = 1

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

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


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

если таблица не содержит значений или ноль строк

добавить цель для ошибки mysql Type update from on SELECT


устранение:

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

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

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

  • давайте удалим значение AUTO_INCREMENT из столбца 'Id' (это не удалит вставленные строки):
ALTER TABLE tbl_test MODIFY COLUMN Id  int(11) NOT NULL FIRST;
  • наконец, мы создаем триггер перед вставкой, чтобы автоматически генерировать значение "Id". Но использование этого способа не повлияет на значение 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;

вот именно! Ты молодец!

не за что.

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

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


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

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) вставьте новую запись вместе с основным идентификатором, который вы только что получили из БД. Это переопределит текущий индекс автоматического приращения и убедитесь, что между записями нет пробелов ID.

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


$masterConn = mysql_connect("localhost", "root", '');
mysql_select_db("sample", $masterConn);

for($i=1; $i<=10; $i++) {
    mysql_query("START TRANSACTION",$masterConn);
    $qry_insert = "INSERT INTO `customer` (id, `a`, `b`) VALUES (NULL, '$i', 'a')";
    mysql_query($qry_insert,$masterConn);
    if($i%2==1) mysql_query("COMMIT",$masterConn);
    else mysql_query("ROLLBACK",$masterConn);

    mysql_query("ALTER TABLE customer auto_increment = 1",$masterConn);

}



echo "Done";