在 SQL 服务器中批量插入忽略和 return id 的最佳方法?
Best way to do batch insert ignore and return id in SQL Server?
当我从不同来源(Json 文件、其他数据库和 REST API)导入各种数据时,我需要对它们进行重复数据删除,首先我将它们加载到一个 table 中,它定义了为它们键入并将数据存储为 Json,因此稍后当我 运行 批处理时,我可以查找类型并将数据插入到 suitable table秒。导入的行数不同(每种类型去不同 table/tables),但总是超过 100 万(如果我将它们以 Json 格式放在单个 [=64 中,总共约 10G 数据) =] 使用 VARCHAR(MAX)
).
正如我提到的,我需要处理重复项,因此我尝试为目标 table 定义唯一索引并启用 Ignore Duplicate Keys
,当我 'only' 时会发出警告插入现有数据。问题是,这只在少数情况下有效。大多数时候,我需要使用 5+ varchar(255)
个字段,并且由于限制(900 字节,src),我无法将它们添加到唯一索引。
我苦苦挣扎的另一件事是,在批量插入期间,我需要插入关系数据,这意味着一个 table 将具有另一个的外键。所以首先我需要处理依赖关系,在我得到它们插入的 ID 之后,使用它们我可以插入数据。就像一个产品有一个制造商,所以我首先插入当前批次中的所有制造商名称,然后使用这些 ID 插入产品。
需要 returning ID 并执行重复数据删除导致我想实现的查询:
- 将运行并发,按8-16个线程
- 应该return插入的Id
- 应该只插入数据如果之前没有被另一个线程插入(或者之前根本没有插入)
首先,我尝试通过创建如下存储过程来处理此问题:
- 尝试select数据,如果找到,returnId
- 如果没有找到,开始交易
- 再次检查它是否已被另一个线程插入。
- 如果没有,请插入并 return 新 ID。
此代码示例。:
CREATE PROCEDURE [dbo].usp_insert_pdproductdetails
@GDDataSourceVersionId INT,
@ManufacturerNameId BIGINT,
@ManufacturerReference NVARCHAR(255),
@PropertiesJson NVARCHAR(MAX),
@OriginalContentPage NVARCHAR(MAX),
@NewId BIGINT OUT
AS
BEGIN
SET NOCOUNT ON;
SELECT @NewId = [Id] FROM PDProductDetails
WHERE GDDataSourceVersionId = @GDDataSourceVersionId AND
ManufacturerId = @ManufacturerNameId AND
ManufacturerReference = @ManufacturerReference;
IF @NewId IS NULL
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT @NewId = [Id] FROM PDProductDetails
WHERE GDDataSourceVersionId = @GDDataSourceVersionId AND
ManufacturerId = @ManufacturerNameId AND
ManufacturerReference = @ManufacturerReference;
IF @NewId IS NULL
BEGIN
INSERT INTO PDProductDetails (GDDataSourceVersionId, ManufacturerId, ManufacturerReference, PropertiesJson, OriginalContentPage)
VALUES(@GDDataSourceVersionId, @ManufacturerNameId, @ManufacturerReference, @PropertiesJson, @OriginalContentPage);
SELECT @NewId = SCOPE_IDENTITY();
END
COMMIT TRANSACTION
END
SELECT @NewId;
END
GO
多个线程会调用它并插入产品详细信息。但是,使用它我很快陷入僵局。我改为使用 Merge:
的不同方法
CREATE PROCEDURE [dbo].usp_insert_pdproductdetails
@GDDataSourceVersionId INT,
@ManufacturerNameId BIGINT,
@ManufacturerReference NVARCHAR(255),
@PropertiesJson NVARCHAR(MAX),
@OriginalContentPage NVARCHAR(MAX),
@NewId BIGINT OUT
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
MERGE
INTO [dbo].[PDProductDetails] T
USING (SELECT @GDDataSourceVersionId, @ManufacturerNameId, @ManufacturerReference, @PropertiesJson, @OriginalContentPage)
AS Source (GDDataSourceVersionId, ManufacturerNameId, ManufacturerReference, PropertiesJson, OriginalContentPage)
ON T.GDDataSourceVersionId = Source.GDDataSourceVersionId AND
T.ManufacturerId = Source.ManufacturerNameId AND
T.ManufacturerReference = Source.ManufacturerReference
WHEN NOT MATCHED THEN
INSERT (GDDataSourceVersionId, ManufacturerId, ManufacturerReference, PropertiesJson, OriginalContentPage)
VALUES(Source.GDDataSourceVersionId, Source.ManufacturerNameId,
Source.ManufacturerReference, Source.PropertiesJson, Source.OriginalContentPage);
COMMIT TRANSACTION;
SELECT @NewId = [Id] FROM PDProductDetails (NOLOCK)
WHERE GDDataSourceVersionId = @GDDataSourceVersionId AND
ManufacturerId = @ManufacturerNameId AND
ManufacturerReference = @ManufacturerReference;
SELECT @NewId;
END
GO
这总是合并行和 selects 之后。它仍然死锁困难,不像另一个那么快,但仍然如此。
如何实现 insert ignore 和 return inserted id 功能,在并发环境下不会死锁?
在 @ta.speot.is 提到您可以通过合并执行 OUTPUT 之后,我搜索了如何将其分配给变量和 answer mentioned it.
我使用了这个存储过程。:
CREATE PROCEDURE [dbo].usp_insert_pdproductdetails
@GDDataSourceVersionId INT,
@ManufacturerNameId BIGINT,
@ManufacturerReference NVARCHAR(255),
@PropertiesJson NVARCHAR(MAX),
@OriginalContentPage NVARCHAR(MAX),
@NewId BIGINT OUT
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
MERGE
INTO [dbo].[PDProductDetails] T
USING (SELECT @GDDataSourceVersionId, @ManufacturerNameId, @ManufacturerReference, @PropertiesJson, @OriginalContentPage)
AS Source (GDDataSourceVersionId, ManufacturerNameId, ManufacturerReference, PropertiesJson, OriginalContentPage)
ON T.GDDataSourceVersionId = Source.GDDataSourceVersionId AND
T.ManufacturerId = Source.ManufacturerNameId AND
T.ManufacturerReference = Source.ManufacturerReference
WHEN MATCHED THEN
UPDATE SET @NewId = T.Id
WHEN NOT MATCHED THEN
INSERT (GDDataSourceVersionId, ManufacturerId, ManufacturerReference, PropertiesJson, OriginalContentPage)
VALUES(Source.GDDataSourceVersionId, Source.ManufacturerNameId,
Source.ManufacturerReference, Source.PropertiesJson, Source.OriginalContentPage);
SET @NewId = ISNULL(@NewId, SCOPE_IDENTITY());
COMMIT TRANSACTION;
SELECT @NewId;
END
GO
编辑:如@ta.speot.is 所述,使用相同的方法使用 table 值参数进行批处理请求会更好(MERGE 将使用 table 作为来源输入)。
当我从不同来源(Json 文件、其他数据库和 REST API)导入各种数据时,我需要对它们进行重复数据删除,首先我将它们加载到一个 table 中,它定义了为它们键入并将数据存储为 Json,因此稍后当我 运行 批处理时,我可以查找类型并将数据插入到 suitable table秒。导入的行数不同(每种类型去不同 table/tables),但总是超过 100 万(如果我将它们以 Json 格式放在单个 [=64 中,总共约 10G 数据) =] 使用 VARCHAR(MAX)
).
正如我提到的,我需要处理重复项,因此我尝试为目标 table 定义唯一索引并启用 Ignore Duplicate Keys
,当我 'only' 时会发出警告插入现有数据。问题是,这只在少数情况下有效。大多数时候,我需要使用 5+ varchar(255)
个字段,并且由于限制(900 字节,src),我无法将它们添加到唯一索引。
我苦苦挣扎的另一件事是,在批量插入期间,我需要插入关系数据,这意味着一个 table 将具有另一个的外键。所以首先我需要处理依赖关系,在我得到它们插入的 ID 之后,使用它们我可以插入数据。就像一个产品有一个制造商,所以我首先插入当前批次中的所有制造商名称,然后使用这些 ID 插入产品。
需要 returning ID 并执行重复数据删除导致我想实现的查询:
- 将运行并发,按8-16个线程
- 应该return插入的Id
- 应该只插入数据如果之前没有被另一个线程插入(或者之前根本没有插入)
首先,我尝试通过创建如下存储过程来处理此问题:
- 尝试select数据,如果找到,returnId
- 如果没有找到,开始交易
- 再次检查它是否已被另一个线程插入。
- 如果没有,请插入并 return 新 ID。
此代码示例。:
CREATE PROCEDURE [dbo].usp_insert_pdproductdetails
@GDDataSourceVersionId INT,
@ManufacturerNameId BIGINT,
@ManufacturerReference NVARCHAR(255),
@PropertiesJson NVARCHAR(MAX),
@OriginalContentPage NVARCHAR(MAX),
@NewId BIGINT OUT
AS
BEGIN
SET NOCOUNT ON;
SELECT @NewId = [Id] FROM PDProductDetails
WHERE GDDataSourceVersionId = @GDDataSourceVersionId AND
ManufacturerId = @ManufacturerNameId AND
ManufacturerReference = @ManufacturerReference;
IF @NewId IS NULL
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT @NewId = [Id] FROM PDProductDetails
WHERE GDDataSourceVersionId = @GDDataSourceVersionId AND
ManufacturerId = @ManufacturerNameId AND
ManufacturerReference = @ManufacturerReference;
IF @NewId IS NULL
BEGIN
INSERT INTO PDProductDetails (GDDataSourceVersionId, ManufacturerId, ManufacturerReference, PropertiesJson, OriginalContentPage)
VALUES(@GDDataSourceVersionId, @ManufacturerNameId, @ManufacturerReference, @PropertiesJson, @OriginalContentPage);
SELECT @NewId = SCOPE_IDENTITY();
END
COMMIT TRANSACTION
END
SELECT @NewId;
END
GO
多个线程会调用它并插入产品详细信息。但是,使用它我很快陷入僵局。我改为使用 Merge:
的不同方法CREATE PROCEDURE [dbo].usp_insert_pdproductdetails
@GDDataSourceVersionId INT,
@ManufacturerNameId BIGINT,
@ManufacturerReference NVARCHAR(255),
@PropertiesJson NVARCHAR(MAX),
@OriginalContentPage NVARCHAR(MAX),
@NewId BIGINT OUT
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
MERGE
INTO [dbo].[PDProductDetails] T
USING (SELECT @GDDataSourceVersionId, @ManufacturerNameId, @ManufacturerReference, @PropertiesJson, @OriginalContentPage)
AS Source (GDDataSourceVersionId, ManufacturerNameId, ManufacturerReference, PropertiesJson, OriginalContentPage)
ON T.GDDataSourceVersionId = Source.GDDataSourceVersionId AND
T.ManufacturerId = Source.ManufacturerNameId AND
T.ManufacturerReference = Source.ManufacturerReference
WHEN NOT MATCHED THEN
INSERT (GDDataSourceVersionId, ManufacturerId, ManufacturerReference, PropertiesJson, OriginalContentPage)
VALUES(Source.GDDataSourceVersionId, Source.ManufacturerNameId,
Source.ManufacturerReference, Source.PropertiesJson, Source.OriginalContentPage);
COMMIT TRANSACTION;
SELECT @NewId = [Id] FROM PDProductDetails (NOLOCK)
WHERE GDDataSourceVersionId = @GDDataSourceVersionId AND
ManufacturerId = @ManufacturerNameId AND
ManufacturerReference = @ManufacturerReference;
SELECT @NewId;
END
GO
这总是合并行和 selects 之后。它仍然死锁困难,不像另一个那么快,但仍然如此。
如何实现 insert ignore 和 return inserted id 功能,在并发环境下不会死锁?
在 @ta.speot.is 提到您可以通过合并执行 OUTPUT 之后,我搜索了如何将其分配给变量和 answer mentioned it.
我使用了这个存储过程。:
CREATE PROCEDURE [dbo].usp_insert_pdproductdetails
@GDDataSourceVersionId INT,
@ManufacturerNameId BIGINT,
@ManufacturerReference NVARCHAR(255),
@PropertiesJson NVARCHAR(MAX),
@OriginalContentPage NVARCHAR(MAX),
@NewId BIGINT OUT
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
MERGE
INTO [dbo].[PDProductDetails] T
USING (SELECT @GDDataSourceVersionId, @ManufacturerNameId, @ManufacturerReference, @PropertiesJson, @OriginalContentPage)
AS Source (GDDataSourceVersionId, ManufacturerNameId, ManufacturerReference, PropertiesJson, OriginalContentPage)
ON T.GDDataSourceVersionId = Source.GDDataSourceVersionId AND
T.ManufacturerId = Source.ManufacturerNameId AND
T.ManufacturerReference = Source.ManufacturerReference
WHEN MATCHED THEN
UPDATE SET @NewId = T.Id
WHEN NOT MATCHED THEN
INSERT (GDDataSourceVersionId, ManufacturerId, ManufacturerReference, PropertiesJson, OriginalContentPage)
VALUES(Source.GDDataSourceVersionId, Source.ManufacturerNameId,
Source.ManufacturerReference, Source.PropertiesJson, Source.OriginalContentPage);
SET @NewId = ISNULL(@NewId, SCOPE_IDENTITY());
COMMIT TRANSACTION;
SELECT @NewId;
END
GO
编辑:如@ta.speot.is 所述,使用相同的方法使用 table 值参数进行批处理请求会更好(MERGE 将使用 table 作为来源输入)。