Автоинкрементные поля в базах данных без автоинкрементного поля - PullRequest
9 голосов
/ 09 февраля 2009

В MS Sql Server легко создавать автоинкрементные поля. В моих системах я перестал использовать поля автоинкремента для первичных ключей, и теперь я использую Guid. Это было круто, у меня есть много преимуществ с этим изменением. Но в других областях, не относящихся к первичным ключам, мне действительно нужно было реализовать «мягкий автоинкремент». Это потому, что моя система не зависит от БД, поэтому я автоматически создаю значение autoinc в c #.

Я хотел бы узнать о решениях для автоинкрементных полей в базах данных без автоинкремента, какое решение вы используете и почему? Есть какое-то заявление Sql Ansi по этому поводу? и генерация непосредственно из моего c #, лучшее решение?

PS: я знаю, что выбирает max (id) +1 из таблицы это не совсем дружественно по отношению к ...

Ответы [ 5 ]

15 голосов
/ 09 февраля 2009

Механизм генерации уникальных значений идентификаторов не должен подвергаться изоляции транзакции. Это необходимо для базы данных, чтобы генерировать отдельное значение для каждого клиента, лучше, чем уловка SELECT MAX(id)+1 FROM table, что приводит к состоянию гонки, если два клиента пытаются одновременно выделить новые значения id.

Вы не можете смоделировать эту операцию с использованием стандартных запросов SQL (если вы не используете блокировки таблиц или сериализуемые транзакции). Это должен быть механизм, встроенный в ядро ​​базы данных.

ANSI SQL не описывал операцию генерации уникальных значений для суррогатных ключей до SQL: 2003. До этого не было стандарта для автоинкрементных столбцов, поэтому почти каждая марка СУБД предоставляла свое собственное решение. Естественно, они сильно различаются, и нет возможности использовать их простым, независимым от базы данных способом.

  • MySQL имеет опцию столбца AUTO_INCREMENT или псевдотип данных SERIAL, который эквивалентен BIGINT UNSIGNED AUTO_INCREMENT;
  • Microsoft SQL Server имеет параметр столбца IDENTITY и NEWSEQUENTIALID(), что является чем-то между автоинкрементом и GUID;
  • Oracle имеет SEQUENCE объект;
  • PostgreSQL имеет объект SEQUENCE или псевдо-тип данных SERIAL, который неявно создает объект последовательности в соответствии с соглашением об именах;
  • InterBase / Firebird имеет объект GENERATOR, который во многом похож на SEQUENCE в Oracle; Firebird 2.1 тоже поддерживает SEQUENCE;
  • SQLite обрабатывает любое целое число, объявленное как ваш первичный ключ, как неявное автоинкрементное;
  • В DB2 UDB есть почти все: SEQUENCE объекты, или вы можете объявить столбцы с опцией "GEN_ID".

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

2 голосов
/ 10 февраля 2009

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

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

Сделано так, ваши вставки могут быть такими простыми как:

INSERT INTO foo (id, name, rank, serial_number)
 VALUES (getNextFooId(), 'bar', 'fooRank', 123456);

Затем определите getNextFooId () особым образом, когда база данных инициализируется.

1 голос
/ 09 февраля 2009

Традиционное решение состоит в том, чтобы иметь таблицу идентификаторов, которая выглядит примерно так

CREATE TABLE ids (
  tablename VARCHAR(32) NOT NULL PRIMARY KEY,
  nextid INTEGER
)

, которая заполняется одной строкой на таблицу при создании базы данных.

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

1 голос
/ 09 февраля 2009

Большинство баз данных, в которых нет полей автоинкремента, таких как SQL Server (я имею в виду Oracle), имеют последовательности, в которых вы запрашиваете последовательность для следующего числа. Независимо от того, сколько людей запрашивают номера одновременно, каждый получает уникальный номер.

0 голосов
/ 07 февраля 2011

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

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

(из http://dev.mysql.com/doc/refman/5.1/en/information-functions.html#function_last-insert-id)

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

CREATE TABLE  `post` (
  `id` INT(10) UNSIGNED NOT NULL,
  `title` VARCHAR(100) NOT NULL,
  `comment_sequence` INT(10) UNSIGNED NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
);

CREATE TABLE  `comment` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `post_id`  INT(10) UNSIGNED NOT NULL,
  `sequence` INT(10) UNSIGNED NOT NULL,
  `content` TEXT NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO post(id, title) VALUES(1, 'first post');
INSERT INTO post(id, title) VALUES(2, 'second post');

UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=1;
INSERT INTO `comment`(post_id, sequence, content) VALUES(1, Last_insert_id(), 'blah');

UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=1;
INSERT INTO `comment`(post_id, sequence, content) VALUES(1, Last_insert_id(), 'foo');

UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=1;
INSERT INTO `comment`(post_id, sequence, content) VALUES(1, Last_insert_id(), 'bar');

UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=2;
INSERT INTO `comment`(post_id, sequence, content) VALUES(2, Last_insert_id(), 'lorem');

UPDATE post SET comment_sequence=Last_insert_id(comment_sequence+1) WHERE id=2;
INSERT INTO `comment`(post_id, sequence, content) VALUES(2, Last_insert_id(), 'ipsum');

SELECT * FROM post;
SELECT * FROM comment;
...