2011-07-13 221 views
4

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

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

在上面的查询的问题是,它会越来越为表大小的增加速度较慢。它将始终进行全聚簇索引扫描,以随机顺序查找top(10)行。

有没有其他更好的方法来做到这一点?

+3

为什么你按校验和(newid())而不是order by newid()? – eugeneK

+0

如果您要为每一行添加一个值(并且在'ORDER BY'子句中评估函数时会隐式执行此操作),则需要在执行排序之前处理每一行。我现在试图考虑是否有办法避免为每行分配一个排序值......如果每行都包含一个唯一的int值,那么可能是可行的 - 不同的解决方案则取决于int值是否连续。 –

回答

0

不,这里没有办法提高性能。既然你想要“随机”顺序的行,索引将是无用的。但是,您可以尝试按newid()而不是其校验和排序,但这只是对随机排序的优化,而不是排序本身。

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

+0

这是我的猜测,有没有办法避免完整的索引扫描,以获得百万行表中的前10行。这意味着从一个非常大的表中以随机顺序获取大块行将成为数据库服务器的一个重要负担。 – kumar

+0

@kumar:这是正确的。 –

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(),然后执行聚集索引搜索10次以获取输出的值。

性能增益存在,因为Col5上的索引比聚簇密钥更窄,导致读取次数更少。

感谢Martin Smith对于pointing that out

+0

原始版本只能进行聚簇索引扫描,因为这是所有必需的(排序不使用表中的任何实际数据,所以表扫描是不必要的)。 –

+0

@亚当罗宾逊 - 这不是一个表扫描。它是索引扫描而不是聚簇索引扫描。我已经更新了我的答案。 –

+0

正确;你的回答似乎意味着OP在之前进行过一次表格扫描(“我猜这里的表现增益是它不需要读整个表格,只有整个窄指数”),但是他没有。只是想澄清一点。话虽如此,你可能正确的解释为什么你的解决方案更快(不必读取所有记录中的整行)。 –

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 
+0

这工作得很好。我认为8000应该是8060,尽管根据http://technet.microsoft.com/en-us/library/ms190969(v=sql.105).aspx – oldwizard