инкрементный счетчик mysql

Мой вопрос довольно прост, но ответ может быть сложным.

Я работаю на PHP и хочу вручную управлять уникальным идентификатором для своих объектов. Что сложно, так это управлять атомарностью. Я не хочу, чтобы 2 элемента получили один и тот же идентификатор.

«Элементы» сгруппированы в «Группы». В каждой группе я хочу, чтобы идентификатор элементов начинался с 1 и увеличивался постепенно для каждой вставки в этой группе.

Мое первое решение - иметь столбец «lastID» в таблице «Группы»:

CREATE TABLE groups ( id INT AUTO_INCREMENT, lastId INT )
CREATE TABLE elements ( myId INT, multiple values ...)

Чтобы избежать множества элементов с одинаковым идентификатором, мне нужно обновить lastId и выбрать его в атомарном SQL-запросе.

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

Мой вопрос в том, как решить жирную часть? Моя база данных — это MySQL с движком MyISAM, поэтому поддержка транзакций не поддерживается.

UPDATE groups 
SET lastId = lastId + 1 
WHERE id = 42

SELECT lastId 
FROM groups
WHERE id = 42

Есть ли что-то более атомарное, чем эти 2 запроса?

Спасибо


person Nicolas Thery    schedule 24.07.2012    source источник
comment
Почему вы не можете использовать идентификатор автоматического увеличения? Это именно та проблема, для решения которой он был разработан.   -  person Mark Byers    schedule 25.07.2012
comment
Мне нужно иметь много последовательностей идентификаторов для каждой группы   -  person Nicolas Thery    schedule 25.07.2012


Ответы (3)


UPDATE groups SET lastId = last_insert_id(lastId + 1)

а затем вы можете получить свой новый идентификатор с помощью

SELECT last_insert_id()

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

Это из руководства по MySQL:

Если expr передается в качестве аргумента функции LAST_INSERT_ID(), значение аргумента возвращается функцией и запоминается как следующее значение, которое будет возвращено функцией LAST_INSERT_ID(). Это можно использовать для имитации последовательностей:

Создайте таблицу для хранения счетчика последовательности и инициализируйте его:

CREATE TABLE sequence (id INT NOT NULL); 
INSERT INTO sequence VALUES (0); 

Используйте таблицу для генерации порядковых номеров следующим образом:

UPDATE sequence SET id=LAST_INSERT_ID(id+1);
SELECT LAST_INSERT_ID(); 

Оператор UPDATE увеличивает счетчик последовательности и заставляет следующий вызов LAST_INSERT_ID() возвращать обновленное значение. Оператор SELECT извлекает это значение. Функцию C API mysql_insert_id() также можно использовать для получения значения. См. Раздел 21.8.3.37, «mysql_insert_id()».

Вы можете генерировать последовательности без вызова LAST_INSERT_ID(), но полезность использования функции таким образом заключается в том, что значение ID сохраняется на сервере как последнее автоматически сгенерированное значение. Это безопасно для нескольких пользователей, поскольку несколько клиентов могут выполнить оператор UPDATE и получить свое собственное значение последовательности с помощью оператора SELECT (или mysql_insert_id()), не влияя и не подвергаясь влиянию других клиентов, которые генерируют свои собственные значения последовательности.

person Vatev    schedule 24.07.2012
comment
Это не атомарно. Например, если есть два последовательных UPDATE, за которыми следуют два последовательных SELECT, один и тот же идентификатор будет возвращен seles. - person Nicolas Thery; 25.07.2012
comment
Только если вы делаете два последовательных обновления на одном и том же соединении. Это похоже на то, как вы обычно используете last_insert_id (вы должны использовать его сразу после вставки, но это безопасно от других подключений, вставляющих вещи). - person Vatev; 25.07.2012
comment
Вы имеете в виду, что если два разных пользователя запускают обновление одновременно, last_insert_id не может возвращать одно и то же значение каждому пользователю? Если это так, то это хороший ответ, но я вижу, что MyISAM блокирует таблицу! что произойдет, если пользователь обновит заблокированную таблицу ?? В моей таблице миллионы записей! - person Nicolas Thery; 25.07.2012
comment
Да, last_insert_id разный для каждого соединения. В противном случае это будет испорчено, когда несколько пользователей вставят что-то одновременно. Таблица заблокирована только во время фактического обновления (несколько миллисекунд), пока она заблокирована, следующий запрос на обновление будет ждать, а после выполнения 1-го обновления начнется 2-е. Как я отметил в ответе, это полезно для создания автономеров с использованием специальной таблицы next_number, которая обычно имеет одну строку. - person Vatev; 25.07.2012
comment
К моей информации, я понимаю, что LAST_INSERT_ID работает только в том случае, если соединение между update и select одинаково. Затем я должен убедиться, что мой PHP Framework поддерживает соединение открытым, не так ли? Я надеюсь, что CodeIgniter держит его открытым. - person Nicolas Thery; 25.07.2012
comment
Да. Если соединение прервется до того, как вы получите last_insert_id, ваш номер будет потерян. - person Vatev; 25.07.2012

Один из вариантов — использовать изящную функцию MyISAM, позволяющую увеличивать значения auto_increment для каждой группы.

CREATE UNIQUE INDEX elements_ix1 ON элементы (groupId, myID)

myID INT NOT NULL AUTO_INCREMENT

Это более «атомарно», чем что-либо, связанное с обновлением отдельной таблицы. Обратите внимание, что это работает только для MyISAM, а не для InnoDB.


выдержка из http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html

Заметки MyISAM

Для таблиц MyISAM можно указать AUTO_INCREMENT для вторичного столбца в многостолбцовом индексе. В этом случае сгенерированное значение для столбца AUTO_INCREMENT вычисляется как MAX(auto_increment_column) + 1 WHERE prefix=given-prefix. Это полезно, когда вы хотите поместить данные в упорядоченные группы.

person spencer7593    schedule 24.07.2012
comment
Это выглядит очень красиво, это стоит +1. Но я не уверен, что хочу привязать поведение своего приложения к движку базы данных. Я хочу, чтобы его можно было перенести на другой движок. - person Nicolas Thery; 25.07.2012
comment
На самом деле моя среда разработки находится в InnoDB, чтобы принудительно проверять внешние ключи. И моя производственная среда находится на MyISAM, без FK. - person Nicolas Thery; 25.07.2012

Я бы предположил, что ваша установка MySQL также имеет механизм InnoDB, который поддерживает транзакции. Вам просто нужно изменить тип движка ваших таблиц.

person Mike Brant    schedule 24.07.2012
comment
На самом деле моя среда разработки находится в InnoDB, чтобы принудительно проверять внешние ключи. И моя производственная среда находится на MyISAM, без FK. - person Nicolas Thery; 25.07.2012
comment
Нельзя ли поменять движки в рабочих таблицах БД? Вы действительно должны сопоставить свои две среды, поскольку MyISAM и InnoDB имеют совершенно разное поведение. - person Mike Brant; 25.07.2012
comment
Нет, я не могу. Я завишу от своего провайдера. Я использую innoDB в среде разработки для принудительной проверки внешнего ключа, потому что это показывает ошибки, которых нет в MyISAM. - person Nicolas Thery; 25.07.2012