【数据库设计】“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());
            }
        }

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

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







本文转自JeffWong博客园博客,原文链接:http://www.cnblogs.com/jeffwongishandsome/archive/2011/07/09/2102020.html,如需转载请自行联系原作者

目录
相关文章
|
9月前
|
SQL 存储 Oracle
Oracle数据库中日期的操作、主键自增与分页查询
Oracle数据库中日期的操作、主键自增与分页查询
79 0
|
5月前
|
Oracle 关系型数据库 数据库
在Flink CDC中,使用Oracle 11g数据库的NUMBER类型作为主键
在Flink CDC中,使用Oracle 11g数据库的NUMBER类型作为主键
48 1
|
7月前
|
关系型数据库 数据库 数据库管理
关系型数据库中的主键和外键
关系型数据库中的主键和外键
|
8月前
|
算法 NoSQL 关系型数据库
数据库主键一定要自增吗?有哪些场景不建议自增?
数据库主键一定要自增吗?有哪些场景不建议自增?
283 0
|
8月前
|
数据库 OceanBase
在OceanBase数据库中,当使用主键自增功能插入一条带有主键的数据
在OceanBase数据库中,当使用主键自增功能插入一条带有主键的数据
1125 1
|
10月前
|
SQL Java 关系型数据库
数据库表主键类型设计踩坑记录
数据库表主键类型设计踩坑记录
60 0
|
10月前
|
算法 安全 Java
数据库如何合理生成主键:UUID、雪花算法
1.使用自增主键的弊端 首先在实际工程中我们很少用1,2,3......这样的自增主键,原因如下: 主键冲突 性能问题 安全问题 主键冲突: 比如我要跨数据库进行数据同步、或者在分布式系统中跨“分区”进行数据同步,不难想象,1,2,3......这种递增的单数字是极容易产生冲突的。
255 0
|
10月前
|
Java 数据库 Spring
数据库|Spring·JPA设置复合主键
数据库|Spring·JPA设置复合主键
364 0
|
10月前
|
算法 关系型数据库 MySQL
数据库主键
数据库主键
101 0
|
11月前
|
SQL Oracle 关系型数据库
SQL Server2019数据库查询所有数据库名、表名、表结构、表字段、主键方法演示,执行sql提示对象名‘user_tab_columns‘、 ‘user_cons_columns‘ 无效问题解决
SQL Server2019数据库查询所有数据库名、表名、表结构、表字段、主键方法演示,执行sql提示对象名‘user_tab_columns‘、 ‘user_cons_columns‘ 无效问题解决
309 0

热门文章

最新文章