前言

本节我们来讲讲并发中最常见的情况存在即更新,在并发中若未存在行记录则插入,此时未处理好极容易出现插入重复键情况,本文我们来介绍对并发中存在就更新行记录的七种方案并且我们来综合分析最合适的解决方案。

探讨存在就更新七种方案

首先我们来创建测试表

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('Test') IS NOT NULL
    DROP TABLE TestCREATE TABLE Test
(
    Id int,
    Name nchar(100),    [Counter] int,primary key (Id),    unique (Name)
);GO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

解决方案一(开启事务) 

我们统一创建存储过程通过来SQLQueryStress来测试并发情况,我们来看第一种情况。

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100)) 
    BEGIN TRANSACTION
    IF EXISTS ( SELECT  1
                FROM    Test                WHERE   Id = @Id )        UPDATE  Test        SET     [Counter] = [Counter] + 1
        WHERE   Id = @Id;    ELSE
        INSERT  Test
                ( Id, Name, [Counter] )        VALUES  ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训 

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

同时开启100个线程和200个线程出现插入重复键的几率比较少还是存在。

解决方案二(降低隔离级别为最低隔离级别UNCOMMITED)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100)) 
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
    BEGIN TRANSACTION
    IF EXISTS ( SELECT  1
                FROM    Test                WHERE   Id = @Id )        UPDATE  Test        SET     [Counter] = [Counter] + 1
        WHERE   Id = @Id;    ELSE
        INSERT  Test
                ( Id, Name, [Counter] )        VALUES  ( @Id, @name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

此时问题依旧和解决方案一无异(如果降低级别为最低隔离级别,如果行记录为空,前一事务如果未进行提交,当前事务也能读取到该行记录为空,如果当前事务插入进去并进行提交,此时前一事务再进行提交此时就会出现插入重复键问题)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

解决方案三(提升隔离级别为最高级别SERIALIZABLE)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100)) 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION
    IF EXISTS ( SELECT  1
                FROM    dbo.Test                WHERE   Id = @Id )        UPDATE  dbo.Test        SET     [Counter] = [Counter] + 1
        WHERE   Id = @Id;    ELSE
        INSERT  dbo.Test
                ( Id, Name, [Counter] )        VALUES  ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

在这种情况下更加糟糕,直接到会导致死锁 

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训 

此时将隔离级别提升为最高隔离级别会解决插入重复键问题,但是对于更新来获取排它锁而未提交,而此时另外一个进程进行查询获取共享锁此时将造成进程间相互阻塞从而造成死锁,所以从此知最高隔离级别有时候能够解决并发问题但是也会带来死锁问题。

解决方案四(提升隔离级别+良好的锁)

此时我们再来在添加最高隔离级别的基础上增添更新锁,如下:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100)) 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    BEGIN TRANSACTION
    IF EXISTS ( SELECT  1
                FROM    dbo.Test WITH(UPDLOCK)                WHERE   Id = @Id )        UPDATE  dbo.Test        SET     [Counter] = [Counter] + 1
        WHERE   Id = @Id;    ELSE
        INSERT  dbo.Test
                ( Id, Name, [Counter] )        VALUES  ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训 

运行多次均未发现出现什么异常,通过查询数据时使用更新锁而非共享锁,这样的话一来可以读取数据但不阻塞其他事务,二来还确保自上次读取数据后数据未被更改,这样就解决了死锁问题。貌似这样的方案是可行得,如果是高并发不知是否可行。

解决方案五(提升隔离级别为行版本控制SNAPSHOT) 

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

ALTER DATABASE UpsertTestDatabaseSET ALLOW_SNAPSHOT_ISOLATION ON
 ALTER DATABASE UpsertTestDatabaseSET READ_COMMITTED_SNAPSHOT ONGO IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100)) 
    BEGIN TRANSACTION
    IF EXISTS ( SELECT  1
                FROM    dbo.Test                WHERE   Id = @Id )        UPDATE  dbo.Test        SET     [Counter] = [Counter] + 1
        WHERE   Id = @Id;    ELSE
        INSERT  dbo.Test
                ( Id, Name, [Counter] )        VALUES  ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

