【数据库设计】'Max加一”生成主键的注意点
Sql Server数据库的主键设计有多种,比如经典的自增长,欢乐的guid,按照时间生成id(有通过程序生成的方式,还有一种也是通过数据库时间和随机数生成),按照业务模型组合生成有意义的id等等。最近项目中接触到一种模拟自增长自动编号主键的方式,即Max加一
Sql Server数据库的主键设计有多种,比如经典的自增长,欢乐的guid,按照时间生成id(有通过程序生成的方式,还有一种也是通过数据库时间和随机数生成),按照业务模型组合生成有意义的id等等。最近项目中接触到一种模拟自增长自动编号主键的方式,即“Max加一”。
Max加一的原理看上去和自增长是相似的,表的唯一主键也设计成数字型(如bigint)的,只是把自动增长去掉了(表设计器标识规范一栏,“是标识”的选项选择否即可)。在Insert记录的时候,通常情况下的流程大致是这样的:读取当前表的Max主键值后加一,然后按照传递的相关参数,显式插入主键及其他列的值。这种生成主键方式的一个最显著的优点是可以按照自己的规则生成主键。比如有如下生成主键的用户自定义的存储过程usp_GetNewID:
<span>if</span> <span>exists</span> (<span>select</span> * <span>from</span> dbo.sysobjects <span>where</span> id = object_id(N<span>'[dbo].usp_GetNewID'</span>) <span>and</span> OBJECTPROPERTY(id, N<span>'IsProcedure'</span>) = 1) <span>drop</span> <span>procedure</span> [dbo].usp_GetNewID <span>GO</span> <span>CREATE</span> <span>PROCEDURE</span> [dbo].usp_GetNewID @tableName nvarchar(30), --表名 @columnName nvarchar(30), --字段名 @NewId <span>int</span> <span>output</span> --<span>Max</span>(ID)生成的新ID <span>AS</span> <span>BEGIN</span> <span>DECLARE</span> @MaxId bigint <span>DECLARE</span> @MaxIdTemp bigint <span>DECLARE</span> @<span>SQL</span> nvarchar(500) <span>DECLARE</span> @SQLDBId nvarchar(8) <span>set</span> @SQLDBId=<span>'10'</span> --获取原最大ID <span>set</span> @<span>SQL</span> =<span>'SELECT @Mymaxid= isnull(MAX('</span>+@columnName+<span>'),101) From '</span>+ @tableName; <span>-- select @MaxId </span> <span>if</span> @@error=0 <span>begin</span> <span>exec</span> sp_executesql @<span>SQL</span>,N<span>'@Mymaxid bigint output'</span>,@MaxId <span>output</span> <span>end</span> --生成新ID <span>if</span> @@error=0 <span>begin</span> <span>select</span> @MaxIdTemp=<span>SUBSTRING</span>(<span>cast</span>(@MaxId <span>as</span> nvarchar),3,100)+1; <span>end</span> <span>if</span> @@error=0 <span>begin</span> <span>set</span> @NewId=@SQLDBId+<span>cast</span>(@MaxIdTemp <span>as</span> nvarchar) <span>end</span> <span>else</span> <span>begin</span> <span>set</span> @NewId=-1 <span>end</span> END
通过将@SQLDBId='10'和set @NewId=@SQLDBId cast(@MaxIdTemp as nvarchar)这种方式的组合,我们可以控制不同的数据库服务器(或者不同的库)生成的主键都有规律可循,比如第一台服务器生成的id都以10开头,第二台都以20开头,依此类推,这样多少有利于数据库的分布式管理。
下面简单说说这种方式的两个重大缺陷:
1、效率问题
虽然主键有聚集索引,但是当我们的数据表数据达到一定数量级的时候(比如千万),那么通过聚合函数Max取值肯定会有不小的代价,这样显然会影响一点效率。但是到底效率几何,和自增长的性能比较又如何?这个我真的还没有这方面的测试数据,如果有童鞋有这方面的经验请不吝赐教,恳求告知。
【UPDATE】:根据今天的性能测试,在表已有1百万数据基础上,继续插入数据,每次插入10000条记录,自增和Max加一这种方式的时间相差不足1秒,总体上自增长的方式会稍快一点,但是并不明显,在可接受范围内。测试结果见下图:
2、并发插入问题
当我们在程序中有顺序的先后插入数据的时候,这个问题当然不会发生。但是在大部分应用中,经常会并发处理一些数据,这个时候通过Max加一的方式就会造成插入上的并发问题。因为如果同时有两个或者多个插入请求读到相同的MAX值加一以后,在插入的时候就会发生插入重复主键的错误。
我们可以做一个简单的测试:
(1)、添加用户的存储过程usp_AddUser
<span>if</span> <span>exists</span> (<span>select</span> * <span>from</span> dbo.sysobjects <span>where</span> id = object_id(N<span>'[dbo].usp_AddUser'</span>) <span>and</span> OBJECTPROPERTY(id, N<span>'IsProcedure'</span>) = 1) <span>drop</span> <span>procedure</span> [dbo].usp_AddUser <span>GO</span> <span>CREATE</span> <span>PROCEDURE</span> [dbo].usp_AddUser <span>AS</span> <span>BEGIN</span> <span>DECLARE</span> @Id bigint <span>EXEC</span> usp_GetNewID <span>'Users'</span>,<span>'Id'</span>,@Id <span>OUT</span> <span>SELECT</span> @Id <span>if</span> @@ERROR=0 <span>BEGIN</span> INSERT <span>INTO</span> Users ( Id, Name ) <span>VALUES</span> ( @Id, <span>'jeff wong'</span> ) <span>END</span> END
用户表简单设计成有Id和Name两个字段,插入的时候,Name的值不受任何干扰,固定为”jeff wong”。
(2)、然后在应用程序中调用如下:
<span>static</span> <span>void</span> Main(<span>string</span>[] args) { <span>int</span> counter = 2000; Action action = <span>null</span>; <span>for</span> (<span>int</span> i = 0; i < counter; i++) { action = AddUser; action.BeginInvoke((a) => { Action method = a.AsyncState <span>as</span> Action; method.EndInvoke(a); }, action); } Console.Read(); } <span>private</span> <span>static</span> <span>void</span> AddUser() { <span>try</span> { <span>using</span> (var conn = <span>new</span> SqlConnection(sqlConnString)) { SqlCommand cmd = <span>new</span> SqlCommand(<span>"usp_AddUser"</span>, conn); cmd.CommandType = CommandType.StoredProcedure; conn.Open(); <span>int</span> result = cmd.ExecuteNonQuery(); } } <span>catch</span> (Exception ex) { Console.WriteLine(ex.ToString()); } }
在今晚本地的几组测试中,无一例外地都抛出了插入重复主键的异常。这个问题在这几天的一个数据同步程序中竟然没有发现,原因就是当时数据库没有或者很少符合条件的需要同步的数据。当然现在所有同步都已经改成通过在存储过程中利用游标顺序处理,这样就合理地解决掉并发插入问题了。
最后,我感觉主键的生成选择还有很多东西可以挖掘,有一些知识可以拿过来深入讨论一下,比如自增长是如何控制并发插入的,诸如此类,欢迎您的意见和建议。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

