Что такое автоинкремент
Перейти к содержимому

Что такое автоинкремент

  • автор:

Снова про AUTO_INCREMENT

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

Действительно, будем все хранить в оперативной памяти. Хотя, нет, ее маловато будет. Да и при неожиданном выключении все данные пропадут. Поставим ИБП. Нет, лучше 2. А еще лучше представим, что память у нас бесконечная и данные при выключении не теряет.

Теперь можно писать что-то типа такого:

Order order = new Order(); User user = new User(); order.creator = user; 

И насоздавать хоть 10 таких заказов, хоть 20, хоть 100… Хм, что-то много я насоздавал, надо бы коллекцию какую-нибудь. А к ней поиск по полям. И язык запросов, чтобы все универсально было. Плюс индексы по этим полям для ускорения поиска.

Что у нас получилось? Получилась практически обычная база данных. За одним исключением – у объектов нет первичного ключа. Все объекты идентифицируются по адресу в адресном пространстве.

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

Это подводит к мысли, почему целочисленные ключи удобны в использовании. Причины не только в реализации систем управления БД. База данных – это адресное пространство для размещения объектов. А целочисленный ключ (ID) – это ссылка на объект.

Из этого следует, что auto_increment в пределах одной таблицы — это не совсем правильно. Каждая запись в БД должна иметь уникальный адрес. Получается как бы двухмерное адресное пространство – адреса растут в одном направлении, сами объекты в другом; размер одного объекта не влияет на адреса соседних объектов. При этом неважно, распределенная БД или нет, сколько в ней серверов, баз и таблиц. Это одно адресное пространство, и адресация должна быть однозначной.

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

Однако, ID не нужно относить к атрибутам самого объекта как элемента модели данных. Например:

int x = 2; 

У переменной x есть значение 2 и адрес 0x123456. Но нельзя сказать, что адрес – это атрибут целочисленных значений. Также нельзя сказать, что ID – это атрибут объектов типа User и Order. Он служит просто для связи абстрактной модели и технической реализации.

Все ограничения естественного ключа нужно делать дополнительными техническими средствами, на уровне самой БД или приложения. На самом деле, я даже не могу представить ситуацию, в которой естественный первичный ключ является «естественным». Все варианты типа номер паспорта, телефона, ИНН – это искусственно введенная нумерация объектов, именно потому что нельзя выделить у них или их владельцев уникальный набор признаков. И использовать ее можно только для идентификации именно этих объектов – выданных паспортов, узлов телефонной сети, экономического субъекта, платящего налоги. В реальности всегда есть возможность существования дубликатов с одинаковыми свойствами, и набора разных свойств в разное время у одной сущности.

P.S.: Мои рассуждения носят теоретический характер, я не работал с базами с подобной структурой. Технически это сделать несложно – одна sequence на все таблицы. Если у кого-то есть такой опыт, просьба поделиться информацией в комментариях – плюсы, минусы, подводные камни.

  • SQL
  • Проектирование и рефакторинг

Первичный ключ – GUID или автоинкремент?

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

Ниже приведены преимущества каждого из вариантов.

  • Занимает меньший объем
  • Теоретически, более быстрая генерация нового значения
  • Более быстрая десериализация
  • Проще оперировать при отладке, поддержке, так как число гораздо легче запомнить
  • При репликации между несколькими экземплярами базы, где добавление новых записей происходит более, чем в одну реплику, GUID гарантирует отсутствие коллизий
  • Позволяет генерировать идентификатор записи на клиенте, до сохранения ее в базу
  • Обобщение первого пункта — обеспечивает уникальность идентификаторов не только в пределах одной таблицы, что для некоторых решений может быть важно
  • Делает практически невозможным «угадывание» ключа в случаях, когда запись можно получить, передав ее идентификатор в какой-нибудь публичный API

GUID можно генерировать как на клиенте, так и самой базой данных — уже два варианта. К тому же, в MS SQL есть две функции для получения уникального идентификатора — NEWID и NEWSEQUENTIALID. Давайте разберемся, в чем их отличие и может ли оно быть существенным на практике.

