用SQL存储过程生成唯一单据号
在一些系统中,经理要生成单据号,为了不使多台客户端生成的单据号重复,一般要在服务端生成这种流水号,本文是在数据库中生成流水号,并且可以生成多种类型的单据号(比如销售单据号,盘点单据号,进货单据号等),利用数据库锁的原理,先看一下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,查看执行的时间,有明显的区别
锁行结果如下(本结果只作比较,快慢与硬件有很大关系):