【问题标题】:Slow insert with "While Exists" loop使用“While Exists”循环进行慢速插入
【发布时间】:2013-09-30 22:00:23
【问题描述】:

我正在尝试向表中插入大量记录。 这是场景:

  1. SQL Server 2008(数据库为 2005)

  2. 目标表具有聚集索引 (PK)。该字段应该是一个身份,但是数据库的开发人员(我们无法更改它,因为它会影响程序)将其创建为一个整数。每次程序需要向表中添加一行时,查看最大 id(在这种情况下为historyno)并求和。

  3. 当我们需要同时插入大量记录时,这会影响我们的性能,因此我们创建了一个流程来在生产时间之外从临时表 (AKT_ES_CampTool_TempHist) 中插入行。

  4. 问题是,在一小时内,它只插入了 8K 行。考虑到我们需要插入超过 120K,我们用完了几个小时。

我们使用的代码如下。请,如果有人有任何改进它的想法,将不胜感激。

DECLARE             @HistNo         AS INT

WHILE EXISTS (SELECT * FROM AKT_ES_CampTool_TempHist WHERE Inserted = 0)
BEGIN

    SELECT  @HistNo=MIN(HistoryNo) FROM AKT_ES_CampTool_TempHist WHERE Inserted = 0

    INSERT INTO NOVADB.dbo.niHist   (
        HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo, 
        Time,Type,Priority,Collector,Code,
        Action,RemainingAmount,Obliterated,SubType,ActSegment,
        Data,FreetextData,quantity
                                        )
        SELECT      
        (SELECT  max(historyNo)+1 
        FROM    NOVADB..niHist),ObjectType,ObjectNo,SubNo,ReferenceNo,
        Time,Type,Priority,Collector,Code,
        Action,RemainingAmount,Obliterated,SubType,ActSegment,
        Data,FreetextData,quantity 
        FROM        AKT_ES_CampTool_TempHist 
        WHERE       HistoryNo=@HistNo

        UPDATE  AKT_ES_CampTool_TempHist
        SET     Inserted=1
        WHERE   HistoryNo=@HistNo

END

【问题讨论】:

  • 忘记说Temp表上的“HistoryNo”只是一个ID,与主表上的HistoryNo无关。

标签: sql-server performance loops insert while-loop


【解决方案1】:

显然,正确的答案是将 historyNo 列更改为一个标识,但由于您不能这样做,为什么不在整个集合中使用 ROW_NUMBER 来获得一个递增的数字以添加到 prev max historyNo 中?

然后您可以将插入更改为

DECLARE  @OldMaxHistNo  AS INT
SELECT @OldMaxHistNo = MAX(historyNo) FROM NOVADB..niHist

INSERT INTO NOVADB.dbo.niHist   (
    HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo, 
    Time,Type,Priority,Collector,Code,
    Action,RemainingAmount,Obliterated,SubType,ActSegment,
    Data,FreetextData,quantity
                                    )
    SELECT      
    @OldMaxHistNo+ ROW_NUMBER() OVER(ORDER BY ObjectNo)
    FROM    NOVADB..niHist),ObjectType,ObjectNo,SubNo,ReferenceNo,
    Time,Type,Priority,Collector,Code,
    Action,RemainingAmount,Obliterated,SubType,ActSegment,
    Data,FreetextData,quantity 
    FROM        AKT_ES_CampTool_TempHist 
    WHERE       Inserted = 0

    UPDATE  AKT_ES_CampTool_TempHist
    SET     Inserted=1

在执行此操作时可能必须锁定事务中的表

【讨论】:

  • 我以前也这样做过,但事务的问题是我会将表锁定,因此其他进程无法访问表(如果我错了,请纠正我)。我完全同意正确的方法是将 HistoryNo 转换为 Identity 的观点(也许在以后的版本中......)
  • 你会在那里,但事务(具有可序列化的隔离级别)是阻止另一个进程从表中读取(错误)最大 ID 然后尝试写入 +1 值的唯一方法这与大型数据集中的条目冲突。也许您可以使用混合解决方案-您在 while 循环中一次插入 1000 个,提交事务,并在每次迭代后开始另一个?我现在唯一的其他建议是永远不要插入 HistoryNo 值,而是让表上的触发器来解决它。
