Случайный порядок по производительности - PullRequest
5 голосов
/ 13 июля 2011

Каков наилучший способ получить top n строк в случайном порядке?
Я использую запрос как:

Select top(10) field1,field2 .. fieldn
from Table1
order by checksum(newid())

Проблема в приведенном выше запросе заключается в том, что он будет работать медленнее с увеличением размера таблицы. он всегда будет выполнять полное сканирование кластеризованного индекса, чтобы найти top(10) строк в случайном порядке.

Есть ли другой лучший способ сделать это?

Ответы [ 4 ]

4 голосов
/ 13 июля 2011

Я проверил это и получил лучшую производительность при изменении запроса.

DDL для таблицы, которую я использовал в своих тестах.

CREATE TABLE [dbo].[TestTable]
(
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Col1] [nvarchar](100) NOT NULL,
    [Col2] [nvarchar](38) NOT NULL,
    [Col3] [datetime] NULL,
    [Col4] [nvarchar](50) NULL,
    [Col5] [int] NULL,
 CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
 (
    [ID] ASC
 )
)

GO

CREATE NONCLUSTERED INDEX [IX_TestTable_Col5] ON [dbo].[TestTable] 
(
    [Col5] ASC
)

В таблице 722888 строк.

Первый запрос:

select top 10
  T.ID,
  T.Col1,
  T.Col2,
  T.Col3,
  T.Col5,
  T.Col5
from TestTable as T
order by newid()

Статистика по первому запросу:

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 13 ms.

(10 row(s) affected)
Table 'TestTable'. Scan count 1, logical reads 12492, physical reads 14, read-ahead reads 6437, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 859 ms,  elapsed time = 1700 ms.

План выполнения первого запроса: enter image description here

Второй запрос:

select 
  T.ID,
  T.Col1,
  T.Col2,
  T.Col3,
  T.Col5,
  T.Col5
from TestTable as T
  inner join (select top 10 ID
              from TestTable
              order by newid()) as C
    on T.ID = C.ID

Статистика для второго запроса:

SQL Server parse and compile time: 
   CPU time = 125 ms, elapsed time = 183 ms.

(10 row(s) affected)
Table 'TestTable'. Scan count 1, logical reads 1291, physical reads 10, read-ahead reads 399, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 516 ms,  elapsed time = 706 ms.

План выполнения второго запроса: enter image description here

Резюме:

Второй запрос использует индекс на Col5, чтобы упорядочить строки на newid(), а затем выполняет кластерный поиск индекса 10 раз, чтобы получить значения для вывода.

Повышение производительности происходит потому, что индекс на Col5 уже кластеризованного ключа и вызывает меньше операций чтения.

Спасибо Мартину Смиту за , указавшему на это .

2 голосов
/ 13 июля 2011

Одним из способов уменьшения необходимого размера сканирования является использование комбинации TABLESAMPLE с ORDER BY newid для выбора случайного числа строк из выбора страниц в таблице, а не сканирования всей таблицы.

Идея состоит в том, чтобы рассчитать среднее количество строк на страницу, а затем использовать шаблон таблицы, чтобы выбрать 1 случайную страницу данных для каждой строки, которую вы хотите вывести. Затем вы выполните запрос ORDER BY newid () только для этого подмножества данных. Этот подход будет несколько менее случайным, чем первоначальный подход, но он намного лучше, чем просто использование таблицы, и предполагает считывание намного меньшего количества данных из таблицы.

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

declare @factor int
select @factor=8000/avg_record_size_in_bytes from sys.dm_db_index_physical_stats(db_id(), object_id('sample'), null, null, 'detailed') where index_level = 0
declare @numRows int = 10
declare @sampledRows int = @factor * @numRows
declare @stmt nvarchar(max) = N'select top (@numRows) * from sample tablesample (' + convert(varchar(32), @sampledRows) + ' rows) order by checksum(newid())'
exec sp_executesql @stmt, N'@numRows int', @numRows
1 голос
/ 20 сентября 2018

