1

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

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

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

4

7 回答 7

3

如果更改表的结构可行,那么可能在行生成代码中将auniqueidentifiernewid()[SQL] 或[C#] 一起用于 PK。Guid.NewGuid()

来自Guid.NewGuid() 文档

新 Guid 的值全为零或等于任何其他 Guid 的概率非常低。

于 2010-06-16T19:56:30.573 回答
3
  • 使用更通用的唯一标识符数据类型,如UNIQUEIDENTIFIER( UUID) 而不是INTEGER. 在这种情况下,您基本上可以在客户端创建它,将它传递给SQL并且不必担心它。缺点当然是这个字段的大小。
  • 在数据库中创建一个简单的表CREATE TABLE ID_GEN (ID INTEGER IDENTITY),并将其用作为factory您提供标识符。理想情况下,您将创建一个存储过程(或函数),您将向其传递所需的标识符数量。然后,存储过程会将这个数量的行(空)插入到这个ID_GEN表中,并返回所有新ID的,你可以在你的代码中使用。显然,您原来的表将不再有IDENTITY
  • 创建您自己的ID_Factory上述变体。

UUID如果您不受其他限制,我会选择简单 ( )。

于 2010-06-16T20:02:01.387 回答
2

为什么要使用 ADO.net Entity Framework 来做听起来像 ETL 的工作?(请参阅下面对 ADO.NET Entity Framework 和 ORM 的一般评论。它是免费的)。

为什么要使用整数?使用唯一标识符将解决“应用程序运行的多个实例”问题。

使用 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 的挫折中失去了程序员生产力所获得的东西。

于 2010-06-16T21:11:17.783 回答
0

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

如果您使用常规的基于集合的插入(带有 select 子句而不是 values 子句的插入)而不是批量插入,如果您使用的是 SQL Server 2008,则可以使用 output 子句来获取行的标识。

于 2010-06-16T20:08:58.957 回答
0

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

什么是 Hi/Lo 算法?

于 2010-06-16T20:12:19.463 回答
0

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

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

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

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

严格来说,实体框架和其他 ORM 一样,不需要对象的标识符,还没有保存到数据库中,它足以正确操作新实体的对象引用。实际主键值仅在更新/删除实体和更新/删除/插入引用新实体的实体时需要,例如在实际主键值即将写入数据库的情况下。如果实体是新的,则在新实体未保存到数据库之前,不可能保存引用新实体的其他实体,并且 ORM 维护实体保存的特定顺序,其中考虑了引用映射。

于 2010-06-16T20:32:33.927 回答
0

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

除了通过锁定序列化插入之外,没有其他解决方案。

请参阅MSDN 中的锁定提示

于 2010-06-16T20:01:22.587 回答