上述解决方案也会出现插入重复键问题不可取。

解决方案六(提升隔离级别+表变量)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))    DECLARE @updated TABLE ( i INT ); 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;    BEGIN TRANSACTION
    UPDATE  Test    SET     [Counter] = [Counter] + 1
    OUTPUT  DELETED.Id            INTO @updated
    WHERE   Id = @Id; 
    IF NOT EXISTS ( SELECT  i                    FROM    @updated )        INSERT  INTO Test
                ( Id, Name, counter )        VALUES  ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训 

经过多次认证也是零错误,貌似通过表变量形式实现可行。

解决方案七(提升隔离级别+Merge)

通过Merge关键来实现存在即更新否则则插入,同时我们应该注意设置隔离级别为 SERIALIZABLE  否则会出现插入重复键问题,代码如下:

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))    SET TRAN ISOLATION LEVEL SERIALIZABLE 
    BEGIN TRANSACTION
    MERGE Test AS [target]
    USING
        ( SELECT    @Id AS Id
        ) AS source    ON source.Id = [target].Id    WHEN MATCHED THEN
        UPDATE SET
               [Counter] = [target].[Counter] + 1
    WHEN NOT MATCHED THEN
        INSERT ( Id, Name, [Counter] )        VALUES ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

多次认证无论是并发100个线程还是并发200个线程依然没有异常信息。

总结

本节我们详细讨论了在并发中如何处理存在即更新,否则即插入问题的解决方案,目前来讲以上三种方案可行。

解决方案一(最高隔离级别 + 更新锁)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100)) 
    BEGIN TRANSACTION; 
    UPDATE  dbo.Test WITH ( UPDLOCK, HOLDLOCK )    SET     [Counter] = [Counter] + 1
    WHERE   Id = @Id; 
    IF ( @@ROWCOUNT = 0 )        BEGIN
            INSERT  dbo.Test
                    ( Id, Name, [Counter] )            VALUES  ( @Id, @Name, 1 );        END
 
    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

解决方案二(最高隔离级别 + 表变量)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))    DECLARE @updated TABLE ( i INT ); 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;    BEGIN TRANSACTION
    UPDATE  Test    SET     [Counter] = [Counter] + 1
    OUTPUT  DELETED.id            INTO @updated
    WHERE   id = @id; 
    IF NOT EXISTS ( SELECT  i                    FROM    @updated )        INSERT  INTO Test
                ( Id, Name, counter )        VALUES  ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

解决方案三(最高隔离级别 + Merge)

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

IF OBJECT_ID('TestPro') IS NOT NULL
    DROP PROCEDURE TestPro;GO
 CREATE PROCEDURE TestPro ( @Id INT )AS
    DECLARE @Name NCHAR(100) = CAST(@Id AS NCHAR(100))    SET TRAN ISOLATION LEVEL SERIALIZABLE 
    BEGIN TRANSACTION
    MERGE Test AS [target]
    USING
        ( SELECT    @Id AS Id
        ) AS source    ON source.Id = [target].Id    WHEN MATCHED THEN
        UPDATE SET
               [Counter] = [target].[Counter] + 1
    WHEN NOT MATCHED THEN
        INSERT ( Id, Name, [Counter] )        VALUES ( @Id, @Name, 1 );    COMMITGO

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训

暂时只能想到这三种解决方案,个人比较推荐方案一和方案三, 请问您有何高见,请留下您的评论若可行,我将进行后续补充。

万码学堂,电脑培训,计算机培训,Java培训,JavaEE开发培训,青岛软件培训,软件工程师培训


感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨一下吧,让楼主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下“”按钮,您的将是我最大的写作动力!
本文版权归作者和博客园共有,来源网址:欢迎各位转载,但是未经作者本人同意,转载文章之后,否则保留追究法律责任的权利。

http://www.cnblogs.com/CreateMyself/p/6906594.html

网友评论

更多精彩分享

万码学堂联系方式-Java培训机构,青岛Java培训,青岛计算机培训,软件编程培训,seo优化培训,网络推广培训,网络营销培训,SEM培训,网络优化,在线营销培训,Java培训万码学堂联系方式