Как правильно зарезервировать значения идентификаторов для использования в базе данных? - PullRequest
1 голос
/ 16 июня 2010

У нас есть некоторый код, в котором нам нужно поддерживать собственный столбец идентификаторов (PK) в SQL.У нас есть таблица, в которую мы массово вставляем данные, но мы добавляем данные в связанные таблицы до того, как массовая вставка будет выполнена, поэтому мы не можем использовать столбец IDENTITY и заранее узнать значение.

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

Я использую модель сущности ADO.net.Как бы я занялся «резервированием» диапазона идентификаторов для использования, и когда этот диапазон закончится, возьмите новый блок для использования и гарантируйте, что этот же диапазон не будет использоваться.

Ответы [ 7 ]

3 голосов
/ 17 июня 2010
  • использовать более универсальный тип данных уникального идентификатора, например UNIQUEIDENTIFIER (UUID) вместо INTEGER.В этом случае вы можете создать его на стороне клиента, передать его SQL и не беспокоиться об этом.Недостатком является то, что, конечно, размер этого поля.
  • создает простую таблицу в базе данных CREATE TABLE ID_GEN (ID INTEGER IDENTITY), и используйте ее как factory, чтобы дать вам идентификаторы.В идеале вы должны создать хранимую процедуру (или функцию), которой вы передадите нужное количество идентификаторов.Затем хранимая процедура вставит это количество строк (пустых) в эту таблицу ID_GEN и вернет вам все новые ID, которые вы можете использовать в своем коде.Очевидно, что ваши исходные таблицы больше не будут иметь IDENTITY.
  • создаст ваш собственный вариант ID_Factory выше.

Я бы выбрал простоту (UUID), еслииначе вы не ограничены.

3 голосов
/ 16 июня 2010

Если возможно изменить структуру таблицы, то, возможно, вместо этого используйте uniqueidentifier для PK вместе с newid() [SQL] или Guid.NewGuid() [C #] в коде генерации строки.

С Guid.NewGuid () doco:

Существует очень низкая вероятность того, что значение нового Guid равно нулю или равно любому другому Guid.

2 голосов
/ 17 июня 2010

Почему вы используете ADO.net Entity Framework для работы, которая походит на работу ETL? (См. Критический анализ ADO.NET Entity Framework и ORM в целом ниже. Он является бесплатным).

Зачем вообще использовать целые? Использование uniqueidentifier решит проблему «несколько экземпляров запущенного приложения».

Использование uniqueidentifier в качестве столбца по умолчанию будет медленнее, чем использование int IDENTITY ... для создания guid требуется больше времени, чем для int. Guid также будет больше (16 байт), чем int (4 байта). Попробуйте сначала, и если это приведет к приемлемой производительности, запустите его.

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

Пример кода TSQL:

CREATE TABLE testinsert
 (
  date_generated datetime   NOT NULL DEFAULT GETDATE(), 
  guid   uniqueidentifier NOT NULL, 
  TheValue  nvarchar(255)  NULL
 )
GO

CREATE TABLE guids 
 (
  guid   uniqueidentifier NOT NULL DEFAULT newid(), 
  used   bit     NOT NULL DEFAULT 0, 
  date_generated datetime   NOT NULL DEFAULT GETDATE(), 
  date_used  datetime   NULL
 )
GO

CREATE PROCEDURE GetGuid
 @guid uniqueidentifier OUTPUT
AS
BEGIN
 SET NOCOUNT ON
 DECLARE @return int = 0

 BEGIN TRY
  BEGIN TRANSACTION
   SELECT TOP 1 @guid = guid FROM guids WHERE used = 0

   IF @guid IS NOT NULL
    UPDATE guids
    SET 
     used = 1, 
     date_used = GETDATE()
    WHERE guid = @guid
   ELSE
    BEGIN
     SET @return = -1
     PRINT 'GetGuid Error: No Unused guids are available'
    END
  COMMIT TRANSACTION
 END TRY

 BEGIN CATCH
  SET @return = ERROR_NUMBER() -- some error occurred
  SET @guid = NULL
  PRINT 'GetGuid Error: ' + CAST(ERROR_NUMBER() as varchar) + CHAR(13) + CHAR(10) + ERROR_MESSAGE()
  ROLLBACK
 END CATCH

 RETURN @return
END
GO

CREATE PROCEDURE InsertIntoTestInsert
 @TheValue nvarchar(255)
AS
 BEGIN
  SET NOCOUNT ON
  DECLARE @return int = 0

  DECLARE @guid uniqueidentifier
  DECLARE @getguid_return int

  EXEC @getguid_return = GetGuid @guid OUTPUT

  IF @getguid_return = 0 
   BEGIN
    INSERT INTO testinsert(guid, TheValue) VALUES (@guid, @TheValue)
   END
  ELSE
   SET @return = -1

  RETURN @return
 END
GO

-- generate the guids
INSERT INTO guids(used) VALUES (0)
INSERT INTO guids(used) VALUES (0)

--Insert data through the stored proc
EXEC InsertIntoTestInsert N'Foo 1'
EXEC InsertIntoTestInsert N'Foo 2'
EXEC InsertIntoTestInsert N'Foo 3' -- will fail, only two guids were created

-- look at the inserted data
SELECT * FROM testinsert

-- look at the guids table
SELECT * FROM guids

Забавный вопрос ... как вы сопоставляете это с Entity Framework ADO.Net?

Это классическая проблема, которая началась в первые дни ORM (Object Relational Mapping).

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

Таким образом, все обрезают это, и люди пишут запросы непосредственно к нормализованной базе данных, которую они не понимают ... таким образом, потребность в ORM, в данном случае, ADO.NET Entity Framework.

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

0 голосов
/ 17 июня 2010

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

Этот способ безопасен для использования в приложении, когда многие пользователи вставляют данные одновременно. Любые другие способы, кроме GUID, не являются многопользовательскими.

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

Если вы используете ADO.NET Entity Framework, вам, вероятно, не стоит беспокоиться о генерации идентификатора: EF генерирует идентификаторы самостоятельно, просто пометьте первичный ключ сущности как IsDbGenerated = true.

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

0 голосов
/ 17 июня 2010

Алгоритм Hi / Lo может быть интересен вам:

Что такое алгоритм Hi / Lo?

0 голосов
/ 17 июня 2010

У меня много дочерних таблиц, которые вы, возможно, не захотите менять PK. PLus целочисленного filedsa вряд ли будет работать лучше в соединениях. Но вы все равно можете добавить поле GUID и заполнить его в массовой вставке предварительно сгенерированными значениями. Затем вы можете оставить вставку идентификатора в одиночку (почти всегда плохая идея отключить ее) и использовать предварительно сгенерированные значения GUID, чтобы вернуть значения идентификаторов, которые вы только что вставили для вставки в дочерние таблицы.

Если вы используете обычную вставку на основе набора (одну с предложением select вместо предложения values) вместо массовой вставки, вы можете использовать предложение output, чтобы вернуть идентификаторы для строк, если вы используете SQL Server 2008

0 голосов
/ 17 июня 2010

Два клиента могут зарезервировать один и тот же блок идентификаторов.

Не существует решения, кроме сериализации ваших вставок путем блокировки.

См. Советы по блокировке в MSDN.

...