一个预约系统,用最基本的ado.net写的,同一时间段只能由一个用户预约,现在的做法是用户点预约按钮后先ado.net执行select查询数据库是否存在该时间段,存在则提示用户不能预约,否则ado.net执行insert语句插入数据库
现在某一时间内网站并发高了,多个用户预约同一时间,搞得我发现同一时间被多个用户预约上了,请问怎么解决这个问题?让一个时间段只能由一个用户预约?
把select语句和insert语句放到存储过程中再用事务可以解决吗?
或者直接在c#代码中直接在ado.net执行语句前加lock行吗?求指教 。。。
2个方法,数据库锁
lock锁
数据库其实比较简单,因为可以用一个update语句实现,这个算是一个技巧了.
你update的时候where里面要加上你select出来的判断条件.比如库存>0
update 库存-=1 where库存大于0
这样然后根据返回值判断这个操作是否成功.这个还有很大的优化空间,
但总的来说如果是同一张表.这样操作是比较好的
我不是update,数据库里事先 是没有数据的。。我要先Selec查询一次后没有数据才向数据库中插入数据的,用户A在select后还没有insert之前,用户B已经insert进数据库了,然后用户A也跟着select了,所以导致有二条重复数据。
用把select语句和insert语句写在一起吗?然后用ado.net执行这个语句:
if((select count(1) from tab where time='2014-05-27')=0){
insert into tab values('2014-05-27')
}
@牛腩: 这个可以用merge语句,怎么用就得百度了
@吴瑞祥: 最后我把select 语句和insert语句放到一个存储过程中了,不过没用事务 。现在看起来客户也没反馈什么问题。。。
USE [cajx] GO /****** Object: StoredProcedure [dbo].[proc_xyyuyue_cajx] Script Date: 2014-05-31 星期六 08:03:37 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: 牛腩 -- Create date: 2013年11月08日11时4分 -- Description: 学员预约 -- ============================================= ALTER PROCEDURE [dbo].[proc_xyyuyue_cajx] -- Add the parameters for the stored procedure here @userid int, --学员ID @jlid int, --教练ID @date nvarchar(16), --预约日期 @time nvarchar(16), --预约时间 @xs int, -- 学时(通过上面的预约时间计算) @front_time nvarchar(16), --前一个时间段,只有在flag=0预约加班时间时这里才会用到 @yycount int, --一周可以预约的次数, 星期1-4只能约3次,其他能约4次,正常预约时才判断 @zhou_start nvarchar(16), --今天时间,计算一周 @zhou_end nvarchar(16), --以今天为准 ,一周结束后的时间 @ip nvarchar(32), --学员预约时的IP地址 @ksid int, --考试ID @flag int=2, -- 0预约的是加班时间,1考试预约,2正常预约 @mes nvarchar(64) output, --输出信息 @kemu nvarchar(64), --练车科目 @lcdd nvarchar(64), --练车地点 @jsdd nvarchar(64) --接送地点 AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- 验证学员 if((select count(1) from [user] where id=@userid)=0) begin set @mes='没有该学员!' return end -- 验证教练 if((select count(1) from jiaolian where id=@jlid)=0) begin set @mes='没有该教练!' return end -- 教练已停用 if((select count(1) from jiaolian where ty=1 and id=@jlid)>0) begin set @mes='教练已停用,请联系管理员!' return end -- 取教练名,用于插入预约表 declare @jlname nvarchar(50) select @jlname = realname from jiaolian where id=@jlid -- 教练在该时间段已有预约 if((select count(1) from yuyue where replace(CONVERT(char(10),yydate,111),'/','-')=@date and jlid=@jlid and yytime=@time and status<>2)>0) begin set @mes='教练在该时间段已有预约!' return end -- 学员学时不足 declare @userxs int --学员剩余学时 declare @username nvarchar(64) --学员姓名,用于最后的插入日志表 select @userxs=xueshi,@username=username from [user] where id=@userid if(@userxs<>-1 and (@userxs-@xs)<0) begin set @mes='学员的剩余学时不足,不能预约!' return end -- 判断flag if(@flag=0) begin --预约的是加班时间,判断当前time的前一小时是否预约,没约则不能约 if(@time<>'18-19点' and (select count(1) from yuyue where status in (0,1) and userid=@userid and yytime=@front_time and replace(CONVERT(char(10),yydate,111),'/','-')=@date)=0) begin set @mes='请先预约前一时间段!' return end end else if(@flag<>1) begin --即不是考试预约也不是加班时间预约,进行预约时间限制判断 --判断一天只能约一次 if((select count(1) from yuyue where status<>2 and replace(CONVERT(char(10),yydate,111),'/','-')=@date and userid=@userid)>=1) begin set @mes='每个学员一天只能预约一次!' return end --星期1-4只能约3次,其他能约4次,联杰不用此计算 --if((select count(1) from yuyue where status<>2 and userid=@userid and replace(CONVERT(char(10),yydate,111),'/','-')>=@zhou_start and replace(CONVERT(char(10),yydate,111),'/','-')<=@zhou_end)>=@yycount) -- begin -- set @mes='一周最多只能预约'+(ltrim(rtrim(str(@yycount))))+'次' -- return -- end end --上面验证全通过,插入预约表 insert into yuyue(userid,jlid,yydate,yytime,ksid,[status],username,jlname,kemu,lcdd,jsdd) values(@userid,@jlid,@date,@time,@ksid,0,@username,@jlname,@kemu,@lcdd,@jsdd) --插入日志表 insert into log(remark,ip,username,userid) values('会员预约('+@date+' '+@time+')',@ip,@username,@userid) set @mes='预约申请提交成功.' END
如果从数据库这边考虑,把select语句和insert语句放到存储过程中再用事务,应该解决不了的。我感觉用触发器应该能解决。
c#这边考虑,可以将插入的这段代码做成一个静态方法,静态方法中使用lock,应该就能解决了
被预约的对象应该有一个状态字段。每次再预约时,先update这个状态字段=被预约 where 状态字段=未预约。当返回为1时,再insert记录。返回为0时,提示该对象已被其他人预约。
使用事务,先锁起来,在运行事务中的代码
我把语句都放存储过程中了,目前看来客户没反馈,应该没问题了。
USE [cajx] GO /****** Object: StoredProcedure [dbo].[proc_xyyuyue_cajx] Script Date: 2014-05-31 星期六 08:03:37 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: 牛腩 -- Create date: 2013年11月08日11时4分 -- Description: 学员预约 -- ============================================= ALTER PROCEDURE [dbo].[proc_xyyuyue_cajx] -- Add the parameters for the stored procedure here @userid int, --学员ID @jlid int, --教练ID @date nvarchar(16), --预约日期 @time nvarchar(16), --预约时间 @xs int, -- 学时(通过上面的预约时间计算) @front_time nvarchar(16), --前一个时间段,只有在flag=0预约加班时间时这里才会用到 @yycount int, --一周可以预约的次数, 星期1-4只能约3次,其他能约4次,正常预约时才判断 @zhou_start nvarchar(16), --今天时间,计算一周 @zhou_end nvarchar(16), --以今天为准 ,一周结束后的时间 @ip nvarchar(32), --学员预约时的IP地址 @ksid int, --考试ID @flag int=2, -- 0预约的是加班时间,1考试预约,2正常预约 @mes nvarchar(64) output, --输出信息 @kemu nvarchar(64), --练车科目 @lcdd nvarchar(64), --练车地点 @jsdd nvarchar(64) --接送地点 AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- 验证学员 if((select count(1) from [user] where id=@userid)=0) begin set @mes='没有该学员!' return end -- 验证教练 if((select count(1) from jiaolian where id=@jlid)=0) begin set @mes='没有该教练!' return end -- 教练已停用 if((select count(1) from jiaolian where ty=1 and id=@jlid)>0) begin set @mes='教练已停用,请联系管理员!' return end -- 取教练名,用于插入预约表 declare @jlname nvarchar(50) select @jlname = realname from jiaolian where id=@jlid -- 教练在该时间段已有预约 if((select count(1) from yuyue where replace(CONVERT(char(10),yydate,111),'/','-')=@date and jlid=@jlid and yytime=@time and status<>2)>0) begin set @mes='教练在该时间段已有预约!' return end -- 学员学时不足 declare @userxs int --学员剩余学时 declare @username nvarchar(64) --学员姓名,用于最后的插入日志表 select @userxs=xueshi,@username=username from [user] where id=@userid if(@userxs<>-1 and (@userxs-@xs)<0) begin set @mes='学员的剩余学时不足,不能预约!' return end -- 判断flag if(@flag=0) begin --预约的是加班时间,判断当前time的前一小时是否预约,没约则不能约 if(@time<>'18-19点' and (select count(1) from yuyue where status in (0,1) and userid=@userid and yytime=@front_time and replace(CONVERT(char(10),yydate,111),'/','-')=@date)=0) begin set @mes='请先预约前一时间段!' return end end else if(@flag<>1) begin --即不是考试预约也不是加班时间预约,进行预约时间限制判断 --判断一天只能约一次 if((select count(1) from yuyue where status<>2 and replace(CONVERT(char(10),yydate,111),'/','-')=@date and userid=@userid)>=1) begin set @mes='每个学员一天只能预约一次!' return end --星期1-4只能约3次,其他能约4次,联杰不用此计算 --if((select count(1) from yuyue where status<>2 and userid=@userid and replace(CONVERT(char(10),yydate,111),'/','-')>=@zhou_start and replace(CONVERT(char(10),yydate,111),'/','-')<=@zhou_end)>=@yycount) -- begin -- set @mes='一周最多只能预约'+(ltrim(rtrim(str(@yycount))))+'次' -- return -- end end --上面验证全通过,插入预约表 insert into yuyue(userid,jlid,yydate,yytime,ksid,[status],username,jlname,kemu,lcdd,jsdd) values(@userid,@jlid,@date,@time,@ksid,0,@username,@jlname,@kemu,@lcdd,@jsdd) --插入日志表 insert into log(remark,ip,username,userid) values('会员预约('+@date+' '+@time+')',@ip,@username,@userid) set @mes='预约申请提交成功.' END