弃用数据库自增ID,曝光一下我自己用到的解决方法之---终结篇
我写这篇随笔的目的旨在 澄清我在上一篇随笔 “弃用数据库自增ID,曝光一下我自己用到的解决方法“ 中的一些事实与看法,同时,我将继续在并发的问题的作题,
我将在原来的存储过程上得用锁来解决并问题并附加上我的测试代码与测试数据。
我之所以放在首页,并不是代表我这篇文章多有水平,多专业,我只是想分享一个程序员内心里深藏着的一点点设计思路与项目经验。通过分享来提高个人!
PS:我写作的初衷重在于分享自己的技术知识,对问题的设计思路以及通过分享来得到更好的建议,同样的一件东西,并不是也并不可能适合于所有人或都所有场景,就算拿来主义也需要先去其糟粕,而后取其精华!老赵原来写过一些文章的初衷被很多人误解,而且还有一部分人在不明白作者的原意初衷上进行攻击,真的是悲哀! 不过还好,老赵这厮足够扎实,顶得住压力,呵呵....
同时我针对一些评论也发下自己的看法:
有很多人说用GUID代替,这当然也是一种方法,我如果说要曝光的方法竟然是用GUID来解决数据移值、数据分割的问题,相信肯定会有大把砖头扔过来!
有人说用自增ID,自增ID也可以设置起始值,没错,看项目需要吧,我的随笔中真的没有提到一定要你用我的方法啊! 但是我总觉得插入关联数据时不方便,总是需要先拿到关键的外键值,不过有人Peter.zhu说到,可以在插入前拿到这个值,这个我还真不知道,还望告之...,
有人说( 活雷锋 Dorian Deng)每次看到为了数据移植等情况摒弃自增ID都觉得可笑... 我一直都想不通迁移的频率或者概率有多大,如果这是十年后的问题...我觉得这话说得有点绝对,未雨绸缪并不见得就是一件坏事,从全局性看问题绝对没错,没必要把自己的那点项目经验来一并否决所有项目需要。这得看是什么类型项目了,这个世界上没有绝对的事情,我就还真碰到过!
最多人说的就是并发问题,没错,确实会有并发问题,我讲得很清楚这个是它的一个缺点。不过还是有人拿它作文章,哪有十全十美的事情,不过对于一些小的应用,应该可以满足了!当然我曾经也用过!
最后,也得感谢很多人,如: llzhzhbb, 卡通一下,sinxsoft, 周强,.........
废话不多讲了!言归正传
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~separator~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1:测试前提:
测试之前,我先对原来的存储过程做一个单词的修改(当然了,我个存储过程可以优化,上一篇中有人给过不少的建议),这里我就不再罗嗦了,先看看我改后之后一个存储过程:
create procedure up_get_table_key
(@table_name varchar(50),@key_value int output
)
as
beginbegin trandeclare @key int--initialize the key with 1set @key=1--whether the specified table is existif not exists(select table_name from table_key where table_name=@table_name)begininsert into table_key values(@table_name,@key) --default key vlaue:1end-- step increaseelse beginselect @key=key_value from table_key with (updlock) where table_name=@table_nameset @key=@key+1--update the key value by table nameupdate table_key set key_value=@key where table_name=@table_nameend--set ouput valueset @key_value=@key--commit trancommit tranif @@error>0rollback tran
end
注意在查询的时候我使用了updlock,我在检索时对该行加锁,updlock的优点是允许用户读取数据(不阻塞其它事务)并在以后更新数据,同时确保自从上次读取数据后数据没有被更改。也就是说,我在本次事务调用中取到key值之后,直到更新该数据之后,锁可以保证了key值不会更改,确实不会的会话不会拿到相同的key值。
2:测试
我的测试环境:SQL server 2008,.net framework 3.5
先附上我的程序代码:
private void GetIncreateID(){while (!isStop){int a = Database.GetIncreaseID("stud");//int b = Database.GetIncreaseID("stud");//int c = Database.GetIncreaseID("stud");//int d = Database.GetIncreaseID("stud");Database.AddStudent(a,a.ToString());//Debug.WriteLine(string.Format("a={0},b={1},c={2},d={3}", a, b, c, d));Thread.Sleep(200);}}protected void start_Click(object sender, EventArgs e){Thread thread1 = new Thread(new ThreadStart(GetIncreateID));Thread thread2 = new Thread(new ThreadStart(GetIncreateID));Thread thread3 = new Thread(new ThreadStart(GetIncreateID));Thread thread4 = new Thread(new ThreadStart(GetIncreateID));thread1.Start();thread2.Start();thread3.Start();thread4.Start();for(int i = 0;i<1000;i++){Thread thread = new Thread(new ThreadStart(GetIncreateID));thread.Start();}}protected void stop_Click(object sender, EventArgs e){isStop = true;}
数据访问层代码:
public static int GetIncreaseID(string tableName){DbCommand command = CreateCommand();command.CommandType = System.Data.CommandType.StoredProcedure;command.CommandText = "up_get_table_key"; //GetSequenceDbParameter para1 = command.CreateParameter();para1.ParameterName = "@table_name";para1.Value = tableName;para1.DbType = System.Data.DbType.String;para1.Size = 50;DbParameter para2 = command.CreateParameter();para2.ParameterName = "@key_value";para2.DbType = System.Data.DbType.Int32;para2.Direction = System.Data.ParameterDirection.Output;command.Parameters.Add(para1);command.Parameters.Add(para2);int increaseId = 0;try{command.Connection.Open();command.ExecuteNonQuery();increaseId = Convert.ToInt32(command.Parameters["@key_value"].Value); }catch (DbException ex){throw ex;}finally{command.Connection.Close();}return increaseId;}public static void AddStudent(int id, string name){DbCommand command = CreateCommand();command.CommandType = System.Data.CommandType.Text;command.CommandText = string.Format("insert into stud values({0},'{1}')",id,name);try{command.Connection.Open();command.ExecuteNonQuery();}catch (DbException ex){throw ex;}finally{command.Connection.Close();}}
我开启了至少1000个线程来插入数据,插入数据 id,name ,其中 id是表stud表的主键值,如果存在相同的ID值,插入数据时肯定会抛异常!
再看看我的结果:
我插入了700多万条数据,,纯粹只为测试并发性,没有发生相同的ID情况!
BTW,我根据 llzhzhbb 的建议,也试着去实现它来测试并发性!这是一种全新的做法,先看看思路:
1:定义静态int变量来取代自增ID
2:应用程序启动时初始化该静态变量为表MAX ID值
3:插入数据时,先对静态变量Interlocked.Increment自增1,然后再插入新的数据
这种做法实际就是绕过数据库,利用应用程序来解决并发性,Interlocked.Increment会以生成原子操作来进行递增并存储结果,它可以保证多线程的并发时同步性,从而使得自增ID的唯一性。
如果不是使用Interlocked.Increment会有同样的并发问题,因为很大一部分计算机上,自增操作并不是一种原子操作,因为在递增的过程中CPU会先把你要递增的变量值先读取到寄存器中,然后再对它进行自增操作,最后再把寄存器中已自增的值保存到变量中,明显,这中间发生了三个操作。因此无法保证在多线程下的并发问题。
当然,这只是一种思路,来看看它的实际应用情况吧,还是用数据说话,先看看代码:
public static class TableMappingVariable{public static int CourseMappingID = 0;static TableMappingVariable(){DbCommand command = Database.CreateCommand();command.CommandType = System.Data.CommandType.Text;command.CommandText = "select max(cour_id) from course";try{command.Connection.Open();DbDataReader reader = command.ExecuteReader();if (reader.Read()){CourseMappingID = reader.GetInt32(0);}}catch (DbException ex){throw ex;}finally{command.Connection.Close();}}}
为了简单起见,我直接把它写在静态构造函数了,初始化时取最大ID值。
同样,我在测试程序中,开启了至少1000个线程来插入数据。
protected void btnStart_Click(object sender, EventArgs e){Thread thread1 = new Thread(new ThreadStart(GetIncreateID));Thread thread2 = new Thread(new ThreadStart(GetIncreateID));Thread thread3 = new Thread(new ThreadStart(GetIncreateID));Thread thread4 = new Thread(new ThreadStart(GetIncreateID));thread1.Start();thread2.Start();thread3.Start();thread4.Start();for (int i = 0; i < 1000; i++){Thread thread = new Thread(new ThreadStart(AddCourse));thread.Start();}}public void AddCourse(){while (!isStop){Database.AddCourse(Interlocked.Increment(ref TableMappingVariable.CourseMappingID), TableMappingVariable.CourseMappingID.ToString());Thread.Sleep(200);}}protected void btnStop_Click(object sender, EventArgs e){isCourseStop = true;}
再看看测试数据:
我插入了400多万的数据。测试结果非常正确!这种方法相对于MAX(ID)+1来说,确实减少了数据库访问的次数,MAX(ID)+1在每次插入时都需要去表中检索一次来得到MAX(ID). 这种方法,我只需要读取一次就够了!当然不包括程序挂掉的情况........
总结,我不想讲太多,当然了,你可以继续使用GUID,自增ID,或者MAX(ID)+1.....等来作为你数据库的主键,不过这些对我来说,并不重要,我不懂金谍K3,SQLLITE,对ORACLE懂得也不是很多,更不知道什么是copmiere,更不懂得他们的设计思想,如果能公开,那就最好不过了。这个东西纯粹只为测试并发性!有可能并不适合你!
欢迎评论!
最后,附上我的测试代码:(点击下载)
PS: 顺道推荐一下我的博客音乐!真的不错!呵呵...
转载于:https://www.cnblogs.com/repository/archive/2011/01/20/1939450.html
弃用数据库自增ID,曝光一下我自己用到的解决方法之---终结篇相关推荐
- 8年面试官问到:数据库自增 ID 用完了会咋样?
有主键 如果你的表有主键,并且把主键设置为自增. 在 MySQL 中,一般会把主键设置成 int 型.而 MySQL 中 int 型占用 4 个字节,作为有符号位的话范围就是 [-2^31,2^31- ...
- 解决数据库自增ID的问题
(1)设置主键自增为何不可取 这样的话,数据库本身是单点,不可拆库,因为id会重复. (2)依赖数据库自增机制达到全局ID唯一 使用如下语句: REPLACE INTO Tickets64 (stub ...
- MySQL分布式ID_分布式唯一ID系列(3)——数据库自增ID机制适合做分布式ID吗
数据库自增ID机制原理介绍 在分布式里面,数据库的自增ID机制的主要原理是:数据库自增ID和mysql数据库的replace_into()函数实现的.这里的replace数据库自增ID和mysql数据 ...
- SQLServer “无法对数据库‘XXX‘ 执行删除,因为它正用于复制”的解决方法
SQLServer "无法对数据库'XXX' 执行删除,因为它正用于复制"的解决方法 参考文章: (1)SQLServer "无法对数据库'XXX' 执行删除,因为它正用 ...
- Mybatis的逆向工程,MySQL8的数据库,8.0.11驱动的逆向工程的坑的解决方法
Mybatis的逆向工程,MySQL8的数据库,8.0.11驱动的逆向工程的坑的解决方法 参考文章: (1)Mybatis的逆向工程,MySQL8的数据库,8.0.11驱动的逆向工程的坑的解决方法 ( ...
- Field ‘id‘ doesn‘t have a default value错误解决方法
Field 'id' doesn't have a default value错误解决方法 参考文章: (1)Field 'id' doesn't have a default value错误解决方法 ...
- java抖音获取用户信息失败_为什么抖音用id搜不到用户?抖音用id搜不到用户的原因与解决方法...
在抖音短视频上,网友可以通过抖音id来搜索指定的抖音用户,并添加为好友,不过,这几天,不断有网友反映一个问题,那就是:抖音用id搜不到用户,那么,为什么抖音用id搜不到用户?今天,小编就为大家介绍一下 ...
- oracle数据库报错12154,PL/SQL登录Oracle数据库报错ORA-12154:TNS:无法解析指定的连接标识符解决方法...
本篇文章小编给大家分享一下PL/SQL登录Oracle数据库报错ORA-12154:TNS:无法解析指定的连接标识符解决方法,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. ...
- Excel导入数据库出现“外部表不是预期的格式”错误的解决方法
Excel导入数据库出现"外部表不是预期的格式"错误的解决方法 参考文章: (1)Excel导入数据库出现"外部表不是预期的格式"错误的解决方法 (2)http ...
最新文章
- java swing中英文支持,java - Swing国际化 - 如何在运行时更新语言 - SO中文参考 - www.soinside.com...
- 综述 | 森林微生物组:多样性,复杂性和动态变化(IF:11.3)
- [官版翻译]OpenStack centos版安装(三)
- 软件过程与项目管理第四周作业
- Cannot retrieve mapping for action
- Java并发编程之Semaphore信号量
- 微软、谷歌和 BAT 等巨头成立机密计算联盟,联手保护数据安全
- 第11章 樱花树(《C和C++游戏趣味编程》配套教学视频)
- python中标点符号大全_Python处理中文标点符号大集合
- 24点算法讲解与实现
- PHP运行出现502是什么原因,php出现502错误怎么解决
- android_98_自定义DragLayout
- Shell的解释和一些用法
- 18[NLP训练营]拉格朗日乘子法、对偶、KTT
- App inventor打地鼠
- 百度地图LBS应用开发代码
- 学习OpenCV2——Mat之通道的理解
- Unity热更新机制
- 微信数据运营面试心得(社招)
- Apache占用tcp6的80端口
热门文章
- php-java-bridge 作用_PHP-Java-Bridge的使用(平安银行支付功能专版)
- 人工智能AI、机器学习和深度学习的区别
- php 设置跨域axios,vue2.0中proxyTable用axios进行跨域请求的设置方法
- 边缘检测算法_机器视觉怎样检测产品边缘
- 矩阵的奇异值分解_线性代数31——奇异值分解
- 小升初数学计算机考试题,【2020年小升初数学常考题型及易错题分析】- 环球网校...
- ObjC学习4-多态、动态类型、动态绑定及异常处理
- 基于Matlab----16QAM调制与解调
- Windows打印机驱动开发笔记(二)
- 大数据、物联网、区块链:融合趋势三重奏的好处