≡
  • 网络编程
  • 数据库
  • CMS技巧
  • 软件编程
  • PHP笔记
  • JavaScript
  • MySQL
位置:首页 > 数据库 > SQL Server

SQL Server并发处理存在就更新解决示例

人气:363 时间:2020-06-18

这篇文章主要为大家详细介绍了SQL Server并发处理存在就更新解决示例,具有一定的参考价值,可以用来参考一下。

感兴趣的小伙伴,下面一起跟随四海网的小编两巴掌来看看吧!

 

前言

 

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

 

探讨存在就更新七种方案

 

首先我们来创建测试表

代码如下:


IF OBJECT_ID('Test') IS NOT NULL
 DROP TABLE Test

CREATE TABLE Test
(
 Id int,
 Name nchar(100),
 [Counter] int,primary key (Id),
 unique (Name)
);
GO

 

解决方案一

(开启事务)

 

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

代码如下:


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 );
 COMMIT
GO

【图片暂缺】

【图片暂缺】

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

 

解决方案二

(降低隔离级别为最低隔离级别UNCOMMITED)

 

代码如下:


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 );
 COMMIT
GO

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

【图片暂缺】

 

解决方案三

(提升隔离级别为最高级别SERIALIZABLE)

 

代码如下:


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 );
 COMMIT
GO

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

【图片暂缺】

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

 

解决方案四

(提升隔离级别+良好的锁)

 

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

代码如下:


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 );
 COMMIT
GO

【图片暂缺】

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

 

解决方案五

(提升隔离级别为行版本控制SNAPSHOT)

 

代码如下:


ALTER DATABASE UpsertTestDatabase
SET ALLOW_SNAPSHOT_ISOLATION ON
 
ALTER DATABASE UpsertTestDatabase
SET READ_COMMITTED_SNAPSHOT ON
GO 

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 );
 COMMIT
GO

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

 

解决方案六

(提升隔离级别+表变量)

 

代码如下:


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 );
 COMMIT
GO

【图片暂缺】

【图片暂缺】

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

 

解决方案七

(提升隔离级别+Merge)

 

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

代码如下:


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 );
 COMMIT
GO

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

 

总结

 

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

 

解决方案一

(最高隔离级别 + 更新锁)

 

代码如下:


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
 
 COMMIT
GO

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

 

解决方案二

(最高隔离级别 + 表变量)

 

代码如下:


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 );
 COMMIT
GO


 

解决方案三

(最高隔离级别 + Merge)

 

代码如下:


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 );
 COMMIT
GO

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持四海网。

本文来自:http://www.q1010.com/179/9160-0.html

注:关于SQL Server并发处理存在就更新解决示例的内容就先介绍到这里,更多相关文章的可以留意四海网的其他信息。

关键词:SQL SERVER

您可能感兴趣的文章

  • 远程连接阿里云SqlServer 2012 数据库服务器的简单示例
  • 解决SqlServer 各版本 sa帐户不能登录问题
  • Windows使用性能监视器监控SqlServer的常见指标示例
  • 调整SQLServer2000运行数据库结构的简单示例
  • 解决SQLServer远程连接失败的问题
  • 数据库触发器DB2和SqlServer区别解析
  • 解决在Azure上部署Sqlserver网络访问不了示例
  • 在sqlserver中如何使用CTE解决复杂查询问题
  • SqlServer应用之sys.dm_os_waiting_tasks 引发的疑问(下)
  • SQLServer中merge函数用法分析
上一篇:SQL Server使用扩展事件获取Session级别的等待信息及SQLServer 2016中Session级别等待信息的增强示例
下一篇:SQL Server基数估计计算预估行数的简单示例
热门文章
  • SQL Server SQL获取第一条记录的方法
  • SQL Server出现System.OutOfMemoryException异常的解决方法
  • SQL Server的 update from 语句的简单示例
  • SQL Server 数据库备份方法菜鸟教程
  • SQL Server 多表关联时在where语句中慎用trim()方法
  • SQL Server数据类型及长度限制详细说明
  • mybaits非配置原因,导致SqlSession was not registered for synchronization异常解析
  • SQL Server 收缩后对数据库的使用有影响吗?
  • SQL Server 格式导致的Excel导入sql出现异常的解决方法
  • SQL Server 连接服务器出现错误 7391的解决方法
  • 最新文章
    • SQL Server存储过程基本语法的简单示例
    • sql查询时增加自动编号和分页的简单示例
    • sql轻松应付百万数据的高效数据分页存储过程的简单示例
    • sql获取一条数据中所有字段的名称和值的实现方法
    • sql分割函数的简单示例
    • SQL Server异常捕获的简单示例
    • SQL SERVER回滚恢复误操作数据的实现方法
    • SQL Server函数或存储过程中抛出异常的实现方法
    • SQL Server创建数据库的完整代码
    • SQL Server创建数据库的命令用法示例

四海网收集整理一些常用的php代码,JS代码,数据库mysql等技术文章。