代码之家  ›  专栏  ›  技术社区  ›  esac

如何正确地保留标识值以便在数据库中使用?

  •  1
  • esac  · 技术社区  · 14 年前

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

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

    我正在使用ADO.NET实体模型。我该如何“保留”一个要使用的ID范围,当该范围用完时,获取一个要使用的新块,并确保不会使用相同的范围。

    7 回复  |  直到 13 年前
        1
  •  3
  •   van    14 年前
    • 使用更通用的唯一标识符数据类型,如 UNIQUEIDENTIFIER ( UUID )而不是 INTEGER . 在这种情况下,您基本上可以在客户端创建它,并将其传递给 SQL 不用担心。缺点当然是这个领域的规模。
    • 在数据库中创建一个简单表 CREATE TABLE ID_GEN (ID INTEGER IDENTITY) ,并将其用作 factory 给你标识符。理想情况下,您将创建一个存储过程(或函数),向其传递所需的标识符数量。然后,存储过程会将此行数(空)插入 ID_GEN 桌上还有新的 ID ,可以在代码中使用。显然,您的原始表将没有 IDENTITY 不再。
    • 创建自己的变体 ID_Factory 上面。

    我会选择简单( UUID )如果你不受其他约束。

        2
  •  3
  •   Reddog    14 年前

    如果改变表的结构是可行的,那么可以使用 uniqueidentifier 对于pk和 newid() [SQL]或 Guid.NewGuid() 行生成代码中的[C]。

    Guid.NewGuid() 多科:

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

        3
  •  2
  •   Marc Jellinek    13 年前

    为什么要使用ADO.NET实体框架来执行听起来像ETL的工作?(见下文对ADO.NET实体框架和ORM的评论。它是免费的)。

    为什么要使用ints?使用唯一标识符可以解决“正在运行的应用程序的多个实例”问题。

    将uniqueidentifier用作列默认值将比使用int标识慢…生成一个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工具生成的查询效率极低,这会使数据库服务器性能下降。最终用户的等待和DBA的挫折使程序员的工作效率下降。

        4
  •  0
  •   Bill Karwin    14 年前

    两个客户机可以保留同一个ID块。

    除了通过锁定对插入进行序列化之外,没有任何解决方案。

    Locking Hints 在MSDN中。

        5
  •  0
  •   HLGEM    14 年前

    我想你有很多子表,你可能不想更改pk。加上整数filedsa relikley可以更好地执行连接。但是您仍然可以添加一个guid字段,并用预先生成的值将其填充到大容量插入中。然后,您可以将标识插入单独使用(关闭它几乎是个坏主意),并使用预先生成的guid值来返回刚刚为插入子表而插入的标识值。

    如果使用基于规则集的插入(其中一个使用select子句而不是values子句)而不是大容量插入,那么如果使用SQL Server 2008,则可以使用output子句返回行的标识。

        6
  •  0
  •   Community Ramakrishna.p    7 年前

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

    What's the Hi/Lo algorithm?

        7
  •  0
  •   STO    14 年前

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

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

    但是,如果在将实体保存到数据库之前需要知道实体的主键,并且不可能使用guid,那么您可能会使用防止标识符重叠的标识符生成算法。 最简单的方法是为每个连接的客户机分配一个唯一的标识符前缀,并将其预先发送给这个客户机生成的每个标识符。

    如果您使用的是ADO.NET实体框架,那么您可能不必担心标识符的生成:ef本身生成标识符,只需将实体的主键标记为isdbgenerated=true。

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