Привычная генерация уникальных идентификаторов в том же .NET через Guid.NewGuid() дает множество значений, не связанных друг с другом никакой закономерностью. Если ряд GUID-ов, полученных из этой функции, держать в отсортированном списке, то каждое новое добавляемое значение может «попадать» в любую его часть. Функция NEWID() в MS SQL работает аналогично — ряд ее значений весьма хаотичен. В свою очередь, NEWSEQUENTIALID() дает те же уникальные идентификаторы, только каждое новое значение этой функции больше предыдущего, при этом идентификатор остается «глобально уникальным».

Если использовать Entity Framework Code First, и объявить первичный ключ вот таким образом

[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id

в базе данных будет создана таблица с первичным кластерным ключом, который имеет значение по умолчанию NEWSEQUENTIALID(). Сделано это из соображений производительности. Опять же, в теории, вставлять новое значение в середину списка более накладно, чем добавление в конец. База данных, конечно же, не массив в памяти, и вставка новой записи в середину списка строк не приведет к физическому сдвигу всех последующих. Тем не менее, дополнительные накладные расходы будут — разделение страниц (page split). По итогу также будет сильная фрагментация индексов, которая может отразиться на производительности выборки данных. Неплохое объяснение того, как происходит вставка данных в кластеризованую таблицу, можно найти в ответах форума по этой ссылке.

Таким образом, для GUID мы имеем 4 варианта, которые стоит проанализировать в плане производительности: последовательный и непоследовательный GUID с генерацией на клиенте, и та же пара вариантов, но с генерацией на стороне базы. Остается вопрос, как получать последовательные GUID на клиенте? К сожалению, стандартной функции в .NET для этих целей нет, но ее можно сделать, воспользовавшись P/Invoke:

internal static class SequentialGuidUtils < public static Guid CreateGuid() < Guid guid; int result = NativeMethods.UuidCreateSequential(out guid); if (result == 0) < var bytes = guid.ToByteArray(); var indexes = new int[] < 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15 >; return new Guid(indexes.Select(i => bytes[i]).ToArray()); > else throw new Exception("Error generating sequential GUID"); > > internal static class NativeMethods

Обратите внимание на то, что без специальной перестановки байт, GUID нельзя отдавать. Идентификаторы получатся корректные, но с точки зрения SQL сервера — непоследовательные, поэтому никакого выигрыша по сравнению с «обычным» GUID даже теоретически не получится. К сожалению, ошибочный код приведен во многих источниках.

К списку остается добавить пятый вариант — автоинкрементный первичный ключ. Других вариантов у него нет, так как на клиенте его генерировать нормально не получится.

С вариантами определились, но есть еще один параметр, который следует учесть при написании теста — физический размер строк таблицы. Размер страницы данных в MS SQL — 8 килобайт. Записи близкого или даже большего размера могут показать более сильный разброс производительности для каждого из вариантов ключа, чем на порядок меньшие записи. Чтобы обеспечить возможность варьировать размер записи, достаточно добавить в каждую из тестовых таблиц NVARCHAR поле, которое затем заполнять нужным количеством символов (один символ в NVARCHAR поле занимает 2 байта).

Тестирование

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

  • Всего три серии тестов с длиной текстового поля в записи 80, 800 и 8000 байт соответственно (количество символов в тестовой программе будет в два раза меньше в каждом из случаев, так как один символ в NVARCHAR занимает два байта).
  • В каждой из серий — по 5 запусков, каждый из которых добавляет по 10000 записей в каждую из таблиц. По результатам каждого из запусков можно будет проследить зависимость времени вставки от количества строк, уже находящихся в таблице.
  • Перед началом каждой из серий таблицы полностью очищаются.

Автоинкремент — Основы реляционных баз данных

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

Автогенерация первичного ключа

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

Автогенерация работает по следующим принципам:

  • Внутри базы создается отдельный счетчик, который привязывается к каждой таблице
  • Счетчик увеличивается на единицу при вставке новой строки
  • Получившееся значение записывается в поле, которое помечается как автогенерируемое

Автогенерацию первичного ключа часто называют автоинкрементом (autoincrement). Что переводится как автоматическое увеличение и напоминает операцию инкремента из программирования ++.

До определенного момента механизм автоинкремента был реализован по-своему в каждой СУБД разными способами. Это создавало проблемы при переходе от одной СУБД к другой и усложняло реализацию программного слоя доступа к базе данных.

Эта функциональность добавлена в стандарт SQL:2003, то есть очень давно. И только в 2018 году PostgreSQL в версии 10 стал его поддерживать. Такой автоинкремент известен под именем GENERATED AS IDENTITY:

CREATE TABLE colors ( -- Одновременное использование и первичного ключа и автогенерации id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY, name varchar(255) ); INSERT INTO colors (name) VALUES ('Red'), ('Blue'); SELECT * FROM colors; 

Этот запрос вернет:

id name
1 Red
2 Blue

Если удалить запись с id равным двум и вставить еще одну запись, то значением поля id будет 3 . Автогенерация не связана с данными в таблице. Это отдельный счетчик, который всегда увеличивается. Так избегаются вероятные коллизии и ошибки, когда один и тот же идентификатор принадлежит сначала одной записи, а потом другой.

Вот его структура из документации:

 AS IDENTITY[ ( sequence_option ) ] 
  • Тип данных может быть SMALLINT, INT или BIGINT
  • GENERATED ALWAYS — не позволит добавлять значение самостоятельно, используя UPDATE или INSERT
  • GENERATED BY DEFAULT — в отличие от предыдущего варианта, этот вариант позволяет добавлять значения самостоятельно

PostgreSQL позволяет иметь более одного автогенерируемого поля на таблицу.

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Всё, что вы хотели знать об автоинкременте, но стеснялись спросить

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

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

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

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

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

Ниже показано, как создать таблицу с автоинкрементным столбцом в различных РСУБД.

CREATE TABLE example_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(50), age INT ); 

