我们即将把我们的网站移到windows azure并面临sql-azure的问题。由于性能优化,它们不支持身份增量属性。 因此,我们必须设计自己的逻辑来支持生成顺序标识的现有功能。
因此,为了生成唯一的顺序值,我们从表的ID字段中获取max(value)并将其递增1以插入新记录。
我们现在必须管理并发事务,因此使用隔离级别进行检查。 但是,没有一个隔离级别锁定表以避免在并发下读取最大值。
关于锁定表或有关此方法的注释的任何帮助都将非常有用。如果你们中的一些人已经以一种很好的方式克服了这个问题,那么请你分享一下.. 谢谢你的帮助。
答案 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)
你可以通过这种方式实现类似的目标
启动事务(处于读提交事务隔离级别)。
在SELECT
查询((XLOCK)
提示)中选择具有排他锁的最大现有值。
更新要增加1的值。
提交交易。
将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。
此功能与队列非常相似。