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
    提供:
Microsoft SQL Server 2008 R2(SP2)-10.50.4000.0(X64)2012年6月28日 08:36:30版权所有(c)微软公司企业版 Windows NT 6.1上的(64位)(版本7601:Service Pack)

  • 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...
    
因此,我提交第一个事务,插入行,然后在user2会话上出现以下错误:

"unique constraint violated" *Cause: An UPDATE or INSERT statement attempted to insert a duplicate key. For Trusted Oracle configured in DBMS MAC mode, you may see this message if a duplicate entry exists at a different level. 默认情况下,此选项处于禁用状态,这意味着SQL Server会抛出错误并丢弃正在插入的非dup行

通过这种方式,您可以保留INSERT/SELECT语法,这在我看来很好

希望能有帮助

资料来源:


明确这一点可能会有所帮助。下面的代码在显式事务中运行,显式锁定行

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