В этом примере столбец id определен как целочисленный тип данных с ключевым словом AUTOINCREMENT. Он также задан как первичный ключ таблицы.

MySQL и MariaDB:

CREATE TABLE example_table ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), age INT ); 

В этом примере столбец id определен как столбец с автоматическим инкрементом с помощью ключевого слова AUTO_INCREMENT. Он также установлен в качестве первичного ключа таблицы.

CREATE TABLE example_table ( id INT IDENTITY(1,1) PRIMARY KEY, name VARCHAR(50), age INT ); 

В этом примере столбец id определен как столбец идентичности с помощью ключевого слова IDENTITY. Он также задан как первичный ключ таблицы. Числа 1,1 задают начальное значение и значение инкремента автоинкрементного столбца. Например мы можем начать нумерацию с 100 и даждый раз прибавлять 10 (100, 110, 120, 130…) заменив IDENTITY(1,1) на IDENTITY(100, 10). Больше того, в качестве начала и шага можно использовать отрицательные числа. Попробуйте сами здесь.

CREATE SEQUENCE example_table_seq; CREATE TABLE example_table ( id NUMBER DEFAULT example_table_seq.nextval PRIMARY KEY, name VARCHAR2(50), age INT ); 

В этом примере сначала создается последовательность с помощью оператора CREATE SEQUENCE. Затем столбец id определяется как числовой тип данных со значением по умолчанию, равным следующему значению последовательности. Он также задается в качестве первичного ключа таблицы. В приведенном примере будет создана последовательность с начальным значением 1 и шагом 1. Если нам нужен другой вариант мы можем указать параметры INCREMENT BY и START WITH при создании

CREATE SEQUENCE example_table_seq INCREMENT BY 10 START WITH 10; 

PostgreSQL имеет несколько вариантов определения автоинкрементного столбца. Наиболее популярным является ключевое слово `SERIAL`.

CREATE TABLE example_table ( id SERIAL PRIMARY KEY, name VARCHAR(50), age INT ); 

В этом примере столбец id определен как последовательный тип данных, что эквивалентно столбцу с автоматическим инкрементом в других СУБД. Он также задан как первичный ключ таблицы.

Кроме того, для создания автоинкрементного столбца в PostgreSQL можно использовать как синтаксис `IDENTITY`, так и синтаксис `SEQUENCE`. Вот примеры создания автоинкрементного столбца с использованием синтаксиса IDENTITY и SEQUENCE в PostgreSQL:

CREATE TABLE example_table ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(50), age INT ); 
CREATE SEQUENCE example_table_seq; CREATE TABLE example_table ( id INT DEFAULT nextval('example_table_seq') PRIMARY KEY, name VARCHAR(50), age INT ); 

Как и в примере с Oracle DB, вариант с SEQUENCE предлагает возможности гибкой конфигурации автоинкремента в отличии от SERIAL

Чтобы вставить новую строку в таблицу с автоинкрементным столбцом, мы можем использовать оператор INSERT INTO, указав для всех столбцов, кроме автоинкрементного:

INSERT INTO example_table (name, age) VALUES ('John Smith', 30); 

Большинство диалектов SQL позволяет вставлять несколько строк в один запрос.

INSERT INTO example_table (name, age) VALUES ("Джейн Доу", 25), ('Боб Джонсон', 40), ('Элис Браун', 35); 

Если сейчас сделать выборку из таблицы Мы увидим что столбец `id` был автоматически заполнен последовательными числами:

SELECT * FROM example_table; 
+====+=============+=====+ | id | name | age | +====+=============+=====+ | 1 | Джейн Доу | 25 | | 2 | Боб Джонсон | 40 | | 3 | Элис Браун | 35 | +----+-------------+-----+ 

SQLite, MySQL & MariaDB позволяет вставлять произвольные значения в столбец с автоматическим увеличением и обновлять последовательность.

INSERT INTO example_table (id, name, age) VALUES (11, 'John Smith', 30); INSERT INTO example_table (name, age) VALUES ('Jane Doe', 25), ('Bob Johnson', 40), ('Alice Brown', 35); SELECT * FROM example_table; 

В приведенном выше примере мы вставляем ’John Smith’ с id = 11 и затем ещё три строки без указания значений `id`. Эти строки будут вставлены со следующими значениями последовательности 12, 13, 14 Проверьте это здесь.

+====+=============+=====+ | id | name | age | +====+=============+=====+ | 11 | John Smith | 30 | | 12 | Jane Doe | 25 | | 13 | Bob Johnson | 40 | | 14 | Alice Brown | 35 | +----+-------------+-----+ 

MS SQL Server по умолчанию не допускает такого волюнтаризма. Поэтому, если нам нужно вставить значение в столбец identity, мы должны установить IDENTITY_INSERT в нашей таблице:

SET IDENTITY_INSERT example_table ON INSERT INTO example_table (id, name, age) VALUES (11, 'John Smith', 30) SET IDENTITY_INSERT example_table OFF; 

Приведенный выше запрос вставляет запись с id = 11 и перемещает вверх счетчик для вставки последующих записей.

А как насчет Oracle? Эта DB позволяет вставлять значения в столбец id, без влияния на последовательности, поэтому мы можем вставлять строку с произвольным id, но это может привести к конфликту, как в следующем примере:

CREATE SEQUENCE example_table_seq; CREATE TABLE example_table ( id NUMBER DEFAULT example_table_seq.nextval PRIMARY KEY, name VARCHAR2(50), age NUMBER ); --вставляем строку с INTO example_table (id, name, age) VALUES (3, 'John Smith', 30); -- следующие запросы вставят строки с id 1 и 2 INSERT INTO example_table (name, age) VALUES ('Jane Doe', 25); -- 1 INSERT INTO example_table (name, age) VALUES ('Bob Johns', 40); -- 2 -- здесь получаем ошибку OCIStmtExecute: -- ORA-00001: unique constraint (0c7690dacb6b.SYS_C009760) violated INSERT INTO example_table (name, age) VALUES ('Alice Brown', 35); SELECT * FROM example_table; 

PostgreSQL с синтаксисом SERIAL или SEQUENCE для столбца с автоматическим увеличением показывают нам такое же поведение, как и Oracle (строка вставлена, последовательность не обновлена, ошибка при дублировании). Но при использовании синтаксиса IDENTITY вставка строки с произвольным идентификатором не допускается. Это ограничение может быть обойдено переопределением системного значения, как в следующем примере, но опять же это не обновляет последовательность и может вызвать конфликт.

