0

我必须做一些 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 秒内插入 10 万条记录。但是,使用我的 RBAR 脚本插入 1M 记录仍然很慢,我仍在寻找可能的基于集合的表达式来填充我的表。如果存在。

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

SELECT * 
INTO Test3
FROM Test2

所以,我相信某种形式的批量插入可以加快这个过程。

4

3 回答 3

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) 范围内。如果您的数据集有其他分布,您的结果将出现偏差(即,某些行将比其他行更频繁地选择)。

于 2013-07-01T08:55:06.447 回答
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 秒内执行,我无法用任何其他方法快速完成。

注意:它会插入比需要更多的记录。但是该方法可以通过限制最后一次插入的数量来安排插入确切的记录数。

于 2013-07-01T14:53:32.953 回答
0

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

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

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

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

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

于 2013-07-01T10:49:35.603 回答