Sql server 为什么此查询会生成PK冲突错误?
因此,我尝试在一个查询中,只插入一行,如果它不存在的话 我的问题如下:Sql server 为什么此查询会生成PK冲突错误?,sql-server,sql-server-2008,Sql Server,Sql Server 2008,因此,我尝试在一个查询中,只插入一行,如果它不存在的话 我的问题如下: INSERT INTO [dbo].[users_roles] ([user_id], [role_id]) SELECT 29851, 1 WHERE NOT EXISTS ( SELECT 1 FROM [dbo].[users_roles] WHERE user_id = 29851 AND role_id = 1) 有时(非常罕见,但仍然如此),它会生成以下错误: 违反主键约束“主键用户角色”。不能 在对象“
INSERT INTO [dbo].[users_roles] ([user_id], [role_id])
SELECT 29851, 1 WHERE NOT EXISTS (
SELECT 1 FROM [dbo].[users_roles] WHERE user_id = 29851 AND role_id = 1)
有时(非常罕见,但仍然如此),它会生成以下错误:
违反主键约束“主键用户角色”。不能
在对象“dbo.users\u roles”中插入重复键。复制品
键值为(29851,1)
PK\u USERS\u ROLES
是[user\u id],[role\u id]
。以下是表架构的完整SQL:
create table users_roles
(
user_id int not null
constraint FK_USERS_ROLES_USER
references user,
role_id int not null
constraint FK_USERS_ROLES_USER_ROLE
references user_role,
constraint PK_USERS_ROLES
primary key (user_id, role_id)
)
上下文:
这是由托管在Apache服务器上的PHP脚本执行的,在数百次事件中,“随机”发生一次(很可能与并发相关)
更多信息:
提供:SELECT@@VERSION
- SQL Server版本:
SQL Server 2008 R2
- 事务隔离级别:
ReadCommitted
- 这是在显式事务中执行的(通过PHP语句,但我认为最终结果是相同的)
- 有人能解释一下为什么会发生这种情况吗
- 什么是一次安全插入(换句话说,在单个查询中)的有效方法? 我见过其他答案,例如,但是解决方案是针对存储过程的
谢谢。此表是在某个时刻被截断还是删除了行?多久一次?对我来说,不应该在某个时刻找到行是有意义的,因为您正在运行“如果不存在,则插入”,在这一时刻,两个或多个查询可能会命中数据库以插入相同的数据。。。只有一个会。。。如果在“不存在”查找之前插入行,则另一个不应执行任何操作;如果在查找之后插入行,则该操作失败 我现在只有一个Oracle数据库来做一些测试,我可以重现这个问题。我的提交模式是显式的:
- 创建空表、唯一约束并将select、insert授予其他用户:
CREATE TABLE just_a_test (val NUMBER(3,0)); ALTER TABLE just_a_test ADD CONSTRAINT foobar UNIQUE (val); GRANT SELECT, INSERT ON just_a_test TO user2;
- user1上的DB会话:
INSERT INTO just_a_test SELECT 10 FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM just_a_test WHERE val = 10 ) ; -- no commit yet...
- user2上的DB会话:
INSERT INTO user1.just_a_test SELECT 10 FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM user1.just_a_test WHERE val = 10 ) ; -- no commit yet, the db just hangs til the other session commit...
明确这一点可能会有所帮助。下面的代码在显式事务中运行,显式锁定行
DECLARE @user_id INT; SET @user_id=29851;
DECLARE @role_id INT; SET @role_id=1;
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @exists INT;
SELECT @exists=1
FROM [dbo].[users_roles] WITH(ROWLOCK,HOLDLOCK,XLOCK)
WHERE user_id=@user_id AND role_id=@role_id;
IF @exists IS NULL
BEGIN
INSERT INTO [dbo].[users_roles] ([user_id], [role_id])
VALUES(@user_id,@role_id);
END
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
那么,主键是否仅位于用户id上?如果是这样的话,也许他们也是
1
@AaronBertrand以外的角色的成员对不起,忘记指定PK\u用户\u角色了,我刚刚编辑了这个问题。它由[user\u id],[role\u id]
组成。因此,一定有什么您没有告诉我们的,或者可能应用程序的两个不同线程在同一时间提交相同的查询。您使用的是什么隔离级别?为什么您如此反对存储过程?它们实际上可以帮助解决并发问题。这就像一个没有“up”的upsert,只有“sert”。1)同一个insert执行了两次(来自同一个连接)2)有多个连接执行同一个insert非常有趣,感谢您的帮助。我会调查此事,然后再打电话给你。@Jeto,不客气。我刚刚编辑了我的答案,并加入了一个“解决方案”(看起来更像是一个变通方法)。希望能多帮点忙。谢谢,我们试试看。顺便说一句,似乎你删除了你的上述评论,我已经尝试通过编辑来回答他们。很难判断哪些信息是相关的,哪些是不相关的,对此表示抱歉。@Jeto我确实删除了该评论,因为您已经更新了答案。然后评论就变得无关紧要了。请注意,在您拥有的版本中,您似乎缺少SP3(请参阅)。当然,如果您错过了,请告诉您。至于SP3更新,我不负责DB服务器/平台本身,但我会看看我们是否能做到。@Jeto到目前为止你有什么见解吗?不幸的是还没有。数据库服务器的管理员整个星期都很忙,我要离开办公室几周,所以在那之前我不能回去。无论如何谢谢你的帮助(upvote是我给的)。
ALTER TABLE [TableName] REBUILD WITH (IGNORE_DUP_KEY = ON);
DECLARE @user_id INT; SET @user_id=29851;
DECLARE @role_id INT; SET @role_id=1;
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @exists INT;
SELECT @exists=1
FROM [dbo].[users_roles] WITH(ROWLOCK,HOLDLOCK,XLOCK)
WHERE user_id=@user_id AND role_id=@role_id;
IF @exists IS NULL
BEGIN
INSERT INTO [dbo].[users_roles] ([user_id], [role_id])
VALUES(@user_id,@role_id);
END
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH