在一些系统中,经理要生成单据号,为了不使多台客户端生成的单据号重复,一般要在服务端生成这种流水号,本文是在数据库中生成流水号,并且可以生成多种类型的单据号(比如销售单据号,盘点单据号,进货单据号等),利用数据库锁的原理,先看一下SQL语句:
CREATE TABLE [dbo].[Lshs]( [MAXLSH] [BIGINT] NULL, [LSHDate] [DATETIME] NULL, [LX] [NVARCHAR](6) NULL ) ON [PRIMARY]
CREATE PROC [dbo].[getlsh] @lx VARCHAR(6) , @lsh VARCHAR(30) OUTPUT AS BEGIN --启动事务处理 DECLARE@tran_point INT --控制事务嵌套 SET @tran_point = @@trancount --保存事务点 IF @tran_point = 0 BEGINTRAN tran_SOF_getmaxdjbh ELSE SAVETRAN tran_SOF_getmaxdjbh DECLARE @bh BIGINT --锁表 --IF EXISTS(SELECT 1 FROM lshs WITH (TABLOCKX) WHERE lx=@lxAND lshdate=CONVERT(VARCHAR(10),GETDATE(),126)) -- BEGIN -- SELECT @bh = MaxLsh + 1 -- FROM dbo.Lshs -- WHERE lx = @lx -- UPDATE Lshs -- SET MaxLSH = @bh -- WHERE lx = @lx --END --ELSE --BEGIN -- UPDATE Lshs -- SET MaxLSH =1,lshdate=CONVERT(VARCHAR(10),GETDATE(),126) -- WHERE lx = @lx --end --锁行 UPDATE Lshs SET @bh = maxlsh= CASE WHEN lshdate=CONVERT(VARCHAR(10),GETDATE(),126) THEN maxlsh+1 ELSE 1 end ,lshdate=CONVERT(VARCHAR(10),GETDATE(),126) WHERE lx = @lx --获取编号 SET @lsh=@lx+REPLACE(CONVERT(VARCHAR(10),GETDATE(),126),'-','')+REPLICATE('0',6-LEN(@bh))+CONVERT(VARCHAR(10),@bh) IF @@error <> 0 BEGIN ROLLBACKTRAN tran_SOF_getmaxdjbh END IF @tran_point = 0 BEGIN COMMITTRAN tran_SOF_getmaxdjbh RETURN 0 END END
语句中注释的是锁表的方式,未注释是用Update语句,是锁行的操作,锁表的操作要更占时间,当一个表中有很多个类型时,就会排队,等一种类型生成后,释放表,才能继续生成下一种类型,锁行只锁相同类型的,相对来说类型越多,这种优势越明显。并且在短时间内生成的单据号越多,锁行的优势也越明显。
下来,我们可以用这样的代码来测试一下:
classProgram { staticDictionary<string, string> yz_dic = newDictionary<string, string>(); staticDictionary<string, string> xs_dic = newDictionary<string, string>(); staticDictionary<string, string> cg_dic = newDictionary<string, string>(); staticvoid GetID() { Console.WriteLine("begin"); void BuildLsh(object obj) { //定义一个时间对象 var oTime = newStopwatch(); oTime.Start(); //记录开始时间 using (var con = newSqlConnection("DataSource=.;Initial Catalog=testlsh;Persist Security Info=True;UserID=sa;Password=******;")) { var cmd = newSqlCommand(); cmd.Connection = con; cmd.CommandText = "getlsh"; cmd.CommandType =System.Data.CommandType.StoredProcedure; var lxnum = DateTime.Now.Millisecond % 3; var lx = "YZ"; switch (lxnum) { case 0: lx = "YZ"; break; case 1: lx = "XS"; break; case 2: lx = "CG"; break; } cmd.Parameters.Add(newSqlParameter() { ParameterName ="@lx", Value = lx }); var par = newSqlParameter(); par.ParameterName = "@lsh"; par.Direction =System.Data.ParameterDirection.Output; par.SqlDbType =System.Data.SqlDbType.VarChar; par.Size = 30; cmd.Parameters.Add(par); con.Open(); cmd.ExecuteReader(); var lsh = par.Value.ToString(); switch (lxnum) { case 0: yz_dic.Add(lsh,obj.ToString()); break; case 1: xs_dic.Add(lsh,obj.ToString()); break; case 2: cg_dic.Add(lsh,obj.ToString()); break; } } oTime.Stop(); //记录结束时间 //输出运行时间。 Console.WriteLine($"---{obj}---程序的运行时间:{ oTime.Elapsed.TotalMilliseconds}毫秒"); } for (int i = 0; i < 2000; i++) { new System.Threading.Thread(BuildLsh).Start(i); } } publicstaticvoid Main() { GetID(); } }
可以切换存付过程中的锁表和锁列的两段SQL,查看执行的时间,有明显的区别
锁行结果如下(本结果只作比较,快慢与硬件有很大关系):
锁表:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。