Этому вопросу 7 лет, и он не получил ответа.Но это заняло высокое место, когда я искал производительность SQL при выборе случайных строк.Но ни один из текущих ответов не дает простого и быстрого решения в случае больших таблиц, поэтому я хочу добавить свое предложение.

Допущения:

  • Первичный ключ - эточисловой тип данных (обычно int / +1 на строку)
  • Первичным ключом является кластеризованный индекс
  • В таблице много строк, и следует выбрать только несколько

Я думаю, что это довольно распространенное явление, поэтому оно может помочь во многих случаях.

Учитывая типичный набор данных, я предлагаю

  1. Найти максимум и мин.
  2. выберите случайное число
  3. проверьте, является ли номер действительным идентификатором в таблице
  4. Повторите при необходимости

Все эти операции должны быть выполненыочень быстро, поскольку все они находятся в кластерном индексе.Только в конце будут прочитаны остальные данные, путем выбора набора на основе списка первичных ключей, чтобы мы извлекали только те данные, которые нам действительно нужны.

Пример (MS SQL):

--
-- First, create a table with some dummy data to select from
-- 

DROP TABLE IF EXISTS MainTable
CREATE TABLE MainTable(
    Id int IDENTITY(1,1) NOT NULL,
    [Name] nvarchar(50) NULL,
    [Content] text NULL
)
GO

DECLARE @I INT = 0
WHILE @I < 40
BEGIN
    INSERT INTO MainTable VALUES('Foo', 'bar')
    SET @I=@I+1
END
UPDATE MainTable SET [Name] = [Name] + CAST(Id as nvarchar(50))

-- Create a gap in IDs at the end
DELETE FROM MainTable
    WHERE ID < 10

-- Create a gap in IDs in the middle
DELETE FROM MainTable
    WHERE ID >= 20 AND ID < 30

-- We now have our "source" data we want to select random rows from



--
-- Then we select random data from our table
-- 

-- Get the interval of values to pick random values from
DECLARE @MaxId int
SELECT @MaxId = MAX(Id) FROM MainTable

DECLARE @MinId int
SELECT @MinId = MIN(Id) FROM MainTable

DECLARE @RandomId int
DECLARE @NumberOfIdsTofind int = 10

-- Make temp table to insert ids from
DROP TABLE IF EXISTS #Ids
CREATE TABLE #Ids (Id int)

WHILE (@NumberOfIdsTofind > 0)
BEGIN
    SET @RandomId = ROUND(((@MaxId - @MinId -1) * RAND() + @MinId), 0)
    -- Verify that the random ID is a real id in the main table
    IF EXISTS (SELECT Id FROM MainTable WHERE Id = @RandomId)
    BEGIN
        -- Verify that the random ID has not already been inserted
        IF NOT EXISTS (SELECT Id FROM #Ids WHERE Id = @RandomId)
        BEGIN
            -- It's a valid, new ID, add it to the list.
            INSERT INTO #Ids VALUES (@RandomId)
            SET @NumberOfIdsTofind = @NumberOfIdsTofind - 1;
        END
    END
END

-- Select the random rows of data by joining the main table with our random Ids   
SELECT MainTable.* FROM MainTable
INNER JOIN #Ids ON #Ids.Id = MainTable.Id
0 голосов
/ 13 июля 2011

Нет, здесь нет способа улучшить производительность. Поскольку вам нужны строки в «случайном» порядке, индексы будут бесполезны. Однако вы можете попробовать упорядочить по newid() вместо контрольной суммы, но это всего лишь оптимизация к случайному упорядочению, а не сама сортировка.

Сервер не может знать, что вы хотите случайный выбор 10 строк из таблицы. Запрос будет оценивать выражение order by для каждой строки в таблице, поскольку это вычисленное значение, которое не может быть определено значениями индекса. Вот почему вы видите полное сканирование кластерного индекса.

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