如何在SQL Server实现upsert功能
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隔离级别即可。
根据多篇参考文章所述,都推荐使用这个方法,该方法性能最佳,原因如下:
- Faster performance. The Engine needs to parse, compile, and execute only one query instead of three (and no temporary variable to hold the key).(更快的性能,引擎只需解析、编译和只需一条语句而不是三条,且不需要临时变量保存键值)
- Neater and simpler T-SQL code (after you get proficient in MERGE). (干净简洁的代码,当你熟悉merge语法后)
- No need for explicit BEGIN TRANSACTION/COMMIT. MERGE is a single statement and is executed in one implicit transaction. (无需显式声明begin transaction/commit. merge是单个声明,在隐式事务中执行)
- 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功能相关推荐
- sql server 数组_如何在SQL Server中实现类似数组的功能
sql server 数组 介绍 (Introduction) I was training some Oracle DBAs in T-SQL and they asked me how to cr ...
- 如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集
如何在SQL Server查询语句(Select)中检索存储过程(Store Procedure)的结果集?(2006-12-14 09:25:36) 与这个问题具有相同性质的其他描述还包括: 如 ...
- 如何在SQL Server数据库中加密数据
如何在SQL Server数据库中加密数据 为了防止某些别有用心的人从外部访问数据库,盗取数据库中的用户姓名.密码.信用卡号等其他重要信息,在我们创建数据库驱动的解决方案时,我们首先需要考虑的的第一条 ...
- SQL Server中的功能与存储过程
介绍 (Introduction) Usually DBAs prefer stored procedures in SQL instead of functions in SQL Server. I ...
- 如何在SQL Server中实现错误处理
错误处理概述 (Error handling overview) Error handling in SQL Server gives us control over the Transact-SQL ...
- 如何在SQL Server Reporting Services中自动创建KPI
关键绩效指标(KPI) (Key Performance Indicator (KPI)) A Key Performance Indicator aka KPI is a metric which ...
- 如果不使用 SQL Mail,如何在 SQL Server 中发送电子邮件
如果不使用 SQL Mail,如何在 SQL Server 中发送电子邮件 察看本文应用于的产品 文章编号 : 312839 最后修改 : 2006年12月21日 修订 : 10.1 本页 概要 SQ ...
- 如何在 SQL Server 2005 故障转移群集中添加或删除节点(安装程序)
如何在 SQL Server 2005 故障转移群集中添加或删除节点(安装程序) 使用此过程管理 Microsoft SQL Server 2005 故障转移群集实例中的节点. 重要提示: 若要更新或 ...
- 无废话-SQL Server 2005新功能(1) - TSQL
无废话-SQL Server 2005新功能(1) - TSQL SQL Server 2005相对于SQL Server 2000改进很大,有些还是非常实用的. 举几个例子来简单说明 这些例子我引用 ...
- SQL Server 2016 新功能之综述
冬去春来,发现之前最后一篇写在2012年,又过去了5年了,时间如飞啊.那时候SQL 2012 发布让人兴奋了一把,哪知道时间如刀,刀刀催人老啊,今天SQL 2016都发布了很久了,很快SQL On l ...
最新文章
- PHP从入门到跑路(一), 安装PHP环境
- 美元汇率pascal程序
- Vb Shell 打开程序 等待运行完毕后再继续
- 索引超出数组界限是什么意思_从V8源码分析一个JS 数组的内存占用问题
- 一步步学习微软InfoPath2010和SP2010--第九章节--使用SharePoint用户配置文件Web service(2)--在事件注册表单上创建表单加载规则...
- 信息学奥赛一本通(1201:菲波那契数列)
- Select控件实现联动下拉列表框效果
- PAT编程:A除以B (20)——C语言
- Netty工作笔记0024---SelectionKey API
- jupyter一直*_不用下载安装,你的机器人可以直接在浏览器里跳舞丨Jupyter-ROS
- 清华排名首登亚洲第一,今年财务预算300亿
- DataParallel使用
- Redis安装可视化管理软件
- 一个广告资源运营管理中台系统简介
- 机密领域管理扩展技术(RME)对TF-A的修改分析笔记。
- Swing学习01:Swing是什么
- keyshot渲染玻璃打光_KeyShot渲染,打光这么打,效果倍儿棒!
- 舰c2018换html5,[ 转] HTML/HTML5 download属性及其兼容性的探讨
- SuperMap iServer常见问题解答集锦(十五)
- scrapy_redis只能使用redis的db0?