为自引用表创建测试负载

时间:2013-07-01 08:38:24

标签: performance tsql sql-server-2008-r2 self-referencing-table

我必须进行一些SQL Server 2008 R2性能测试,只使用SSMS和SQL Server非常方便,无需额外的应用程序支持。

我要做的一项测试是查询内容未知的自引用表(树状结构)。因此,首先,我必须在此表中加载100K-1M随机父子相关行等内容。

CREATE TABLE Test2 (
    ID int IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL,
    ParentID int NULL REFERENCES Test2 (ID))

我目前正在尝试使用SSMS和此脚本将10K行加载到表中:

SET NOCOUNT ON

INSERT INTO Test2 (ParentID)
VALUES (NULL)

DECLARE @n int = 0

;WHILE(1=1)
BEGIN
  --PRINT @n
  INSERT INTO Test2 (ParentID)
  SELECT TOP 1 ID FROM Test2 ORDER BY NEWID()

  SET @n = @n + 1
  IF(@n >= 9999)
    BREAK
END

SET NOCOUNT OFF

我的问题是它在我的笔记本电脑上运行了2m 45s。您可以想象以这种方式加载100K甚至1M记录需要多长时间。

我想有一种更快的方法使用TSQL将这种随机树状结构加载到数据库表中吗?

修改 在Mitch Wheat的建议之后,我取代了

SELECT TOP 1 ID FROM Test2 ORDER BY NEWID()

SELECT TOP 1 ID FROM Test2 
WHERE ID >= RAND(CHECKSUM(NEWID())) * (SELECT MAX(ID) FROM Test2) 

关于随机行选择,结果看起来确实是均匀分布的。执行时间从160秒降至5秒(!) - >这使我能够在〜60秒内插入100K记录。但是,使用我的RBAR脚本插入1M记录仍然非常慢,我仍在搜索可能的基于集合的表达式来填充我的表。如果它存在。

现在,在填充随机数据约10分钟后,我有1M行。它很慢但可以接受。 但是,要使用批量插入将此数据复制到另一个表,需要< 10s。

SELECT * 
INTO Test3
FROM Test2

因此,我认为某种形式的批量插入可以加快这一过程。

3 个答案:

答案 0 :(得分:1)

您并没有真正使用发布的代码测量INSERT性能。

使用ORDER BY子句选择单个随机行,如下所示:

SELECT TOP 1 * FROM table ORDER BY NEWID()

甚至

SELECT TOP 1 * FROM table ORDER BY CHECKSUM(NEWID()) 

执行表扫描(因为显然需要在排序行之前计算与每行关联的随机值),这对于大型表来说可能很慢。使用索引整数列(例如常用于主键的整数列),并使用:

SELECT TOP 1 * FROM table 
WHERE rowid >= RAND(CHECKSUM(NEWID())) * (SELECT MAX(rowid) FROM table) 

在常规时间内工作,前提是rowid列已编入索引。注意:这假设rowid均匀分布在0..MAX(rowid)范围内。如果您的数据集具有其他分布,则结果将会偏斜(即,某些行的选择频率会高于其他行)。

答案 1 :(得分:1)

我最终使用我的原始方法进行了一些调整:

  • 在插入之前禁用参考约束并在之后重新启用
  • 使用批量插入作为Mitch Wheat建议

这是架构:

DROP TABLE Test2
GO

CREATE TABLE Test2 (
    ID int IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL,
    ParentID int NULL /*REFERENCES Test2 (ID)*/
)
GO

ALTER TABLE Test2 
  ADD CONSTRAINT FK_SelfRef
    FOREIGN KEY(ParentID) REFERENCES Test2 (ID)
GO

脚本:

CHECKPOINT;
DBCC DROPCLEANBUFFERS;

SET NOCOUNT ON

ALTER TABLE Test2 NOCHECK CONSTRAINT FK_SelfRef

INSERT INTO Test2 (ParentID)
VALUES (NULL)

DECLARE @n int = 1

;WHILE(1=1)
BEGIN
  INSERT INTO Test2 (ParentID)
  SELECT ID FROM Test2 ORDER BY NEWID()

  SELECT @n = COUNT(*) FROM Test2

  IF(@n >= 999999)
    BREAK
END

ALTER TABLE dbo.Test2 WITH CHECK CHECK CONSTRAINT FK_SelfRef

SET NOCOUNT OFF

这在10秒内执行,我无法用其他任何方法快速完成。

注意:它会插入超出需要的记录。但是该方法可以通过限制最后一次传递中的插入次数来安排精确的记录数。

答案 2 :(得分:0)

当从先前插入的行中随机分配父级时,无法控制树高(级别数)和填充的方式级别,这在某些情况下可能是不可取的。

使用数据级别逐层填充树可能更方便。

使用辅助表值函数生成数字序列,使用Itzik的交叉连接CTE方法(参见例如here关于它)

