Оптимизация создания таблицы чисел на SQL Server? - PullRequest
6 голосов
/ 01 февраля 2011

Запуск SQL Server Express 2008. Я создал «таблицу чисел» для некоторых служебных функций.Поскольку заполнение таблицы является частью автоматической сборки, каждый раз при развертывании объекта требуется непомерное количество времени.

С риском "чрезмерной оптимизации", кто-нибудь может прокомментировать, как я могу сделать это как можно быстрее?Может, играть с коэффициентом заполнения индекса или при создании ПК?

IF EXISTS (SELECT *  FROM dbo.sysobjects 
WHERE id = OBJECT_ID(N'Numbers') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
drop TABLE [Numbers]
end

CREATE TABLE [Numbers]
(
      [Number] [int]
    , CONSTRAINT [Index_Numbers] PRIMARY KEY CLUSTERED
        (
            [number] ASC
        ) ON [PRIMARY]
) 
ON [PRIMARY]
Declare @cnt int
Select @cnt=0
SET NOCOUNT ON
while (@cnt<10000)
BEGIN
INSERT INTO NUMBERS(NUMBER) SELECT @cnt
SELECT @cnt=@cnt+1

end

Ответы [ 4 ]

12 голосов
/ 01 февраля 2011

SQL, Вспомогательная таблица чисел Джефф Моден. Этот был самым быстрым

--===== 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
   INTO #Tally4
   FROM cteTally
  WHERE N <= 1000000;
GO
4 голосов
/ 17 мая 2013

Нужно сделать несколько тестов, оборачивая вещи в функции и используя разные сценарии для заполнения таблицы.

Опции

Предлагаемое решение Микаэля можно улучшить, удалив предложение 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

Тесты

  1. Вставка во временную кучу самая быстрая (600),
  2. вставка во временную таблицу с кластерным индексом PK медленнее (3000) и, наконец,
  3. заполнение физической таблицы с индексом самого медленного (10000). Дополнительное сравнение сделано с
  4. заполнение временной таблицы с идентификатором PK, как предположил Грегори, из того же поколения строк в стиле CTE почти равно (3000) производительности кластерной температуры # 2.
  5. Предложение Грегори было слишком медленным для рассмотрения (21000). Чтобы удивить вас,
  6. исходный пример не будет реалистичным измерить миллионом строк; Вместо этого было использовано 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).

Предложение

Согласно измерениям вы получите

  1. с использованием решения на основе CTE + JOIN + ROW_NUMBER + TOP, в отличие от циклов или рекурсивного CTE,
  2. не использует индекс - использует кучу, а
  3. с использованием временной таблицы, а не физической таблицы.

Вы можете вообще избегать физических или временных таблиц и просто использовать функцию , но тогда в ваших последующих запросах вы будете выбирать оптимизатор, поскольку будете летать без статистики по числам. Затем вы можете решить эту проблему с помощью подсказок запроса, что нехорошо, но работает ... Хотя, глядя на план выполнения, номера строк, по крайней мере, для dbo.Numbers оценены правильно.

0 голосов
/ 01 февраля 2011

Я не уверен, что согласен с архитектурой, с высокого уровня, но, так как вы просто прикрепляете одно поле с числами от 0 до 9999, вы можете изменить определение таблицы, чтобы она автоматически добавлялась кначать с 0. Затем нужно вставить значение по умолчанию в таблицу.Это должно быть очень быстро.

Вы также можете создать прототип таблицы с правильными строками, а затем вставить 10000, "скопировав" строки.Это означает еще одну таблицу, которая ничего не делает, что может или не может решить проблему.

Просто две мысли.

0 голосов
/ 01 февраля 2011

Одна простая вещь, которую вы можете сделать, это отложить создание индекса до тех пор, пока не будут заполнены все числа.Другими словами, удалите его из CREATE TABLE и добавьте оператор CREATE INDEX после цикла.Это предотвратит обновление индекса при каждой вставке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...