为啥此查询会生成 PK 违规错误?

Posted

技术标签:

【中文标题】为啥此查询会生成 PK 违规错误?【英文标题】:Why is this query generating a PK violation error?为什么此查询会生成 PK 违规错误? 【发布时间】:2018-08-27 12:48:10 【问题描述】:

因此,我尝试在单个查询中仅插入不存在的行。

我的查询如下:

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)

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

违反主键约束“PK_USERS_ROLES”。不能 在对象“dbo.users_roles”中插入重复键。复制品 键值为 (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) 2012 年 6 月 28 日 08:36:30 版权所有 (c) Microsoft Corporation Enterprise Edition (64 位)在 Windows NT 6.1(内部版本 7601:Service Pack)上

SQL Server 版本:SQL Server 2008 R2

事务隔离级别:ReadCommitted

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

问题

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

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

谢谢。

【问题讨论】:

嗯,主键只是user_id 吗?如果是这样,也许他们也是1以外的角色的成员。 @AaronBertrand 抱歉,忘记指定PK_USER_ROLES,我刚刚编辑了这个问题。它由[user_id], [role_id]组成。 好吧,I can't reproduce this problem,所以一定有什么你没有告诉我们的,或者可能是应用程序的两个不同线程同时提交了相同的查询。您使用的是什么隔离级别?你为什么这么反对存储过程?它们实际上可以帮助解决并发问题。 我会选择 MERGE 语句,that SO post 中有一个示例。这就像一个没有“up”而只有“sert”的 upsert。 1) 同一个插入被执行了两次(来自同一个连接) 2) 有多个连接在做同一个插入 【参考方案1】:

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

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

【讨论】:

谢谢,我们试试看。顺便说一句,似乎您删除了上述评论,我已尝试通过编辑来回答它们。很难确定哪些信息是相关的,哪些不相关,对此感到抱歉。 @Jeto 我确实删除了该评论,因为您已经更新了答案。然后评论变得无关紧要。请注意,对于您拥有的版本,您似乎缺少 SP3(请参阅sqlserverbuilds.blogspot.com)。 当然,只是让您知道以防您错过。至于 SP3 更新,我不负责数据库服务器/平台本身,但我会看看我们是否能得到。 @Jeto 到目前为止,您有什么见解吗? 不幸的是还没有。数据库服务器的管理员整个星期都很忙,我将离开办公室几个星期,所以在那之前无法回复它。无论如何,感谢您的帮助(赞成票来自我)。【参考方案2】:

此表是否在某个时刻被截断或删除了行?多久一次?对我来说,在某个时刻不应该找到这些行是有道理的,因为您正在运行“如果不存在则插入”,此时两个或多个查询可能会命中数据库以插入相同的数据......只有一个将...如果在“不存在”查找之前插入了行,则另一个不应该做任何事情,或者如果在查找之后插入行,则应该失败。

我现在只有一个 Oracle 数据库来做一些测试,我可以重现这个问题。我的提交模式是明确的:

创建空表,唯一约束和授权选择,插入到另一个用户:

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;

用户 1 上的数据库会话:

INSERT INTO just_a_test 
SELECT 10 
FROM DUAL 
WHERE NOT EXISTS 
(
  SELECT 1 
  FROM just_a_test 
  WHERE val = 10
)
;

-- no commit yet...

用户 2 上的数据库会话:

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 会话中出现以下错误:

“违反了唯一约束” *原因:UPDATE 或 INSERT 语句试图插入重复键。 对于在 DBMS MAC 模式下配置的 Trusted Oracle,您可能会看到 如果在不同级别存在重复条目,则显示此消息。

现在我回滚第二个事务并在 user2 上再次运行相同的插入,现在我得到以下输出:

已插入 0 行。

可能你的场景就是这样的。希望对您有所帮助。

编辑

对不起。你问了两个问题,我只回答了Could someone explain why/how this is happening? 一个。所以我错过了What would be an efficient way to safely insert in one go (in other words, in a single query)?

对您来说,“安全”到底意味着什么?假设您正在运行包含大量行的 INSERT/SELECT,并且与存储的行相比,其中只有一个是重复的。为了您的“安全”级别,您应该忽略所有插入的行还是只忽略重复的行,存储其他行?

同样,我现在没有 SQL Server 可以试一试,但看起来您可以告诉 SQL Server 是拒绝插入所有行以防出现任何重复,还是只拒绝重复,保留其他行.这同样适用于单行的插入...如果它是重复的,则抛出错误...或者直接忽略它。

语法应如下所示,仅忽略 dup 行且不抛出错误:

    ALTER TABLE [TableName] REBUILD WITH (IGNORE_DUP_KEY = ON);

默认情况下,此选项为 OFF,这意味着 SQL Server 会引发错误并丢弃插入的非重复行。

这样你可以保留你的 INSERT/SELECT 语法,这在 imo 中看起来不错。

希望对你有帮助。

来源:

https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-index-option-transact-sql?view=sql-server-2008

https://***.com/a/11207687/1977836

【讨论】:

非常有趣,感谢您的帮助。我会对此进行调查并回复您。 @Jeto 不客气。我刚刚编辑了我的答案并包含了一个“解决方案”(看起来更像是一种解决方法)。希望对您有所帮助。

以上是关于为啥此查询会生成 PK 违规错误?的主要内容,如果未能解决你的问题,请参考以下文章

SQL 命令生成器查询值

为啥此查询中出现错误“ORA-00904: invalid identifier”?

为啥 Apex/Oracle 会出现此错误?

Postgresql: 在 ""ViewShifts"" 处或附近出现语法错误 为啥我在下面的查询中会出现此错误?

为啥当我的子查询无效时,此 SQL 查询会起作用? - 甲骨文 [重复]

为啥dojo会发出这个错误?未捕获的类型错误:对象 [对象对象] 没有方法“查询”