Нужно сделать несколько тестов, оборачивая вещи в функции и используя разные сценарии для заполнения таблицы.
Опции
Предлагаемое решение Микаэля можно улучшить, удалив предложение WHERE WHERE N <= @count
и добавив предложение TOP TOP (@count)
. Это сокращает время его выполнения почти в шесть раз для меня в следующем:
create function dbo.Numbers2(@count bigint)
RETURNS TABLE RETURN
--===== Itzik's CROSS JOINED CTE method
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),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT TOP (@count) N
FROM cteTally
По сравнению с более медленной адаптацией:
create function dbo.Numbers3(@count bigint)
RETURNS TABLE RETURN
--===== Itzik's CROSS JOINED CTE method
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),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT N
FROM cteTally
WHERE N <= @count
Решение, предложенное в https://stackoverflow.com/a/14386663/481812, имеет производительность, аналогичную Numbers2:
create function dbo.Numbers(@count bigint)
RETURNS TABLE RETURN
with byte (n) as (
-- shortened, see the other answer for full code
Тесты
- Вставка во временную кучу самая быстрая (600),
- вставка во временную таблицу с кластерным индексом PK медленнее (3000) и, наконец,
- заполнение физической таблицы с индексом самого медленного (10000). Дополнительное сравнение сделано с
- заполнение временной таблицы с идентификатором PK, как предположил Грегори, из того же поколения строк в стиле CTE почти равно (3000) производительности кластерной температуры # 2.
- Предложение Грегори было слишком медленным для рассмотрения (21000). Чтобы удивить вас,
- исходный пример не будет реалистичным измерить миллионом строк; Вместо этого было использовано 10000, что в 100 раз меньше, чем в других тестах, чтобы довести его до разумного времени (15000). Предполагая, что он будет масштабироваться пропорционально - в лучшем случае - теоретически он должен завершить 1 м строк за 25 минут (1500000).
Предложение Бена (пробное), вероятно, имело бы значение, если бы данные не были отсортированы - здесь мы вставляем отсортированные данные, следовательно, всегда добавляемся, без увеличения производительности при построении индекса после.
Группа испытаний 1 - вставки временной кучи
CREATE TABLE #Numbers ([Number] [int])
insert #Numbers select n from dbo.Numbers(1000000)
drop table #Numbers
-- 480 - 900 ms, avg 600 ms
CREATE TABLE #Numbers ([Number] [int])
insert #Numbers select n from dbo.Numbers2(1000000)
drop table #Numbers
-- 440 - 800 ms, avg 550 ms
-- just in case you wondered how it scales, 30 times more went in in 14000,
-- Numbers and Numbers2 scale linearly
CREATE TABLE #Numbers ([Number] [int])
insert #Numbers select n from dbo.Numbers3(1000000)
drop table #Numbers
-- 2700 - 4000 ms, avg 3200 ms
Тестовая группа 2 - вставка во временный кластер
CREATE TABLE #Numbers ([Number] [int] primary key)
insert #Numbers select n from dbo.Numbers(1000000)
drop table #Numbers
-- 1900 - 4000 ms, avg 3000 ms
CREATE TABLE #Numbers ([Number] [int] primary key)
insert #Numbers select n from dbo.Numbers2(1000000)
drop table #Numbers
-- 1900 - 4000 ms, avg 3000 ms
CREATE TABLE #Numbers ([Number] [int] primary key)
insert #Numbers select n from dbo.Numbers3(1000000)
drop table #Numbers
-- 4000 - 7000 ms, avg 5000 ms
Группа испытаний 3 - вставка в кластер физических таблиц
CREATE TABLE PNumbers ([Number] [int] primary key)
insert PNumbers select n from dbo.Numbers(1000000)
drop table PNumbers
-- 7000 - 12000 ms, avg 10000 ms
CREATE TABLE PNumbers ([Number] [int] primary key)
insert PNumbers select n from dbo.Numbers2(1000000)
drop table PNumbers
-- 7000 - 12000 ms, avg 10000 ms
CREATE TABLE PNumbers ([Number] [int] primary key)
insert PNumbers select n from dbo.Numbers3(1000000)
drop table PNumbers
-- 7000 - 12000 ms, avg 10000 ms
Тест 4 - заполнение столбца временных кластеризованных идентификаторов из сгенерированных строк
CREATE TABLE #Numbers ([Number] [int] identity primary key, x bit)
INSERT INTO #Numbers(x) select 1 from dbo.Numbers2(1000000)
drop table #Numbers
-- 2000 - 4000 ms, avg 3000 ms
Тест 5 - заполнение столбца временных кластерных идентификаторов в цикле
CREATE TABLE #Numbers ([Number] [int] identity primary key)
declare @cnt int = 0
while (@cnt<1000000)
BEGIN
INSERT INTO #Numbers DEFAULT values
set @cnt = @cnt+1
END
drop table #Numbers
-- 20000 - 22000 ms, avg 21000 ms
Выводы
Параметры Numbers (n) и Numbers2 (n) работают одинаково (лучше всего). Numbers3 (n) намного медленнее из-за предложения WHERE. Другие протестированные сценарии слишком медленны для рассмотрения.
Вставка в неиндексированную временную кучу происходит быстрее всего. Как только вы добавляете какой-либо индекс (пробный) в столбец (либо кластеризованный PK, либо вторичный неуникальный), он больше не связан с генерацией чисел, а скорее со вставками индекса (и, возможно, с IO). Худшее - вставить в физическую таблицу. Это должно быть потому, что временные таблицы не обязательно записываются на диск (tempdb).
Предложение
Согласно измерениям вы получите
- с использованием решения на основе CTE + JOIN + ROW_NUMBER + TOP, в отличие от циклов или рекурсивного CTE,
- не использует индекс - использует кучу, а
- с использованием временной таблицы, а не физической таблицы.
Вы можете вообще избегать физических или временных таблиц и просто использовать функцию , но тогда в ваших последующих запросах вы будете выбирать оптимизатор, поскольку будете летать без статистики по числам. Затем вы можете решить эту проблему с помощью подсказок запроса, что нехорошо, но работает ... Хотя, глядя на план выполнения, номера строк, по крайней мере, для dbo.Numbers оценены правильно.