如何正确保留数据库中使用的标识值?

时间:2010-06-16 19:46:26

标签: c# sql identity ado.net-entity-data-model

我们有一些代码需要在SQL中维护自己的身份(PK)列。我们有一个表,我们在其中批量插入数据,但是在批量插入完成之前我们将数据添加到相关表中,因此我们不能使用IDENTITY列并预先找出值。

当前代码选择字段的MAX值并将其递增1.虽然我们的应用程序的两个实例同时运行的可能性非常小,但它仍然不是线程安全的(不是提到它每次都进入数据库。)

我正在使用ADO.net实体模型。我将如何“保留”一系列id来使用,当该范围用完时,抓住一个新的块来使用,并保证不会使用相同的范围。

7 个答案:

答案 0 :(得分:3)

如果更改表格的结构是可行的,那么可能在您的行代中使用uniqueidentifier代替PK newid() [SQL]或Guid.NewGuid() [C#]代码。

来自Guid.NewGuid() doco:

  

新Guid的值很可能全为零或等于任何其他Guid。

答案 1 :(得分:3)

  • 使用更多通用唯一标识符数据类型,例如UNIQUEIDENTIFIERUUID)而不是INTEGER。在这种情况下,您基本上可以在客户端创建它,将其传递给SQL,而不必担心它。当然,缺点是这个领域的规模。
  • 在数据库CREATE TABLE ID_GEN (ID INTEGER IDENTITY)中创建一个简单表,并将其用作factory来为您提供标识符。理想情况下,您将创建一个存储过程(或函数),您将传递所需的标识符数。然后,存储过程会将此行数(空)插入此ID_GEN表中,并返回所有新的ID,您可以在代码中使用它们。显然,您的原始表格将不再具有IDENTITY
  • 创建您自己的ID_Factory上面的变体。

如果你不受限制,我会选择简单性(UUID)。

答案 2 :(得分:2)

为什么使用ADO.net Entity Framework执行听起来像ETL的工作? (请参阅下面对ADO.NET实体框架和ORM的批评。它是免费的。)

为什么要使用int?使用uniqueidentifier可以解决“应用程序运行的多个实例”问题。

使用uniqueidentifier作为列默认值比使用int IDENTITY要慢...生成guid比使用int需要更多时间。 guid也比int(4字节)更大(16字节)。首先尝试这个,如果它导致可接受的性能,请运行它。

如果通过在每一行上生成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

有趣的问题是......你如何将其映射到ADO.Net的实体框架?

这是一个在ORM(对象关系映射)早期开始的经典问题。

如果您使用关系数据库最佳实践(永远不允许直接访问基表,只允许通过视图和存储过程进行数据操作),那么您添加了人数(有能力并且不仅愿意编写数据库模式的人,还有构成API的所有视图和存储过程,并将延迟(实际编写此东西的时间)引入项目。

所以每个人都削减了这一点,人们直接针对规范化的数据库编写查询,而这些数据库是他们不理解的......因此需要ORM,在本例中是ADO.NET实体框架。

ORM吓得我吓坏了。我已经看到ORM工具生成非常低效的查询,这会使性能不佳的数据库服务器瘫痪。在最终用户等待和DBA挫败感中,程序员工作效率得到了提升。

答案 3 :(得分:0)

两个客户端可以保留相同的id块。

没有解决方案没有通过锁定来序列化插入。

请参阅MSDN中的Locking Hints

答案 4 :(得分:0)

我有很多子表你可能不想改变PK。 PLUS整数filedsa relikely在连接中表现更好。但您仍然可以添加GUID字段并使用预生成的值在批量插入中填充它。然后你可以单独留下身份插入(几乎是一个坏主意将其关闭)并使用你预先生成的GUID值来获取刚刚插入插入子表的Identity值。

如果使用常规的基于set的插入(一个使用select子句而不是values子句)而不是批量插入,那么如果使用SQL Server,则可以使用output子句获取行的标识2008。

答案 5 :(得分:0)

您可能会对Hi / Lo算法感兴趣:

What's the Hi/Lo algorithm?

答案 6 :(得分:0)

最常见的解决方案是生成永远不会跨越数据库标识符的客户端标识符 - 通常是负值,然后在插入时用数据库生成的标识符更新标识符。

这种方式在应用程序中使用是安全的,许多用户同时插入数据。除GUID之外的任何其他方式都不是多用户安全的。

但是如果你有这种罕见的情况,在实体保存到数据库之前需要知道实体的主键,并且不可能使用GUID,你可以使用防止标识符重叠的标识符生成算法。 最简单的是为每个连接的客户端分配唯一的标识符前缀,并将其添加到此客户端生成的每个标识符之前。

如果您使用的是ADO.NET实体框架,您可能不必担心标识符生成:EF自己生成标识符,只需将实体的主键标记为IsDbGenerated = true。

严格地说,实体框架作为其他ORM不需要对象的标识符也没有保存到数据库,它是用于正确操作新实体的对象参考。实际主键值仅在更新/删除实体时以及在更新/删除/插入引用新实体的实体时是必需的,e.i。在实际主键值即将写入数据库的情况下。如果实体是新的,则在新实体未保存到数据库之前,不可能保存引用新实体的其他实体,并且ORM维护实体保存的特定顺序,并将参考映射考虑在内。