create function ftItziksCJCTE
(
    @cnt int
)
returns table as
return
(
    WITH
        E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
        E(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
    select N from E where N <= @cnt
)

用于控制树中元素分布的简单表:

create table #TreeLevels
(
    LevelNo int identity(1, 1) not NULL,
    MinElements int not NULL,
    MaxElements int not NULL,
    primary key clustered (LevelNo)
)

样品分发:

insert into #TreeLevels values (7, 10)
insert into #TreeLevels values (70, 100)
insert into #TreeLevels values (700, 1000)

将为我们提供类似7到10个元素的内容,其中ParentID = NULL,每个元素都有70到100个元素等等。元素总数343000到1000000

或其他分发:

insert into #TreeLevels values (1, 1)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)
insert into #TreeLevels values (9, 15)
insert into #TreeLevels values (10, 12)

意味着将存在具有9到15个子元素之间的单个根元素,每个子元素具有10到12个元素等等。

然后树可以逐层填充:

declare @levelNo int, @eMin int, @eMax int

create table #Inserted (ID int not NULL, primary key nonclustered (ID))
create table #Inserted2 (ID int not NULL, primary key nonclustered (ID))

set @levelNo = 1
while 1=1
begin
    select @eMin = MinElements, @eMax = MaxElements from #TreeLevels where LevelNo = @levelNo

    if @@ROWCOUNT = 0
        break

    if @levelNo = 1
    begin
        insert into TestTree (ParentID)
        output inserted.ID into #Inserted (ID)
        select NULL from ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0))
    end
    else
    begin
        if exists (select 1 from #Inserted)
        begin
            insert into TestTree (ParentID)
            output inserted.ID into #Inserted2 (ID)
            select
                I.ID
            from
                #Inserted I
                cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F

            truncate table #Inserted
        end
        else
        begin
            insert into TestTree (ParentID)
            output inserted.ID into #Inserted (ID)
            select
                I.ID
            from
                #Inserted2 I
                cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F

            truncate table #Inserted2
        end
    end

    set @levelNo = @levelNo + 1
end

但是,无法控制树将包含的元素的确切数量,并且叶节点仅在最后一级。最好有额外的参数控制级别填充(在同一级别上将有子级的节点百分比)。

create table #TreeLevels
(
    LevelNo int identity(1, 1) not NULL,
    MinElements int not NULL,
    MaxElements int not NULL,
    PopulatedPct float NULL,
    primary key clustered (LevelNo)
)

样品分发:

insert into #TreeLevels values (1, 1, NULL)
insert into #TreeLevels values (9, 15, NULL)
insert into #TreeLevels values (10, 12, NULL)
insert into #TreeLevels values (9, 15, 80)
insert into #TreeLevels values (10, 12, 65)
insert into #TreeLevels values (9, 15, 35)
insert into #TreeLevels values (10, 12, NULL)

PopulatedPct百分比的NULL被视为100%。 PopulatedPct控制下一级别的人口,并应在周期中从之前的级别获取。此外,它对#TreeLevels中的最后一行没有任何意义。

现在我们可以考虑使用PopulatedPct来循环低谷。

declare @levelNo int, @eMin int, @eMax int

create table #Inserted (ID int not NULL, primary key nonclustered (ID))
create table #Inserted2 (ID int not NULL, primary key nonclustered (ID))

set @levelNo = 1
while 1=1
begin
    select @eMin = MinElements, @eMax = MaxElements from #TreeLevels where LevelNo = @levelNo

    if @@ROWCOUNT = 0
        break

    if @levelNo = 1
    begin
        insert into TestTree (ParentID)
        output inserted.ID into #Inserted (ID)
        select NULL from ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0))
    end
    else
    begin
        declare @pct float
        select @pct = PopulatedPct from #TreeLevels where LevelNo = @levelNo - 1

        if exists (select 1 from #Inserted)
        begin
            if (@pct is NULL)
                insert into TestTree (ParentID)
                output inserted.ID into #Inserted2 (ID)
                select
                    I.ID
                from
                    #Inserted I
                    cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
            else
                insert into TestTree (ParentID)
                output inserted.ID into #Inserted2 (ID)
                select
                    I.ID
                from
                    (select top (@pct) PERCENT ID from #Inserted order by rand(checksum(newid()))) I
                    cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F

            truncate table #Inserted
        end
        else
        begin
            if (@pct is NULL)
                insert into TestTree (ParentID)
                output inserted.ID into #Inserted (ID)
                select
                    I.ID
                from
                    #Inserted2 I
                    cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F
            else
                insert into TestTree (ParentID)
                output inserted.ID into #Inserted (ID)
                select
                    I.ID
                from
                    (select top (@pct) PERCENT ID from #Inserted2 order by rand(checksum(newid()))) I
                    cross apply ftItziksCJCTE(round(rand(checksum(newid())) * (@eMax - @eMin) + @eMin, 0)) F

            truncate table #Inserted2
        end
    end

    set @levelNo = @levelNo + 1
end

仍无法控制元素的确切数量,但可以更好地控制树形状。