upsert是什么?

相信各位码农在工作当中经常碰到这样的场景,当这个表存在某条记录就更新其值,不存在则插入,如下代码例子所示

IF EXISTS (SELECT 1 FROM mytable WHERE Id = @Id)BEGINUPDATE dbo.mytableSET Value = @ValueWHERE Id = @Id;END
ELSEBEGININSERT INTO dbo.mytable (Id, Value)VALUES(@Id, @Value)END

这就是upsert的概念。你一定会想,这么简单的场景需要讨论吗?如果是在单线程环境下,这个确实没啥好讨论,简单的做下判断即可。但是如果是在多用户并发访问的情况下就不是那么简单了。

并发访问会有什么问题?

会造成重复插入一条相同id的记录,会造成primary key violation, 即违反主键约束,从而造成SQL语句抛出异常。这是由于当并发操作时,可能会存在两个或以上的线程执行时都判断不存在这个id的记录,从而同时执行插入操作。

解决办法

原理:事务 + isolation level serializable + 排他锁(updlock)

SERIALIZABLE隔离级别:

  • Statements cannot read data that has been modified but not yet committed by other transactions. (语句不能读取其他事务已修改但未提交的数据,即避免脏读)

  • No other transactions can modify data that has been read by the current transaction until the current transaction completes.(其他事务不能修改当前事务已读取的数据,除非当前事务已完成)

  • Other transactions cannot insert new rows with key values that would fall in the range of keys read by any statements in the current transaction until the current transaction completes(其他事务不能插入新的记录,该记录的键值属于当前事务读取的键值范围内,除非当前事务已完成).

以下具体方法整理相关参考网址,从个人的理解都是遵循上述原理,只是实现上有些区别。

方法1:

set transaction isolation level serializable
begin transaction
if exists (select 1 from mytable with (updlock) where id = @id)update mytable set value = @value where id = @id;
else insert mytable (id, value) values (@id, @value);
commit

在事务开始前,显式设置隔离级别为serializable ,该隔离级别对当前会话保持有效。

