讨论这个问题的原因,最初来自于惠侨的工程师。我们医院用的HIS是军卫的系统,该系统的数据库是oracle的,在医生开具检验申请的时候,orcle有个序列发生器,用来生成唯一的test_no。但是当这个问题放到sql server里面来的时候,我们发现好像没法做?
此外,真正要解决这个问题,还源于我做的试剂耗材的管理系统。以往生成条码为了保证唯一性,使用日期时间+序列的方式,为了保证它不重复,时间就取的很细,到了毫秒级别。虽然理论上来讲这仍然有重复的可能性。
其实重复毕竟是小概率,但是条码却太长了,比如一个试剂的条码是20140801162430234001,这么一大串,一来不容易被条码枪识别,二来即便识别也容易造成识别前半部分。如果实在没有识别要手输的话,就是要费很大的工夫了。
条码本来就是类似数据库主键的意味,只要保证不重复,就该越短越好。
既然sql server没有序列发生器,那么我们还是用表来做吧。
基本的思路是建立一个有自增ID,时间,序号的表,取每天的该表的序号,每取一次就加一,并且写回该表。那么条码就是日期+序号了。
那么要解决的问题是,如果同时有两个程序都在读取和修改这张表,那该怎么办呢?
一、事务。由于想到事务是sql server里面的排它锁,因此认为只要程序中启用了事务,那么这个问题应该就能够解决,以下是测试的代码:
try dbquery.Connection.BeginTrans; dbquery.sql.clear; dbquery.SQL.Add('select * from lrm_bar where datediff("d",b_time,getdate())=0'); dbquery.Open; if dbquery.RecordCount=0 then begin dbquery.sql.clear; dbquery.SQL.Add('insert into lrm_bar(b_time,b_num) values (getdate(),1)'); if (dbquery.ExecSQL=1) then begin Result:= Self.NewBarHead+GetNumble(1); Query.Connection.CommitTrans; end; end else begin iNum:=dbquery.Int('b_num'); strID:=dbquery.Str('b_id'); iNum:=iNum+1; dbquery.sql.clear; dbquery.SQL.Add('update lrm_bar set b_num='+''''+Inttostr(iNum)+'''' +' where b_id='+''''+strID+''''); if (dbquery.ExecSQL=1) then begin Result:= Self.NewBarHead+GetNumble(iNum); Query.Connection.CommitTrans; end; end; except dbquery.Connection.RollbackTrans; result:=''; end;
然后我启动两个程序,同时获取序列,发现有重复。因此得出结论,事务的锁应该仅限于该链接而已。
二、加锁。
那就只能够人工加锁了,把这个表锁住,直到我操作完了,才给别人操作。把上面的sql查询语句改为:
select * from lrm_bar with(TABLOCKX) where datediff(“d”,b_time,getdate())=0
TABLOCKX是sql server中强制使用独占表级锁,这个锁在事务期间阻止任何其他事务使用这个表。直到这个事务提交或者回滚。
之后我再同时测试两个程序同时获取序列:
A:01 03 05 07…
B:02 04 06 08…
OK不会再重复了。
三、条码长度。
解决了序列生成的问题,最后探讨一下条码长度的问题。
由于试剂一天入库数量不超过一千,那么序号就是001-999就足够了;日期的话年取1位,月份和日期各取两位,总共5位。那么生成的条码号就是8位,比原来的一大串短多了。
40801001,如果实在扫描不出,要手输入的话,看一遍就能输入完了,很方便。
序列生成到此完毕,如果你有更好的方法一定要告诉吵吵哦。
如无特别说明,本博客文章皆为原创。转载请说明,来自吵吵博客。
原文链接:http://chaochaoblog.com/archives/3055
吵吵微信朋友圈,请付款实名加入:
获取序列号干嘛不用存储过程来写?SQLServer内存储过程的执行不是事务排队吗