因此,我试图在一个查询中,仅在不存在的行中插入行。
我的查询如下:
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)
有时(非常罕见,但仍然),它会产生以下错误:
违反了PRIMARY KEY约束'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 Windows NT 6.1(内部版本7601:Service Pack)上安装(64位)
SQL Server版本:SQL Server 2008 R2
事务隔离级别:ReadCommitted
这是在 explicit 事务中执行的(通过PHP语句,但是我认为最终结果是相同的)
问题:
有人可以解释为什么/如何发生吗?
一种安全地一次性插入(换句话说,在单个查询中)的有效方法是什么? 我看到了其他答案,例如this one,但这些解决方案仅适用于存储过程。
谢谢。
答案 0 :(得分:1)
此表是否已被截断或行已删除?以及多久一次?对我来说有意义的是,由于您正在运行“如果不存在则插入”,因此一时不应该找到行,并且此刻可能有两个或更多查询访问数据库以插入相同的数据……只有一个将...如果在行“不存在”之前插入该行,则另一项不执行任何操作;如果在行之后插入该行,则失败。
我现在只有一个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会话上收到以下错误:
"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.
现在,我回滚第二个事务,并在user2上再次运行相同的插入操作,现在我得到以下输出:
0 rows inserted.
您的情况可能与此类似。希望对您有所帮助。
编辑
对不起。您问了两个问题,而我只回答了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,则抛出错误...或者反之亦然。
语法应如下所示,仅忽略dup行并且不抛出错误:
ALTER TABLE [TableName] REBUILD WITH (IGNORE_DUP_KEY = ON);
默认情况下,此选项为OFF,这意味着SQL Server会引发错误并丢弃正在插入的非dup行。
通过这种方式,您将保留INSERT / SELECT语法,看起来不错。
希望有帮助。
来源:
答案 1 :(得分: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