当采用serializable隔离级别后,可以避免出现主键冲突。 但还需要在select语句显式的指定updlock提示,否则容易造成事务之间死锁。因为select语句默认会获取一个RangeS-S共享锁,共享锁是不排他的,即其他事务也会获取这个共享锁。当执行后面的update或insert语句时,当前事务需要将RangeS-S共享锁升级为排他锁,而如果其他事务也拥有这个共享锁,就无法升级,从而导致死锁。加上updlock提示,则一开始select语句获得的就是排他的更新锁,从而避免死锁。(详见https://samsaffron.com/blog/archive/2007/04/04/14.aspx)

方法2

begin tran
if exists (select * from t with (updlock,serializable) where pk = @id)beginupdate mytable set value = @value where id = @id;end
elsebegininsert mytable (id, value) values (@id, @value);end
commit tran

对select语句加提示serializable,是对这个表采用serializable隔离级别,而方法1是隔离级别对当前会话有效。

方法3

先执行update语句,如果执行结果为空,则插入,因update本身默认就是获得更新锁,可以不用加updlock提示。

begin tranupdate mytable with (serializable) set value = @value where id = @id;if @@rowcount = 0begininsert mytable (id, value) values (@id, @value);end
commit tran

根据https://www.cnblogs.com/zhenfengren/p/5618511.html的描述,方法3在都是insert的情况下要好于方法1和2。

方法4

MERGE mytable WITH (serializable) AS tUSING (SELECT @ID AS ID) AS new_idON t.ID = new_id.IDWHEN MATCHED THENUPDATE  SET t.value = @valueWHEN NOT MATCHED THENINSERT ( ID, Value ) VALUES (new_id.ID, @value);

从SQL Server 2008之后,微软引入了一个新的的命令语法:Merge,详见 https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-2017

Merge和insert,update一样,本身是个原子语句声明,不需要用begin tran/commit去显式的声明事务。在这里只需要使用

with (serializable)指定使用serializable隔离级别即可。

根据多篇参考文章所述,都推荐使用这个方法,该方法性能最佳,原因如下:

  1. Faster performance. The Engine needs to parse, compile, and execute only one query instead of three (and no temporary variable to hold the key).(更快的性能,引擎只需解析、编译和只需一条语句而不是三条,且不需要临时变量保存键值)
  2. Neater and simpler T-SQL code (after you get proficient in MERGE). (干净简洁的代码,当你熟悉merge语法后)
  3. No need for explicit BEGIN TRANSACTION/COMMIT. MERGE is a single statement and is executed in one implicit transaction. (无需显式声明begin transaction/commit. merge是单个声明,在隐式事务中执行)
  4. Greater functionality. MERGE can delete rows that are not matched by source (SRC table above). For example, we can delete row 1 from A_Table because its Data column does not match Search_Col in the SRC table. There is also a way to return inserted/deleted values using the OUTPUT clause.“ (很棒的功能,merge可以删除源未匹配的记录,举例,我们可以删除A表一条记录当数据字段未匹配源表查询,而且还可以通过output子句返回插入或删除的值)

综上所述:个人推荐方法3和方法4,如果是对性能要求比较高的场景,则建议方法4.

参考:

https://www.cnblogs.com/zhenfengren/p/5618511.html

https://docs.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-2017

https://blogs.msdn.microsoft.com/dbrowne/2013/02/25/why-is-tsql-merge-failing-with-a-primary-key-violation-isnt-it-atomic/

https://docs.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-2017

如何在SQL Server实现upsert功能相关推荐

  1. sql server 数组_如何在SQL Server中实现类似数组的功能

    sql server 数组 介绍 (Introduction) I was training some Oracle DBAs in T-SQL and they asked me how to cr ...

  2. 如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集

    如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集?(2006-12-14 09:25:36) 与这个问题具有相同性质的其他描述还包括: 如 ...

  3. 如何在SQL Server数据库中加密数据

    如何在SQL Server数据库中加密数据 为了防止某些别有用心的人从外部访问数据库,盗取数据库中的用户姓名.密码.信用卡号等其他重要信息,在我们创建数据库驱动的解决方案时,我们首先需要考虑的的第一条 ...

  4. SQL Server中的功能与存储过程

    介绍 (Introduction) Usually DBAs prefer stored procedures in SQL instead of functions in SQL Server. I ...

  5. 如何在SQL Server中实现错误处理

    错误处理概述 (Error handling overview) Error handling in SQL Server gives us control over the Transact-SQL ...

  6. 如何在SQL Server Reporting Services中自动创建KPI

    关键绩效指标(KPI) (Key Performance Indicator (KPI)) A Key Performance Indicator aka KPI is a metric which ...

  7. 如果不使用 SQL Mail,如何在 SQL Server 中发送电子邮件

    如果不使用 SQL Mail,如何在 SQL Server 中发送电子邮件 察看本文应用于的产品 文章编号 : 312839 最后修改 : 2006年12月21日 修订 : 10.1 本页 概要 SQ ...

  8. 如何在 SQL Server 2005 故障转移群集中添加或删除节点(安装程序)

    如何在 SQL Server 2005 故障转移群集中添加或删除节点(安装程序) 使用此过程管理 Microsoft SQL Server 2005 故障转移群集实例中的节点. 重要提示: 若要更新或 ...

  9. 无废话-SQL Server 2005新功能(1) - TSQL

    无废话-SQL Server 2005新功能(1) - TSQL SQL Server 2005相对于SQL Server 2000改进很大,有些还是非常实用的. 举几个例子来简单说明 这些例子我引用 ...

  10. SQL Server 2016 新功能之综述

    冬去春来,发现之前最后一篇写在2012年,又过去了5年了,时间如飞啊.那时候SQL 2012 发布让人兴奋了一把,哪知道时间如刀,刀刀催人老啊,今天SQL 2016都发布了很久了,很快SQL On l ...

最新文章

  1. PHP从入门到跑路(一), 安装PHP环境
  2. 美元汇率pascal程序
  3. Vb Shell 打开程序 等待运行完毕后再继续
  4. 索引超出数组界限是什么意思_从V8源码分析一个JS 数组的内存占用问题
  5. 一步步学习微软InfoPath2010和SP2010--第九章节--使用SharePoint用户配置文件Web service(2)--在事件注册表单上创建表单加载规则...
  6. 信息学奥赛一本通(1201:菲波那契数列)
  7. Select控件实现联动下拉列表框效果
  8. PAT编程:A除以B (20)——C语言
  9. Netty工作笔记0024---SelectionKey API
  10. jupyter一直*_不用下载安装,你的机器人可以直接在浏览器里跳舞丨Jupyter-ROS
  11. 清华排名首登亚洲第一,今年财务预算300亿
  12. DataParallel使用
  13. Redis安装可视化管理软件
  14. 一个广告资源运营管理中台系统简介
  15. 机密领域管理扩展技术(RME)对TF-A的修改分析笔记。
  16. Swing学习01:Swing是什么
  17. keyshot渲染玻璃打光_KeyShot渲染,打光这么打,效果倍儿棒!
  18. 舰c2018换html5,[ 转] HTML/HTML5 download属性及其兼容性的探讨
  19. SuperMap iServer常见问题解答集锦(十五)
  20. scrapy_redis只能使用redis的db0?

热门文章

  1. QWidget(长文)
  2. 动态规划经典例题:不同路径
  3. java\oracle日期格式操作
  4. ChatGLM + LoRA 进行finetune
  5. java查找最长公共子串
  6. (已解决)access_token没过期,但已失效的问题{errcode:40001}-java-微信
  7. 【Python】Python30个笔试题
  8. java中的取整(/)和求余(%)
  9. 难得的一次技术面——终得小米offer
  10. 深度学习与强化学习的两大联姻:DQN与DDPG的对比分析