Iai*_*ain 21 sql-server sql-server-2005
我从多个进程插入一个SQL数据库.过程有时可能会尝试将重复数据插入表中.我试图以一种处理重复的方式编写查询,但我仍然得到:
System.Data.SqlClient.SqlException: Violation of UNIQUE KEY constraint 'UK1_MyTable'. Cannot insert duplicate key in object 'dbo.MyTable'.
The statement has been terminated.
Run Code Online (Sandbox Code Playgroud)
我的查询看起来像:
INSERT INTO MyTable (FieldA, FieldB, FieldC)
SELECT FieldA='AValue', FieldB='BValue', FieldC='CValue'
WHERE (SELECT COUNT(*) FROM MyTable WHERE FieldA='AValue' AND FieldB='BValue' AND FieldC='CValue' ) = 0
Run Code Online (Sandbox Code Playgroud)
约束'UK1_MyConstraint'表示在MyTable中,3个字段的组合应该是唯一的.
我的问题:
请注意,我知道还有其他方法可以解决"INSERT if not exists"的原始问题,例如(摘要):
我应该使用其中一种方法吗?
编辑1个 SQL用于创建表:
CREATE TABLE [dbo].[MyTable](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[FieldA] [bigint] NOT NULL,
[FieldB] [int] NOT NULL,
[FieldC] [char](3) NULL,
[FieldD] [float] NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON),
CONSTRAINT [UK1_MyTable] UNIQUE NONCLUSTERED
(
[FieldA] ASC,
[FieldB] ASC,
[FieldC] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
Run Code Online (Sandbox Code Playgroud)
编辑2决定:
只是为了更新这个 - 我决定使用链接问题(链接)中建议的"JFDI"实现.虽然我仍然很好奇为什么原来的实现不起作用.
Dan*_*nov 48
为什么这不起作用?
我相信SQL Server的默认行为是在不再需要时立即释放共享锁.您的子查询将导致表上的短暂共享(S)锁定,该子锁定将在子查询完成后立即释放.
此时,没有什么可以防止并发事务插入刚刚验证的行.
我需要进行哪些修改才能因违反约束而无法出现异常?
将HOLDLOCK
提示添加到子查询将指示SQL Server保持锁定,直到事务完成.(在您的情况下,这是一个隐式事务.)HOLDLOCK
提示等同于SERIALIZABLE
提示,提示本身等同于您在"其他方法"列表中引用的可序列化事务隔离级别.
在HOLDLOCK
单独暗示将足以保持S锁,防止并发事务从插入你防范行.但是,您可能会发现您的唯一密钥违例错误被死锁替换,发生在同一频率上.
如果您只保留表上的S锁,请考虑在两次并发尝试之间插入同一行,以锁步方式进行竞争 - 两者都成功获取表上的S锁,但两者都无法成功获取独占(X)执行插入所需的锁定.
幸运的是,这个确切的场景还有另一种锁类型,称为Update(U)锁.U锁与S锁相同,但有以下区别:虽然可以在同一资源上同时保持多个S锁,但一次只能保持一个U锁.(换句话说,虽然S锁相互兼容(即可以共存但没有冲突),U锁彼此不兼容,但可以与S锁共存;并且沿着频谱,独占(X)锁不是兼容S或U锁)
您可以使用UPDLOCK
提示将子查询上的隐式S锁升级为U锁.
在表中插入同一行的两个并发尝试现在将在初始select语句中序列化,因为它获取(并保持)U锁,该U锁与并发插入尝试中的另一个U锁不兼容.
NULL值
FieldC允许NULL值这一事实可能会产生一个单独的问题.
如果ANSI_NULLS
处于打开状态(默认),那么FieldC=NULL
即使在FieldC为NULL的情况下,相等性检查也会返回false(启用时必须使用IS NULL
运算符检查null ANSI_NULLS
).由于FieldC可以为空,因此在插入NULL值时,重复检查将不起作用.
要正确处理空值,您需要修改EXISTS子查询以使用IS NULL
运算符,而不是=
在插入NULL值时.(或者您可以更改表以禁止所有相关列中的NULL.)
SQL Server联机丛书参考
RE:“我仍然很好奇为什么原始实现不起作用。”
为什么会起作用?
有什么可以防止两个并发事务按如下方式交错?
Tran A Tran B
---------------------------------------------
SELECT COUNT(*)...
SELECT COUNT(*)...
INSERT ....
INSERT... (duplicate key violation).
Run Code Online (Sandbox Code Playgroud)
唯一发生冲突锁的时间是在Insert
阶段。
在 SQL Profiler 中查看此内容
create table MyTable
(
FieldA int NOT NULL,
FieldB int NOT NULL,
FieldC int NOT NULL
)
create unique nonclustered index ix on MyTable(FieldA, FieldB, FieldC)
Run Code Online (Sandbox Code Playgroud)
然后将以下内容粘贴到两个不同的 SSMS 窗口中。记下连接的 spid(x 和 y)并设置 SQL Profiler Trace 来捕获锁定事件和用户错误消息。应用 spid=x 或 y 和严重性 = 0 的过滤器,然后执行这两个脚本。
DECLARE @FieldA INT, @FieldB INT, @FieldC INT
SET NOCOUNT ON
SET CONTEXT_INFO 0x696E736572742074657374
BEGIN TRY
WHILE 1=1
BEGIN
SET @FieldA=( (CAST(GETDATE() AS FLOAT) - FLOOR(CAST(GETDATE() AS FLOAT))) * 24 * 60 * 60 * 300)
SET @FieldB = @FieldA
SET @FieldC = @FieldA
RAISERROR('beginning insert',0,1) WITH NOWAIT
INSERT INTO MyTable (FieldA, FieldB, FieldC)
SELECT FieldA=@FieldA, FieldB=@FieldB, FieldC=@FieldC
WHERE (SELECT COUNT(*) FROM MyTable WHERE FieldA=@FieldA AND FieldB=@FieldB AND FieldC=@FieldC ) = 0
END
END TRY
BEGIN CATCH
DECLARE @message VARCHAR(500)
SELECT @message = 'in catch block ' + ERROR_MESSAGE()
RAISERROR(@message,0,1) WITH NOWAIT
DECLARE @killspid VARCHAR(10)
SELECT @killspid = 'kill ' +CAST(SPID AS VARCHAR(4)) FROM sys.sysprocesses WHERE SPID!=@@SPID AND CONTEXT_INFO = (SELECT CONTEXT_INFO FROM sys.sysprocesses WHERE SPID=@@SPID)
EXEC ( @killspid )
END CATCH
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
31590 次 |
最近记录: |