SQL模拟自动增量字段

时间:2013-11-08 04:58:37

标签: sql sql-server transactions azure-sql-database

我们即将把我们的网站移到windows azure并面临sql-azure的问题。由于性能优化,它们不支持身份增量属性。 因此,我们必须设计自己的逻辑来支持生成顺序标识的现有功能。

因此,为了生成唯一的顺序值,我们从表的ID字段中获取max(value)并将其递增1以插入新记录。

我们现在必须管理并发事务,因此使用隔离级别进行检查。 但是,没有一个隔离级别锁定表以避免在并发下读取最大值。

关于锁定表或有关此方法的注释的任何帮助都将非常有用。如果你们中的一些人已经以一种很好的方式克服了这个问题,那么请你分享一下.. 谢谢你的帮助。

4 个答案:

答案 0 :(得分: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

每次要获取要在主表中使用的新ID时,只需EXEC GetNextID 'TableIDField';

答案 1 :(得分:1)

你可以通过这种方式实现类似的目标

  1. 启动事务(处于读提交事务隔离级别)。

  2. SELECT查询((XLOCK)提示)中选择具有排他锁的最大现有值。

  3. 更新要增加1的值。

  4. 提交交易。

  5. 将select锁置入select语句将锁定所有其他想要同时读取ID的进程。他们必须等到交易完成后才会在交易提交时读取新值。


    正如Max Vernon所指出的,这种方法可能不适用于高容量,高度并发的系统。它也可能导致死锁(尽管死锁的可能性不限于此解决方案)。

答案 2 :(得分: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, @CurrentValue=@LastID OUTPUT;
SELECT @LastID AS [LastID #1]; --> 1
GO
-- Test #2
DECLARE @LastID INT;
EXEC dbo.GetNextIndentityValue @ColumnID=1, @HowManyIDs=4, @CurrentValue=@LastID OUTPUT;
SELECT @LastID AS [LastID #2]; --> 5
GO

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

答案 3 :(得分:0)

**不是Sql答案** 在过去,我通过将ID传递给插入来解决了类似的情况。

为了确保它是正确的id,你有一个暴露的服务在一个盒子上运行,它只返回一个内存中的整数,在每个请求后自动递增。

如果服务中断,请从表中获取最大ID,递增并再次开始分发Id。

此功能与队列非常相似。