在一些系统中,经理要生成单据号,为了不使多台客户端生成的单据号重复,一般要在服务端生成这种流水号,本文是在数据库中生成流水号,并且可以生成多种类型的单据号(比如销售单据号,盘点单据号,进货单据号等),利用数据库锁的原理,先看一下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) OUTPUTAS    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
 yz_dic = newDictionary
();       staticDictionary
 xs_dic = newDictionary
();       staticDictionary
 cg_dic = newDictionary
();       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,查看执行的时间,有明显的区别

锁行结果如下(本结果只作比较,快慢与硬件有很大关系):

锁表: