Sql 为什么按主键分组的插入会抛出主键约束冲突错误?

Sql 为什么按主键分组的插入会抛出主键约束冲突错误?,sql,sql-server,database,Sql,Sql Server,Database,我有一个insert语句抛出主键错误,但我不知道怎么可能插入重复的键值 首先,我创建一个带有主键的临时表 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED //Note: I've tried committed and uncommited, neither materially affects the behavior. See screenshots below for proof. IF (OBJECT_ID('TEMPDB..#P'))

我有一个insert语句抛出主键错误,但我不知道怎么可能插入重复的键值

首先,我创建一个带有主键的临时表

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED //Note: I've tried committed and uncommited, neither materially affects the behavior. See screenshots below for proof.

IF (OBJECT_ID('TEMPDB..#P')) IS NOT NULL DROP TABLE #P;

CREATE TABLE #P(idIsbn INT NOT NULL PRIMARY KEY, price SMALLMONEY, priceChangedDate DATETIME);
然后我从prices表中提取价格,按idIsbn分组,idIsbn是temp表中的主键

INSERT  INTO #P(idIsbn, price, priceChangedDate)
SELECT  idIsbn ,
        MIN(lowestPrice) ,
        MIN(priceChangedDate)
FROM Price p
WHERE p.idMarketplace = 3100
GROUP BY p.idIsbn
我知道idIsbn根据定义进行分组使其具有独特性。价格表中的idIsbn为:[idIsbn][int]不为空

但每隔一段时间,当我运行此查询时,就会出现以下错误:

Violation of PRIMARY KEY constraint 'PK__#P________AED35F8119E85FC5'. Cannot insert duplicate key in object 'dbo.#P'. The duplicate key value is (1447858).
注意:我有很多关于时间的问题。我将选择此语句,按F5,不会出现错误。然后我会再做一次,它会失败,然后我会一次又一次地运行它,它会成功几次,然后再次失败。我想我想说的是,我找不到它何时会成功,何时不会成功的模式

如果A我在插入表之前刚刚创建了一个全新的表,B我按设计为主键的列进行分组,那么如何插入重复的行

现在,我正在使用IGNORE_DUP_KEY=ON解决问题,但我真的很想知道问题的根本原因

以下是我在SSMS窗口中实际看到的内容。没有更多,也没有更少:

@@版本为:

Microsoft SQL Server 2008 (SP3) - 10.0.5538.0 (X64) 
    Apr  3 2015 14:50:02 
    Copyright (c) 1988-2008 Microsoft Corporation
    Standard Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)
以及两个非聚集索引:

CREATE NONCLUSTERED INDEX [IX_Price_idMarketplace_INC_idIsbn_lowestPrice_priceDate] ON [dbo].[Price]
(
    [idMarketplace] ASC
)
INCLUDE (   [idIsbn],
    [lowestPrice],
    [priceDate]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_Price_idMarketplace_priceChangedDate_INC_idIsbn_lowestPrice] ON [dbo].[Price]
(
    [idMarketplace] ASC,
    [priceChangedDate] ASC
)
INCLUDE (   [idIsbn],
    [lowestPrice]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

你没有提供你的桌子结构

这是一个复制,其中包含一些假设的细节,导致了read committed NB的问题:现在您提供了我在您的案例中看到的定义。对priceChangedDate列的更新将在IX_Price_idMarketplace_priceChangedDate_INC_idIsbn_lowestPrice index中移动行(如果正在查找的话)

连接1设置表 连接2 并发数据修改,将一行从搜索范围3100,1的开始移动到31002001的结束,然后再次重复移动。 连接3使用唯一约束插入临时表 该计划没有聚合,因为idIsbn上存在唯一约束idIsbn、idMarketplace上的唯一约束也会起作用,因此可以优化group by,因为没有重复值

但在读提交隔离级别,共享行锁会在读取行时立即释放。因此,行可以移动位置,并通过相同的搜索或扫描进行第二次读取


索引ix没有显式地将SomeKey包含为辅助键列,但由于它不是唯一的,SQL Server会在后台静默地包含群集键,因此更新该列值可以移动其中的行。

源表中“idsku”的数据类型是什么?不知道,如果可能是不同的数据类型,则可能是不同的实际值转换为相同的int。能否发布显示创建和插入的整个脚本?是否可能是创建临时表时出错?因为临时表已经存在,因此插入与现有数据冲突?@GSerg如果计划中没有任何一种形式的聚合。我想一定有一个独特的约束,确保没有重复,所以它得到了优化。所以并发活动肯定会导致这种情况。如果一个键列被更新,使得一行在被查找的索引中向上移动并被读取两次,那么这种情况也可能在读取提交时发生。我在一致性方面很灵活,但我已经多次看到这个问题,我总是使用ignore_dup_键,但我决定今天就真正尝试理解它,而你已经来解救了。这真是太棒了。再次感谢。回答得很好,它真的帮助我理解了这个潜在的问题。我做过很多这样的查询,但几乎都是从单独的报表数据库/仓库中进行的,在这些数据库/仓库中,数据基本上是只读的,所以我在现实生活中从未碰到过它。
CREATE NONCLUSTERED INDEX [IX_Price_idMarketplace_INC_idIsbn_lowestPrice_priceDate] ON [dbo].[Price]
(
    [idMarketplace] ASC
)
INCLUDE (   [idIsbn],
    [lowestPrice],
    [priceDate]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_Price_idMarketplace_priceChangedDate_INC_idIsbn_lowestPrice] ON [dbo].[Price]
(
    [idMarketplace] ASC,
    [priceChangedDate] ASC
)
INCLUDE (   [idIsbn],
    [lowestPrice]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
USE tempdb;

CREATE TABLE Price
  (
     SomeKey          INT PRIMARY KEY CLUSTERED,
     idIsbn           INT IDENTITY UNIQUE,
     idMarketplace    INT DEFAULT 3100,
     lowestPrice      SMALLMONEY DEFAULT $1.23,
     priceChangedDate DATETIME DEFAULT GETDATE()
  );

CREATE NONCLUSTERED INDEX ix
  ON Price(idMarketplace)
  INCLUDE (idIsbn, lowestPrice, priceChangedDate);

INSERT INTO Price
            (SomeKey)
SELECT number
FROM   master..spt_values
WHERE  number BETWEEN 1 AND 2000
       AND type = 'P'; 
USE tempdb;

WHILE 1=1
BEGIN
UPDATE Price SET SomeKey = 2001 WHERE SomeKey = 1
UPDATE Price SET SomeKey = 1 WHERE SomeKey = 2001
END
USE tempdb;

CREATE TABLE #P
  (
     idIsbn           INT NOT NULL PRIMARY KEY,
     price            SMALLMONEY,
     priceChangedDate DATETIME
  );

WHILE 1 = 1
  BEGIN
      TRUNCATE TABLE #P

      INSERT INTO #P
                  (idIsbn,
                   price,
                   priceChangedDate)
      SELECT idIsbn,
             MIN(lowestPrice),
             MIN(priceChangedDate)
      FROM   Price p
      WHERE  p.idMarketplace = 3100
      GROUP  BY p.idIsbn
  END