按性能随机排序

时间:2011-07-13 14:14:10

标签: tsql

按随机顺序获取top n行的最佳方法是什么? 我使用如下查询:

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

上述查询中的问题是随着表大小的增加它会继续变慢。它将始终执行完整的聚簇索引扫描,以按随机顺序查找top(10)行。

还有其他更好的方法吗?

4 个答案:

答案 0 :(得分:4)

我已对此进行了测试,并在更改查询方面获得了更好的性能。

我在测试中使用的表的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()对行进行排序,然后执行Clustered Index Seek 10次以获取输出值。

性能提升是因为Col5上的索引比群集密钥窄,导致读取次数减少。

感谢Martin Smithpointing that out

答案 1 :(得分:2)

减少所需扫描大小的一种方法是使用TABLESAMPLE和ORDER BY newid的组合,以便从表中的一系列页面中选择随机行数,而不是扫描整个表。

想法是计算每页的平均行数,然后使用tablesample为要输出的每一行选择1个随机数据页。然后,您将仅对该数据子集运行ORDER BY newid()查询。这种方法比原始方法稍微不那么随机,但比使用tablesample更好,并且涉及从表中读取更少的数据。

不幸的是,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

答案 2 :(得分:1)

这个问题已有7年历史了,没有可以接受的答案。但是当我在选择随机行时搜索SQL性能时,它排名很高。但是,对于大表,当前的答案似乎都没有提供简单,快速的解决方案,因此我想补充一下我的建议。

假设:

  • 主键是数字数据类型(典型的整数/每行+1)
  • 主键是聚簇索引
  • 该表有很多行,应该只选择几行

我认为这很普遍,因此在很多情况下都会有所帮助。

给出一组典型的数据,我的建议是

  1. 查找最大值和最小值
  2. 选择一个随机数
  3. 检查数字在表中是否为有效ID
  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

答案 3 :(得分:0)

不,这里没有办法提高性能。由于您希望以“随机”顺序排列,因此索引将无用。但是,您可以尝试按newid()而不是校验和进行排序,但这只是对随机排序的优化,而不是排序本身。

服务器无法知道您希望从表中随机选择10行。该查询将评估表中每一行的order by表达式,因为它是一个无法通过索引值确定的计算值。这就是您看到完整的聚簇索引扫描的原因。