7月12日消息,荣耀MagicV3系列今日正式发布,搭载全新荣耀视力舒缓绿洲护眼屏,在屏幕本身具备高规格和高素质的同时,还开创性的引入AI主动式护眼技术。据悉,传统的缓解近视的方式是“近视镜”,近视眼镜度数均匀分布,保证了视线中心区域成像在视网膜之上,但周边区域成像在视网膜后,视网膜感应到成像在后,促进眼轴向后生长,从而使度数加深。目前主要的缓解近视发展的方式之一是“离焦镜”,其中心区域度数正常,周边区域通过光学设计分区调整,从而使周边区域成像落在视网膜前,

7月29日消息,荣耀X60i手机今日正式开售,首发1399元。设计上,荣耀X60i手机采用居中挖孔直屏设计,四边近乎无界的超窄边框,极大地拓宽了视野边界。荣耀X60i参数显示屏:6.7英寸高清显示屏电池:5000mAh大容量电池处理器:天玑6080处理器(台积电6nm,2x2.4G的A76+6×2G的A55)系统:MagicOS8.0系统其他功能:5G信号增强灵动胶囊屏下指纹双MIC降噪知识问答摄影能力:后置双摄系统:5000万像素主摄200万像素辅助镜头前置自拍镜头:800万像素价格:8GB

5月13日消息,vivoX100s今晚正式发布,除了出色的影像,新机在信号方面表现也十分强悍。据vivo官方介绍,vivoX100s采用了创新的寰宇信号放大系统,该系统配备了高达21根天线。这一设计基于直屏进行了重新优化,以平衡5G、4G、Wi-Fi、GPS以及NFC等众多信号需求。这使得vivoX100s成为了vivo有史以来信号接收能力最强的手机。新款手机还采用了独特的360°环绕设计,天线分布在机身周围。这一设计不仅增强了信号的强度,还针对日常各种握持姿势进行了优化,避免了因握持方式不当导

苹果公司最新发布的iOS18、iPadOS18以及macOSSequoia系统为Photos应用增添了一项重要功能,旨在帮助用户轻松恢复因各种原因丢失或损坏的照片和视频。这项新功能在Photos应用的"工具"部分引入了一个名为"已恢复"的相册,当用户设备中存在未纳入其照片库的图片或视频时,该相册将自动显示。"已恢复"相册的出现为因数据库损坏、相机应用未正确保存至照片库或第三方应用管理照片库时照片和视频丢失提供了解决方案。用户只需简单几步

7月19日消息,小米MIXFold4首旗舰折叠新机今晚正式发布,首次搭载“立体异形电池”。据介绍,小米MIXFold4在电池技术上实现了重大突破,专为折叠屏设计了创新的“立体异形电池”。传统折叠屏设备多采用常规方形电池,空间利用效率较低。为解决这一问题,小米没有采用常见的卷绕式电芯,而是全新开发叠片工艺,打造全新形态的电池,大幅提升了空间利用率。电池技术创新为了实现精确交替堆叠正负极片,确保锂离子安全嵌入,小米开发了新型超声焊接机和叠片机,提高了焊接和裁切精

如何在PHP中使用MySQLi建立数据库连接:包含MySQLi扩展(require_once)创建连接函数(functionconnect_to_db)调用连接函数($conn=connect_to_db())执行查询($result=$conn->query())关闭连接($conn->close())

PHP中处理数据库连接报错,可以使用以下步骤:使用mysqli_connect_errno()获取错误代码。使用mysqli_connect_error()获取错误消息。通过捕获并记录这些错误信息,可以轻松识别并解决数据库连接问题,确保应用程序的顺畅运行。

小米的Redmi品牌正准备在其产品组合中增加另一款经济型手机——Redmi14C。该设备已确认将于8月31日在越南发布。然而,在发布之前,这款手机的规格已经通过越南零售商被披露。Redmi14CRedmi经常在新系列中带来全新的设计,Redmi14C也不例外。这款手机背面有一个大的圆形摄像头模块,与前代的设计完全不同。蓝色配色版甚至采用渐变设计,让它看起来感觉更加高端。不过,实际上Redmi14C是一款经济型手机。相机模组包括四个环;一个环内装有5000万像素主传感器,另一个可能装有用于深度信息
