2013-11-08 2 views
3

Мы собираемся перенести наш сайт на windows azure и столкнулись с проблемой sql-azure. Они не поддерживают свойство прироста идентификатора из-за оптимизации производительности. Следовательно, мы должны разработать свою собственную логику для поддержки существующей функции в создании последовательной идентификации.SQL-симуляция Auto increment field

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

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

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

+1

Вы знаете, что это будет очень плохо масштабироваться. Существует причина, которая обычно применяется в rdbms. Лучшим подходом может быть использование чего-то типа guid. – steve

+0

@steve Спасибо за предложение –

+0

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

ответ

4

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

CREATE PROCEDURE [dbo].[GetNextID](
    @IDName nvarchar(255) 
) 
AS 
BEGIN 
    /* 
     Description: Increments and returns the LastID value from tblIDs for a given IDName 
     Author:   Max Vernon/Mike Defehr 
     Date:   2012-07-19 

    */ 

    DECLARE @Retry int; 
    DECLARE @EN int, @ES int, @ET int; 
    SET @Retry = 5; 
    DECLARE @NewID int; 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 
    SET NOCOUNT ON; 
    WHILE @Retry > 0 
    BEGIN 
     BEGIN TRY 
      UPDATE dbo.tblIDs 
      SET @NewID = LastID = LastID + 1 
      WHERE IDName = @IDName; 

      IF @NewID IS NULL 
      BEGIN 
       SET @NewID = 1; 
       INSERT INTO tblIDs (IDName, LastID) VALUES (@IDName, @NewID); 
      END 
      SET @Retry = -2; /* no need to retry since the operation completed */ 
     END TRY 
     BEGIN CATCH 
      IF (ERROR_NUMBER() = 1205) /* DEADLOCK */ 
       SET @Retry = @Retry - 1; 
      ELSE 
       BEGIN 
       SET @Retry = -1; 
       SET @EN = ERROR_NUMBER(); 
       SET @ES = ERROR_SEVERITY(); 
       SET @ET = ERROR_STATE() 
       RAISERROR (@EN,@ES,@ET); 
       END 
     END CATCH 
    END 
    IF @Retry = 0 /* must have deadlock'd 5 times. */ 
    BEGIN 
     SET @EN = 1205; 
     SET @ES = 13; 
     SET @ET = 1 
     RAISERROR (@EN,@ES,@ET); 
    END 
    ELSE 
     SELECT @NewID AS NewID; 
END 
GO 

(Для полноты, вот таблица, связанная с сохраненным прок)

CREATE TABLE [dbo].[tblIDs] 
(
    IDName nvarchar(255) NOT NULL, 
    LastID int NULL, 
    CONSTRAINT [PK_tblIDs] PRIMARY KEY CLUSTERED 
    (
     [IDName] ASC 
    ) WITH 
    (
     PAD_INDEX = OFF 
     , STATISTICS_NORECOMPUTE = OFF 
     , IGNORE_DUP_KEY = OFF 
     , ALLOW_ROW_LOCKS = ON 
     , ALLOW_PAGE_LOCKS = ON 
     , FILLFACTOR = 100 
    ) 
); 
GO 

Каждый раз, когда вы хотите, чтобы получить новый идентификатор для использования в основной таблице, вы просто EXEC GetNextID 'TableIDField';

+0

Это похоже на приятное решение, +1. Не приведет ли это к нарушению порядка в некоторых случаях? Вы можете получить новый идентификатор, но затем придерживаться его, выполняя какую-то другую работу, а другой процесс, который получил свой идентификатор позже, может сначала вставить основную запись. – Szymon

+0

Правда, однако, если поле, в которое вы вставляете идентификатор, является первичным ключом, порядок все равно будет исправлен. –

+0

Правильно. Это, вероятно, довольно незначительная вещь, если вы не полагаетесь на порядок, который всегда увеличивается. – Szymon

1

Вы можете достичь нечто подобное таким образом

  1. Начало транзакции (уровень изоляции транзакций чтения совершено).

  2. Выберите максимальное действующее значение с исключительной блокировкой в ​​запросе SELECT ((XLOCK)).

  3. Обновление значение, которое должно быть увеличено на 1.

  4. Совершить транзакцию.

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


Как указывает Max Vernon ниже, этот подход может не подходит для большого объема, высоко параллельных систем. Это может также привести к взаимоблокировкам (хотя возможность взаимоблокировок не ограничивается этим решением).

+1

Спасибо, шиммон, я попробую это. –

+0

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

+0

Нет, откат транзакции освободит блокировку. – Szymon

1

Одним из решений является использование OUTPUT clause:

-- Setup 
CREATE TABLE dbo.IdentityColumn 
(
    ColumnID INT PRIMARY KEY, 
    SeedValue INT NOT NULL, 
    IncrementValue INT NOT NULL, 
    CurrentValue INT NULL 
); 
GO 
CREATE PROCEDURE dbo.GetNextIndentityValue 
(
    @ColumnID INT, 
    @HowManyIDs INT, -- Ussualy you will be intersted in just one value 
    @CurrentValue INT OUTPUT 
) 
AS 
BEGIN 
    IF @HowManyIDs <= 0 
    BEGIN 
     RAISERROR('@HowManyIDs: invalid value', 16, 1); 
    END 
    DECLARE @Output TABLE(CurrentValue INT NULL); 
    UPDATE dbo.IdentityColumn 
    SET  CurrentValue = ISNULL(CurrentValue, SeedValue - 1) + IncrementValue * @HowManyIDs 
    OUTPUT inserted.CurrentValue INTO @Output (CurrentValue) 
    WHERE ColumnID = @ColumnID; 
    IF @@ROWCOUNT <> 1 
    BEGIN 
     RAISERROR('@ColumnID: invalid value', 16, 1); 
    END 
    SELECT @CurrentValue = CurrentValue FROM @Output; 
END 
GO 
INSERT dbo.IdentityColumn (ColumnID, SeedValue, IncrementValue) VALUES (1, 1, 1); 
INSERT dbo.IdentityColumn (ColumnID, SeedValue, IncrementValue) VALUES (2, 101, 2); 
GO 

-- Test #1 
DECLARE @LastID INT; 
EXEC dbo.GetNextIndentityValue @ColumnID=1, @HowManyIDs=1, @[email protected] OUTPUT; 
SELECT @LastID AS [LastID #1]; --> 1 
GO 
-- Test #2 
DECLARE @LastID INT; 
EXEC dbo.GetNextIndentityValue @ColumnID=1, @HowManyIDs=4, @[email protected] OUTPUT; 
SELECT @LastID AS [LastID #2]; --> 5 
GO 

-- Test #3 
DECLARE @LastID INT; 
EXEC dbo.GetNextIndentityValue @ColumnID=2, @HowManyIDs=1, @[email protected] OUTPUT; 
SELECT @LastID AS [LastID #3]; --> 102 
GO 
-- Test #4 
DECLARE @LastID INT; 
EXEC dbo.GetNextIndentityValue @ColumnID=2, @HowManyIDs=1, @[email protected] OUTPUT; 
SELECT @LastID AS [LastID #4]; --> 104 
GO 
0

** Не Sql Ответ ** В прошлом я решил подобные ситуации, передав идентификатор во вставку.

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

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

Эта функция очень похожа на очередь.

+0

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