使用“While Exists”循环缓慢插入

时间:2013-09-25 10:43:57

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

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

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

  2. 目标表具有聚簇索引(PK)。该字段应该是Identity,但DB的开发人员(我们无法更改它,因为它会影响程序)将其创建为Integer。每次程序需要向表中添加一行时,请查看最大ID(在此情况下为historyno)并加1。

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

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

  5. 我们使用的代码如下。如果有人有任何想法改进它,请欣赏。

    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
    

3 个答案:

答案 0 :(得分:2)

显然,正确的答案是将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

虽然

可能必须在事务中锁定表格

答案 1 :(得分:1)

您可以选择应插入临时表的数据,其中包含由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

答案 2 :(得分:0)

您永远不应该使用您用于分配索引的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有多大。如果它很大,那么你可能会遇到性能问题(特别是如果“插入”字段没有索引)

您可以从创建包含相关列的表变量开始。

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

primary key(...)
)

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

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

您不会使用游标,也不会进行大型批处理操作