zoukankan      html  css  js  c++  java
  • 【数据库设计】“Max加一”生成主键的注意点

    Sql Server数据库的主键设计有多种,比如经典的自增长,欢乐的guid,按照时间生成id(有通过程序生成的方式,还有一种也是通过数据库时间和随机数生成),按照业务模型组合生成有意义的id等等。最近项目中接触到一种模拟自增长自动编号主键的方式,即“Max加一”。

    Max加一的原理看上去和自增长是相似的,表的唯一主键也设计成数字型(如bigint)的,只是把自动增长去掉了(表设计器标识规范一栏,“是标识”的选项选择否即可)。在Insert记录的时候,通常情况下的流程大致是这样的:读取当前表的Max主键值后加一,然后按照传递的相关参数,显式插入主键及其他列的值。这种生成主键方式的一个最显著的优点是可以按照自己的规则生成主键。比如有如下生成主键的用户自定义的存储过程usp_GetNewID:

    if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].usp_GetNewID') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
    drop procedure [dbo].usp_GetNewID
    GO
    
    CREATE PROCEDURE [dbo].usp_GetNewID
    @tableName nvarchar(30),  --表名
    @columnName nvarchar(30), --字段名
    @NewId int output          --Max(ID)生成的新ID    
    AS
    BEGIN
    
    DECLARE @MaxId bigint
    DECLARE @MaxIdTemp bigint
    DECLARE @SQL nvarchar(500)
    DECLARE @SQLDBId nvarchar(8)
    
    set @SQLDBId='10'
    
    --获取原最大ID
    set @SQL ='SELECT @Mymaxid= isnull(MAX('+@columnName+'),101) From '+ @tableName;
    -- select @MaxId 
    
    if @@error=0
    begin
        exec sp_executesql @SQL,N'@Mymaxid bigint output',@MaxId output
    end
    
    --生成新ID
    if @@error=0
    begin 
        select @MaxIdTemp=SUBSTRING(cast(@MaxId as nvarchar),3,100)+1; 
    
    end
    
    if @@error=0
    begin
        set @NewId=@SQLDBId+cast(@MaxIdTemp as nvarchar)
    end
    else
    begin
        set @NewId=-1
    end
    
    END

    通过将@SQLDBId='10'和set @NewId=@SQLDBId+cast(@MaxIdTemp as nvarchar)这种方式的组合,我们可以控制不同的数据库服务器(或者不同的库)生成的主键都有规律可循,比如第一台服务器生成的id都以10开头,第二台都以20开头,依此类推,这样多少有利于数据库的分布式管理。

    下面简单说说这种方式的两个重大缺陷:

    1、效率问题

    虽然主键有聚集索引,但是当我们的数据表数据达到一定数量级的时候(比如千万),那么通过聚合函数Max取值肯定会有不小的代价,这样显然会影响一点效率。但是到底效率几何,和自增长的性能比较又如何?这个我真的还没有这方面的测试数据,如果有童鞋有这方面的经验请不吝赐教,恳求告知。

    UPDATE】:根据今天的性能测试,在表已有1百万数据基础上,继续插入数据,每次插入10000条记录,自增和Max加一这种方式的时间相差不足1秒,总体上自增长的方式会稍快一点,但是并不明显,在可接受范围内。测试结果见下图:

    TestDb

    2、并发插入问题

    当我们在程序中有顺序的先后插入数据的时候,这个问题当然不会发生。但是在大部分应用中,经常会并发处理一些数据,这个时候通过Max加一的方式就会造成插入上的并发问题。因为如果同时有两个或者多个插入请求读到相同的MAX值加一以后,在插入的时候就会发生插入重复主键的错误。

    我们可以做一个简单的测试:

    (1)、添加用户的存储过程usp_AddUser

    if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].usp_AddUser') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
    drop procedure [dbo].usp_AddUser
    GO
    
    CREATE PROCEDURE [dbo].usp_AddUser
    AS
    BEGIN
    
    DECLARE @Id bigint
    EXEC  usp_GetNewID 'Users','Id',@Id OUT 
    SELECT @Id 
    
    if @@ERROR=0
    BEGIN
    INSERT INTO Users
    (
        Id,
        Name
    )
    VALUES
    (
       @Id,
       'jeff wong'
    )    
    END
    
    END

    用户表简单设计成有Id和Name两个字段,插入的时候,Name的值不受任何干扰,固定为”jeff wong”。

    (2)、然后在应用程序中调用如下:

           static void Main(string[] args)
            {
                int counter = 2000;
                Action action = null;
                for (int i = 0; i < counter; i++)
                {
                    action = AddUser;
    
                    action.BeginInvoke((a) =>
                     {
                         Action method = a.AsyncState as Action;
                         method.EndInvoke(a);
                     }, action);
    
                }
    
                Console.Read();
    
            }
    
            private static void AddUser()
            {
                try
                {
                    using (var conn = new SqlConnection(sqlConnString))
                    {
                        SqlCommand cmd = new SqlCommand("usp_AddUser", conn);
                        cmd.CommandType = CommandType.StoredProcedure;
                        conn.Open();
                        int result = cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
    

    在今晚本地的几组测试中,无一例外地都抛出了插入重复主键的异常。这个问题在这几天的一个数据同步程序中竟然没有发现,原因就是当时数据库没有或者很少符合条件的需要同步的数据。当然现在所有同步都已经改成通过在存储过程中利用游标顺序处理,这样就合理地解决掉并发插入问题了。

    最后,我感觉主键的生成选择还有很多东西可以挖掘,有一些知识可以拿过来深入讨论一下,比如自增长是如何控制并发插入的,诸如此类,欢迎您的意见和建议。

  • 相关阅读:
    通用的web系统数据导出功能设计实现(导出excel2003/2007 word pdf zip等)
    DALSA Coreco
    环境变量之执行文件路径的变量PATH
    命令与文件的查询
    软件开发工具GCC
    权限与命令之间的关系
    Linux防火墙
    网络管理
    分区及格式化
    VMware Workstation的网络连接方式:NAT、桥接和Host Only
  • 原文地址:https://www.cnblogs.com/jeffwongishandsome/p/2102020.html
Copyright © 2011-2022 走看看