一个表(表名 TestTable)有三个字段,其中
ID 自增列
Name 唯一键 nvarchar
Code    唯一键 nvarchar
一般我这样写存储过程
create proc create
(
@Name nvarchar(50),
@Code nvarchar(50)
)
as
begin
if exists(select 1 from TestTable where Name=@Name)
return -1; if exists(select 1 from TestTable where Code=@Code)
return -2;

set @status int;
begin try
insert into TestTable(Name,Code) select  @Name,@Code;
set @status=@@ROWCOUNT;
end try
begin catch
set @status=-255;
end catch
return @status;
end现在我的问题是,会不会,即使前面两条if语句已经做了判断,当执行到insert语句前,别的客户端程序已经调用了这个过程并提供了一个相同的@Name,所以在执行insert语句时发生异常,然后进入到begin catch中?
我是不是应该修改insert语句像下面这样:
insert into TestTable(Name,Code) select  @Name,@Code where
(not exists(select 1 from TestTable where Name=@Name))
and
(not exists(select 1 from TestTable where Code=@Code))感谢不尽

解决方案 »

  1.   

    避免并发进程互相影响, 可以用sql applock, 限制同一时间只能有一个进程执行此存储过程,
    参考 https://technet.microsoft.com/zh-cn/ms189749
      

  2.   


    有加约束,但是这样会进行到catch中,依赖引发异常不是一个好的办法,我个人认为
      

  3.   


    有加约束,但是这样会进行到catch中,依赖引发异常不是一个好的办法,我个人认为就现在的代码来看,也没有什么大问题,你说的问题的出现概率小于万分之一的。
    如果要100%避免这个问题,最有效和简单的方法就是在前台调用的时候,限制进程的新建,在前一个进程结束前,不允许执行create存储过程。
      

  4.   

    这个问题很好,因为这是很常见的问题。低并发,事务快的时候,其实很少出错。要完全解决这个问题,必然是【锁表】写在存储过程里面是: if exists(select 1 from TestTable(tablock, xlock) where Name=@Name)写在 CLR 里面是串行式事务。
      

  5.   

    【锁表】的确会增加开销。但这是解决你的疑问的唯一办法,设计系统的时候必须做出选择,因为低并发,数据少的时候,出错几率真的很低。你只需要加个唯一约束就行,这样就保证了数据的一致性。
    更正一下,上面的做法,应该是同时在存储过程和 CLR 里面修改才生效。如果要完全写在存储过程里面,可能还要使用 holdlock 表提示符和 begin transaction 语句。
      

  6.   


    更正一下,上面的做法,应该是同时在存储过程和 CLR 里面修改才生效。如果要完全写在存储过程里面,可能还要使用 holdlock 表提示符和 begin transaction 语句。
    感谢您的回复。系统确定只有那么几个人在使用,我也清楚出现这种情况的概率极低,我是希望可以用正确的方法做正确的事情,不考虑并发量的问题。。像我上面这样写会不会有问题:insert into TestTable(Name,Code) select  @Name,@Code where
    (not exists(select 1 from TestTable where Name=@Name))
    and
    (not exists(select 1 from TestTable where Code=@Code))
      

  7.   


    更正一下,上面的做法,应该是同时在存储过程和 CLR 里面修改才生效。如果要完全写在存储过程里面,可能还要使用 holdlock 表提示符和 begin transaction 语句。
    感谢您的回复。系统确定只有那么几个人在使用,我也清楚出现这种情况的概率极低,我是希望可以用正确的方法做正确的事情,不考虑并发量的问题。。像我上面这样写会不会有问题:insert into TestTable(Name,Code) select  @Name,@Code where
    (not exists(select 1 from TestTable where Name=@Name))
    and
    (not exists(select 1 from TestTable where Code=@Code))
    同样有问题,exists 子查询的锁范围是不够的。只有锁定表才能完全阻止重复。
      

  8.   

    哈哈,我想到一个土方法,不用锁表。
    在你成功插入数据后
    select @count_name=count(name) from 表 where name=@name
    select @count_code=count(code) from 表 where code=@code
    if @count_name >1 or @count_code >1
    回滚
    ...
      

  9.   

    在name和code上已经建立了唯一键,所以是不会插入重复值的。