0

Мне нужно выполнить некоторые тесты производительности SQL Server 2008 R2, и было бы очень удобно это делать, используя только SSMS и SQL Server без дополнительной поддержки приложений.Создание тестовой нагрузки для таблицы саморегуляции

Один из тестов, который мне нужно сделать, - это запросить таблицу саморегуляции (древовидную структуру) с неизвестным контентом. Итак, для начала мне пришлось бы загружать в эту таблицу примерно как 100 КБ - 1 М случайные родительские дочерние строки.

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?

EDIT: После внушения Митч пшеницы, я заменил

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 с (!) -> это позволяет мне вставлять 100 тыс. Записей в ~ 60 секунд. Однако вставка 1M записей с использованием моего сценария RBAR все еще очень медленная, и я все еще ищу возможное выражение на основе набора для заполнения моей таблицы. Если он существует.

Теперь, после ~ 10 минут заполнения случайных данных у меня есть 1M строк. Он медленный, но приемлемый. Однако, чтобы скопировать эти данные в другую таблицу, используя пакетную вставку, требуется < 10s.

SELECT * 
INTO Test3 
FROM Test2 

Итак, я считаю, что в какой-то форме пакетная вставка может ускорить процесс.

+0

«SELECT TOP 1 ID FROM Test2 ORDER BY NEWID()« <- не будет очень эффективным ... –

+0

@MitchWheat: есть ли более быстрый способ получить случайную строку из таблицы? – OzrenTkalcecKrznaric

+1

Да: http://mitch-wheat.blogspot.com.au/2011/08/t-sql-generating-random-numbers-random.html –

ответ

1

Я закончил с использованием моего оригинального ПОДХОД с некоторыми ухищрений:

  • отключив ссылочное ограничение, прежде чем вставка и повторное включение после
  • с использованием пакетных вставок, как Митч пшеницы предложил

Это схема:

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 секунд, и я не могу сделать это быстро с помощью любого другого метода.

ПРИМЕЧАНИЕ: Оно вставляет больше записей, чем необходимо. Но метод может быть устроен так, чтобы вставлять точное количество записей, ограничивая количество вставок в последнем проходе.

1

Вы на самом деле не измеряете производительность INSERT с помощью вашего опубликованного кода.

Выбор один случайную строки, используя ОКВЕК В следующим образом:

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). Если у вашего набора данных есть другое распределение, ваши результаты будут искажены (т. Е. Некоторые строки будут выбраны чаще других).

+0

Это значительно улучшает время исполнения! Я включу это в вопрос. Тем не менее, мой запрос по-прежнему является RBAR, и я ищу что-то лучше, если оно существует. – OzrenTkalcecKrznaric

+1

Вы не можете описать INSERT как RBAR: вставка по строкам по определению! Если вы не выполняете пакетную вставку ... –

+0

Возможно ли вставить пакетную вставку? Или серии пакетных вставок, каждая итерация партии удваивается .. hm. – OzrenTkalcecKrznaric

0

Когда родитель назначается случайным образом из ранее вставленных строк, нет контроля над высотой дерева (количеством уровней) и уровнем заполнения уровней, что может быть нежелательным в некоторых сценариях.

Возможно, более удобно заполнить дерево уровнем данных по уровню.

Вспомогательная таблица функция берется для создания порядковые номера, используя крестик Ицик присоединился метод КТР (см, например, 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) 

NULL, для PopulatedPct процента рассматривается как 100%. PopulatedPct контролирует популяцию на следующем уровне и должен быть взят с предыдущего уровня во время цикла. Также он не имеет значения для последней строки в #TreeLevels.

Теперь мы можем циклически контролировать уровни, учитывающие PopulationPct.

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 

По-прежнему отсутствует контроль над точным количеством элементов, но достигается лучший контроль над формой дерева.

+0

Это очень интересный ответ, учитывая, что я вообще не думал о форме дерева. Я должен проверить его еще, но я понимаю. +1 – OzrenTkalcecKrznaric

+0

Я проверил ваши сценарии. Первый вариант заполнял приблизительно 550 тыс. Строк в трех тестовых проходах с временем выполнения 10 с. Второй вариант заполняет 530K строк в 11 секунд. Распределение кажется одобренным. Глубина дерева равномерно распределена по ветвям, а мой метод не имеет такого распределения. Однако этот метод на 40-50% медленнее, чем мой простой пакетный встав. Есть ли способ переставить его несколько быстрее? – OzrenTkalcecKrznaric

+0

Когда вы делали тесты, отключили ли вы FK_SelfRef, как в вашем методе? Если нет, возможно, это поможет. Если да, то, кажется, я не вижу путей его улучшения (в настоящее время я доволен). –