为什么按主键分组的插入会抛出主键约束违规错误?

时间:2016-03-30 19:53:06

标签: sql sql-server database

我有一个插入声明,表示主键错误,但我不知道如何插入重复的键值。

首先,我使用主键创建临时表。

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);

然后我从Price表中提取价格,按idIsbn分组,这是临时表中的主键。

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] NOT NULL

但是每次运行此查询时,我都会收到此错误:

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窗口中实际看到的内容。没有更多,也没有更少:

enter image description here

@@版本是:

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)

执行计划: enter image description here

以下是运行正常时的示例。在这里我使用READ COMMITTED,但它并不重要b / c我得到错误无论我是否读取它已提交或未提交。 enter image description here

这是另一个失败的例子,这次是READ COMMITTED。

enter image description here

此外:

  • 无论我是填充临时表还是填写临时表,我都会得到同样的错误 持久表。
  • 当我将option (maxdop 1)添加到插入的末尾时,它似乎每次都会失败,但我无法确定那个b / c我无法运行它以获得无穷大。但事实似乎就是这样。

以下是价格表的定义。表有25M行。过去一小时内有108,529次更新。

CREATE TABLE [dbo].[Price](
    [idPrice] [int] IDENTITY(1,1) NOT NULL,
    [idIsbn] [int] NOT NULL,
    [idMarketplace] [int] NOT NULL,
    [lowestPrice] [smallmoney] NULL,
    [offers] [smallint] NULL,
    [priceDate] [smalldatetime] NOT NULL,
    [priceChangedDate] [smalldatetime] NULL,
 CONSTRAINT [pk_Price] PRIMARY KEY CLUSTERED 
(
    [idPrice] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [uc_idIsbn_idMarketplace] UNIQUE NONCLUSTERED 
(
    [idIsbn] ASC,
    [idMarketplace] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

两个非聚集索引:

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

1 个答案:

答案 0 :(得分:7)

您还没有提供表格结构。

这是一个带有一些假定细节的repro,导致read committed问题(NB:现在你已经提供了我在案例中看到的定义priceChangedDate列的更新将在{{{ 1}}索引是否是被搜索者

连接1(设置表格)

IX_Price_idMarketplace_priceChangedDate_INC_idIsbn_lowestPrice

连接2

并发数据修改,将一行从搜索范围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'; 的开头移到结尾(3100,1),然后重复返回。

(3100,2001)

连接3(插入具有唯一约束的临时表)

USE tempdb;

WHILE 1=1
BEGIN
UPDATE Price SET SomeKey = 2001 WHERE SomeKey = 1
UPDATE Price SET SomeKey = 1 WHERE SomeKey = 2001
END

enter image description here

该计划没有汇总,因为idIsbn存在唯一约束(对idIsbn的唯一约束,idMarketplace也可以),因此可以优化out group,因为没有重复值。

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

索引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 并未明确包含ix作为辅助键列,但因为它未被声明为唯一SQL Server默认包含幕后的聚类键,因此更新该列值可以在其中移动行。