none
一个多用户情形下的并发情况,疑惑中。。。 RRS feed

  • 问题

  • 现在要实现一个简单的功能,插入数据到表之前检查下是否已经存在,如果存在就update加个计数,不存在就直接insert.

    SQL代码如下:

    create a(id int,times int)

    begin tran

    update a set times=times+1 where id=@id

    if @@rowcount=0

    insert a(id,times)

    select @id,@times

    commit tran

    这种写法在多用户频繁操作的情况,还是会存在重复插入的情况,都做了事务处理了,一直不明白为什么还有重复的现象呢?请大侠们答疑解惑,谢谢!


    • 已编辑 zyyjc 2013年9月20日 11:49
    2013年9月20日 11:48

答案

  • 那还是加约束吧,通过提高事务隔离级别的方式也可以控制,但这种会对正常的查询造成一定的影响,代价更高一些
    • 已标记为答案 zyyjc 2013年9月26日 4:59
    2013年9月23日 12:06
  • 那还是加约束吧,通过提高事务隔离级别的方式也可以控制,但这种会对正常的查询造成一定的影响,代价更高一些

    where not exists(select * from a where id = @id)这里有一定的消耗

    这里我也觉得加约束比较好

    • 已标记为答案 zyyjc 2013年9月26日 4:59
    2013年9月23日 12:19

全部回复

  • 您好,

    或許可改用merge試看看,

    詳細請參考:Developing Modifications that Survive Concurrency


    亂馬客blog: http://www.dotblogs.com.tw/rainmaker/

    2013年9月20日 12:22
  • LZ您好

    方法一:

    你可以在表a中的id字段建立一个唯一约束

    方法二:

    在插入记录之前判断一下,下面插入语句放在if @@rowcount=0的下面


    DECLARE @id int
    DECLARE @times int
    
    SET @id=2
    SET @times=2
    insert a(id,times)
    select @id,@times
    where
    not exists(select * from a where id=@id)

    方法三:

    建立唯一索引

    CREATE UNIQUE INDEX ix ON a(id) 
    --或 
    CREATE UNIQUE CLUSTERED INDEX ix ON a(id)

    2013年9月20日 15:21
  • 特想知道为什么已经用事务了还会导致重复的现象呢?求答案!
    • 已编辑 zyyjc 2013年9月21日 8:07
    2013年9月21日 8:02
  • CREATE TABLE a(id int,times int)
    GO
    INSERT INTO a(id,[times])
    SELECT 1,1 UNION ALL
    SELECT 2,2
    GO
    SELECT * FROM [dbo].[a]
    INSERT INTO a(id,[times])
    SELECT 2,2
    GO

    插入数据的时候有没有用事务? 事务隔离级别是什么?

    2013年9月21日 8:28
  • CREATE TABLE a(id int,times int)
    GO
    INSERT INTO a(id,[times])
    SELECT 1,1 UNION ALL
    SELECT 2,2
    GO
    SELECT * FROM [dbo].[a]
    INSERT INTO a(id,[times])
    SELECT 2,2
    GO

    插入数据的时候有没有用事务? 事务隔离级别是什么?

    sqlserver2008,默认事务隔离级别,有用到事务的
    2013年9月21日 8:38
  • 你说的重复是id还是times?如果两个用户同时访问表,并且表中找不到这个ID,是否可以同时Insert呢?你自己测试一下找不到数据的时候是否可以并发。


    Please Mark As Answer if it is helpful.



    2013年9月22日 2:16
  • 是ID会重复,而且是在ID存在的情况下都会重复,很疑惑啊!
    2013年9月22日 8:03
  • 你能给几个重复的数据吗?

    Please Mark As Answer if it is helpful.

    2013年9月22日 8:25
  • 如果你用 sql 2008, 可以考虑 merge 语句 merge a using(select id = @id) b on a.id = b.id where matched then update set times=a.times+1 when not matched by target then insert(id,times) values(@id,@times)
    • 已建议为答案 啵啵猪 2013年9月24日 7:39
    2013年9月22日 12:31
  • 如果你用供求率方法, 可以试试

    insert a(id,times)

    select @id,@times

    where not exists(select * from a where id = @id)

    if @@rowcount = 0

    update a set times=times+1 where id=@id

    2013年9月22日 12:34
  • 你能给几个重复的数据吗?

    Please Mark As Answer if it is helpful.

    --存储过程

    create proc up_a
    @id int
    as
    begin tran
      update a set times=times+1 where id=@id
      if @@rowcount=0
        insert a(id,times)
        select @id,1
    commit tran

    比如现在a表数据为空,当我在同一个时间点执行exec up_a @id=1这个存储过程时,如果a表没做唯一约束的话,会同时插入2条记录(要求同个id只有一条记录),除了设置约束和主键外,还有其它办法吗?


    • 已编辑 zyyjc 2013年9月23日 10:04
    2013年9月23日 10:03
  • 如果你用供求率方法, 可以试试

    insert a(id,times)

    select @id,@times

    where not exists(select * from a where id = @id)

    if @@rowcount = 0

    update a set times=times+1 where id=@id

    感谢邹老大的帮助,您这种方法我用到生产环境中了,如果不加约束的话,还是会存在重复的现象,因为系统是基于消息处理的,有可能在同一个时间点执行了同一个存储过程
    2013年9月23日 10:15
  • 那还是加约束吧,通过提高事务隔离级别的方式也可以控制,但这种会对正常的查询造成一定的影响,代价更高一些
    • 已标记为答案 zyyjc 2013年9月26日 4:59
    2013年9月23日 12:06
  • 那还是加约束吧,通过提高事务隔离级别的方式也可以控制,但这种会对正常的查询造成一定的影响,代价更高一些

    where not exists(select * from a where id = @id)这里有一定的消耗

    这里我也觉得加约束比较好

    • 已标记为答案 zyyjc 2013年9月26日 4:59
    2013年9月23日 12:19
  • 如果你用 sql 2008, 可以考虑 merge 语句 merge a using(select id = @id) b on a.id = b.id where matched then update set times=a.times+1 when not matched by target then insert(id,times) values(@id,@times)

    我再修正一下,邹建大侠有个地方写错了

    USE [tempdb]
    GO
    CREATE TABLE a ( id INT, times INT )
    GO
    INSERT INTO a(id,[times])
    SELECT 1,1 UNION ALL
    SELECT 2,2
    GO
    SELECT * FROM [dbo].[a]
    GO
    --如果你用 sql 2008, 可以考虑 merge 语句 
    DECLARE @id INT= 6 ,
    @times INT= 10
    MERGE a   --目标表
    USING ( SELECT  id = @id) b  --源表
    ON a.id = b.id
    WHEN MATCHED 
    THEN UPDATE
    SET  a.times = @times
    WHEN NOT MATCHED BY TARGET 
    THEN INSERT ( id, times )
    VALUES    ( @id ,@times );

    更新

    DECLARE @id INT= 2 ,
    @times INT= 9
    MERGE a   --目标表
    USING ( SELECT  id = @id) b  --源表
    ON a.id = b.id
    WHEN MATCHED 
    THEN UPDATE
    SET  a.times = @times
    WHEN NOT MATCHED BY TARGET 
    THEN INSERT ( id, times )
    VALUES    ( @id ,@times );

    2013年10月4日 1:43
  • sql 2008你这样做merge可能造成deadlock,因为它实质上还是2步操作。

    就这个例子来说,提高隔离级别,或者使用hint,延长锁的时间,比用约束效果更好。


    想不想时已是想,不如不想都不想。

    2013年10月4日 5:00
    版主