【解决方案2】:

您可以使用由 Rownumber() 生成的新 HistoryNo 并使用 max(historyNo) FROM NOVADB..niHist 更改来选择应插入临时表的数据。

SELECT  ROW_NUMBER() OVER (Order by ID) as NEW_HistoryNo , * 
into #tmp
FROM AKT_ES_CampTool_TempHist 
WHERE Inserted = 0
ORDER BY HistoryNo

Update #tmp set NEW_HistoryNo=NEW_HistoryNo + (SELECT  max(historyNo) FROM    NOVADB..niHist)

INSERT INTO NOVADB.dbo.niHist   (
        HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo, 
        Time,Type,Priority,Collector,Code,
        Action,RemainingAmount,Obliterated,SubType,ActSegment,
        Data,FreetextData,quantity )                                 )
SELECT      
        NEW_HistoryNo,ObjectType,ObjectNo,SubNo,ReferenceNo,
        Time,Type,Priority,Collector,Code,
        Action,RemainingAmount,Obliterated,SubType,ActSegment,
        Data,FreetextData,quantity
from #tmp

Update AKT_ES_CampTool_TempHist  set Inserted = 1
from #tmp
Where #tmp.HistoryNo=AKT_ES_CampTool_TempHist.HistoryNo and AKT_ES_CampTool_TempHist.Inserted = 0

Drop Table #tmp

【讨论】:

  • 您好,感谢您的回复。一次插入整个表很好,但是由于在这个表上插入行的进程很多,所以我们需要进行动态插入以避免重复PK的错误。
【解决方案3】:

您永远不应该使用您用于分配索引的 max+1 策略。假设您不能使用身份和主表,并且您没有使用最新版本的 sql server -- 基于身份字段创建影子表并使用它来生成序列号

create table AKT_ES_CampTool_Shadow
(
  id int identity(1234,1) not null -- replacing 1234 with a value based on max+1
, dummy varchar(1) null
)

然后生成一个 id -- 比 max+1 便宜 -- 没有锁定问题

create proc AKT_ES_CampTool_idgen(@newid output)
(
  declare @newid int
  begin tran

  insert into dbo.AKT_ES_CampTool_Shadow (dummy) values ('') 
  select @newid = scope_id()
  rollback  
)

你没有说 AKT_ES_CampTool_TempHist 有多大。如果它很大,您可能会在那里遇到性能问题(特别是如果“插入”字段上没有索引)

您可以首先创建一个包含相关列的表 var。

declare @TempHist table
(
  HistNo int
, inserted int
, etc.

primary key(...)
)

然后使用单个插入查询填充@TempHist。如果您没有适合此表的 PK,请使用生成的 RowID s PK

现在,您可以循环遍历此表而不会导致锁争用。只需从 @TempHist 中选择前 1 行,并在完成处理后从 @TempHist 中删除相应的行。

您不会使用游标,也不会进行大批量操作

【讨论】:

  • 抱歉,我没听懂... AKT_ES_CampTool_TempHist 是可变的,理想情况下,必须在一天开始时插入 0 行。到月底,我们曾经在表上有 300K-400K 行。此表上的插入从 200 到 100K。
  • 从未使用过 scope_identity() 并且不知道如何填充影子表。 AKT_ES_CampTool_TempHist 在 HistoryNo 上有一个 PK(IDENTITY 与 niHist 上的 HistoryNo 无关)。 History No 和 HistoryNo-Inserted 上的索引(在您告诉我之前没有插入的索引,现在是双倍速度,仍然很慢,但是是一种改进)
猜你喜欢
  • 2015-01-08
  • 1970-01-01
  • 2021-07-17
  • 2015-01-14
  • 2019-08-08
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多