sql-server - 为什么此查询会生成 PK 违规错误?

标签 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)

有时(很少,但仍然如此),它会产生以下错误:

Violation of PRIMARY KEY constraint 'PK_USERS_ROLES'. Cannot insert duplicate key in object 'dbo.users_roles'. The duplicate key value is (29851, 1).

PK_USERS_ROLES[user_id]、[role_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) Jun 28 2012 08:36:30 Copyright (c) Microsoft Corporation Enterprise Edition (64-bit) on Windows NT 6.1 (Build 7601: Service Pack)

  • SQL Server 版本:SQL Server 2008 R2

  • 事务隔离级别:ReadCommitted

  • 这是在一个显式事务中执行的(通过 PHP 语句,但我认为最终结果是一样的)

问题:

  • 有人可以解释为什么/如何发生这种情况吗?

  • 一次性安全插入(换句话说,在单个查询中)的有效方法是什么? 我看过其他答案,例如 this one但解决方案适用于存储过程。

谢谢。

最佳答案

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

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

关于sql-server - 为什么此查询会生成 PK 违规错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52039734/

相关文章:

sql-server - 聚集索引和分布式数据库

php - 在带有 MS SQL 的 MS 服务器上使用 PHP 有多容易?

c# - 为什么我在打开连接时收到 "Invalid attempt to call HasRows when reader is closed"?

SQL查询;水平到垂直

c# - 存储过程报错

sql - 避免在查询中使用 "SELECT TOP 1"和 "ORDER BY"

sql - 选择两个日期之间的所有日期

sql-server - Excel 2010 与 Excel 2013 VBA 存储过程

sql-server - 临时表存在于 tempdb 中

sql-server - 使用SQLAlchemy将数据从MS SQL迁移到PostgreSQL