CREATE TABLE example_table ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(50), age INT ); INSERT INTO example_table (id, name, age) OVERRIDING SYSTEM VALUE VALUES (3, 'John Smith', 30); INSERT INTO example_table (name, age) VALUES ('Jane Doe', 25); INSERT INTO example_table (name, age) VALUES ('Bob Johnson', 40); INSERT INTO example_table (name, age) VALUES ('Alice Brown', 35); SELECT * FROM example_table; 

Давайте двигаться дальше! Как изменить значение автоинкремента?

В SQLite можно сбросить значение автоматического увеличения для таблицы с помощью таблицы sqlite_sequence.

Таблица sqlite_sequence — это внутренняя таблица, используемая SQLite для отслеживания следующего значения автоинкремента для каждой таблицы в базе данных. Каждая строка в таблице sqlite_sequence представляет таблицу в базе данных, а в столбце seq хранится следующее значение автоматического увеличения для этой таблицы. Поэтому, когда нам нужно изменить его, просто попробуйте следующее:

UPDATE sqlite_sequence SET seq = 100 WHERE name = 'example_table'; INSERT INTO example_table (name, age) VALUES ('John Gold', 30); 

Приведенный выше код изменит значение автоприращения на 100, а следующая вставленная строка получит >

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

DELETE FROM sqlite_sequence WHERE name = 'example_table'; 

или установить его меньше максимального значения в автоматически увеличиваемом столбце, но после вставки новой строки sqlite_sequence будет исправлен автоматически. Просто посмотрите следующий фрагмент кода:

CREATE TABLE example_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(50), age INT ); INSERT INTO example_table (name, age) VALUES ('Jane Doe', 25), ('Bob Johnson', 40), ('Alice Brown', 35); SELECT * FROM sqlite_sequence WHERE name = 'example_table'; 

данный код вернет следующий результат:

+===============+=====+ | name | seq | +===============+=====+ | example_table | 3 | +---------------+-----+ 

Удалим его и убедимся что данные исчезли:

DELETE FROM sqlite_sequence WHERE name = 'example_table'; SELECT * FROM sqlite_sequence WHERE name = 'example_table'; 

но после следующей записи вставленное значение последовательности также будет восстановлено. Попробуйте здесь.

MySQL & MariaDB также позволяет изменять автоинкремент с помощью запроса ALTER TABLE, но оно не может быть меньше максимального значения в автоматически увеличенном столбце:

ALTER TABLE example_table AUTO_INCREMENT = 100; 

В Oracle можно изменить последовательность с помощью инструкции ALTER SEQUENCE. Этот оператор позволяет изменять характеристики последовательности, такие как минимальное и максимальное значения, шаг и начальное значение. Если необходимо внести более существенные изменения в последовательность, например изменить ее тип данных или полностью удалить ее, может потребоваться удалить и пересоздать последовательность.

DROP SEQUENCE example_table_seq; CREATE SEQUENCE example_table_seq INCREMENT BY 1 START WITH 100; 

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

В PostgreSQL не имеет значения, как создается столбец с автоматическим увеличением (SERIAL, GENERATED ALWAYS AS IDENTITY или с помощью CREATE SEQUENCE), поскольку для изменения последовательности можно использовать инструкцию ALTER SEQUENCE. Однако данная СУБД не гарантирует отсутствие конфликтов после модификации последовательности, поэтому важно проявлять осторожность.

ALTER SEQUENCE example_table_id_seq RESTART WITH 100; 

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

Эта функция поддерживается большинством СУБД и может быть легко реализована с использованием разного синтаксиса в каждой базе данных. SQLite, MySQL и MariaDB поддерживают столбцы с автоинкрементом, а также PostgreSQL и Oracle, которые также допускают более продвинутые параметры, такие как последовательности и последовательные типы данных.

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

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

Если Вам понравилась статья, Вы можете поддержать автора.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *