Tsql 按性能随机排序

Tsql 按性能随机排序,tsql,Tsql,按随机顺序获取前n行的最佳方法是什么? 我使用如下查询: Select top(10) field1,field2 .. fieldn from Table1 order by checksum(newid()) 上面的查询中的问题是,随着表大小的增加,它将继续变慢。它将始终执行完整的聚集索引扫描,以按随机顺序查找top(10)行 还有其他更好的方法吗?没有,这里没有办法提高性能。因为您希望行按“随机”顺序排列,所以索引将是无用的。但是,您可以尝试通过newid()而不是其校验和进行排序,但这

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

Select top(10) field1,field2 .. fieldn
from Table1
order by checksum(newid())
上面的查询中的问题是,随着表大小的增加,它将继续变慢。它将始终执行完整的聚集索引扫描,以按随机顺序查找
top(10)


还有其他更好的方法吗?

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


服务器无法知道您想要从表中随机选择10行。查询将为表中的每一行计算
order by
表达式,因为它是一个不能由索引值确定的计算值。这就是您看到完整聚集索引扫描的原因。

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

我在测试中使用的表的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()
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 = 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.
执行计划第一次查询:

第二次查询:

select top 10
  T.ID,
  T.Col1,
  T.Col2,
  T.Col3,
  T.Col5,
  T.Col5
from TestTable as T
order by newid()
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.
执行计划第二次查询:

摘要:

select top 10
  T.ID,
  T.Col1,
  T.Col2,
  T.Col3,
  T.Col5,
  T.Col5
from TestTable as T
order by newid()
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
第二个查询使用
Col5
上的索引按
newid()
对行进行排序,然后执行聚集索引搜索10次,以获取输出值

性能提高是因为
Col5
上的索引比聚集键窄,导致读取次数更少


感谢for.

减少必要扫描大小的一种方法是使用TABLESAMPLE和ORDER by newid的组合,以便从表中的页面选择随机数目的行,而不是扫描整个表

其思想是计算每页的平均行数,然后使用tablesample为要输出的每一行随机选择一页数据。然后,您将仅对该数据子集运行orderbynewid()查询。与原始方法相比,这种方法的随机性要小一些,但比仅使用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

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

假设:

  • 主键是数字数据类型(每行典型的int/+1)
  • 主键是聚集索引
  • 该表有许多行,只应选择少数行
我认为这是相当普遍的,所以在很多情况下都会有帮助

鉴于一组典型的数据,我的建议是

  • 查找最大值和最小值
  • 随机选取一个数字
  • 检查数字是否为表中的有效id
  • 根据需要重复
  • 这些操作都应该非常快,因为它们都在聚集索引上。只有在最后,通过基于主键列表选择一个集合,才能读取其余的数据,这样我们就只需要拉入实际需要的数据

    示例(MS SQL):

    --
    --首先,创建一个表,其中包含一些要从中选择的虚拟数据
    -- 
    如果存在主表,则删除该表
    创建表MainTable(
    Id int标识(1,1)不为空,
    [名称]nvarchar(50)空,
    [内容]文本为空
    )
    去
    声明@I INT=0
    而@I<40
    开始
    插入主表值('Foo','bar')
    设置@I=@I+1
    结束
    更新主表集合[Name]=[Name]+CAST(Id为nvarchar(50))
    --在末端的ID中创建间隙
    从主表中删除
    其中ID<10
    ——在中间创建一个IDS的空隙
    从主表中删除
    其中ID>=20且ID<30
    --我们现在有了我们想要从中随机选择行的“源”数据
    --
    --然后我们从表中选择随机数据
    -- 
    --获取要从中拾取随机值的值的间隔
    声明@MaxId int
    从MainTable中选择@MaxId=MAX(Id)
    声明@MinId int
    从MainTable中选择@MinId=MIN(Id)
    声明@RandomId int
    声明@NumberOfIdsTofind int=10
    --创建临时表以从中插入ID
    删除表格(如果存在)#ID
    创建表#Id(Id int)
    而(@NumberOfIdsTofind>0)
    开始
    设置@RandomId=ROUND(((@MaxId-@MinId-1)*RAND()+@MinId),0)
    --验证随机ID是否为主表中的真实ID
    如果存在(从MainTable中选择Id,其中Id=@RandomId)
    开始
    --确认尚未插入随机ID
    如果不存在(从#Id中选择Id,其中Id=@RandomId)
    开始
    --这是一个有效的新ID,请将其添加到列表中。
    插入到#Ids值(@RandomId)
    设置@NumberOfIdsTofind=@NumberOfIdsTofind-1;
    结束
    结束
    结束
    --通过将主表与随机ID连接,选择随机数据行
    从MainTable中选择MainTable.*
    内部联接#Ids ON#Ids.Id=MainTable.Id
    
    为什么要按校验和(newid())而不是按newid()排序?如果要向每一行添加一个值(并且计算
    order by
    子句中的函数会隐式地这样做),则需要先处理每一行,然后才能执行排序。我现在在想是否有办法避免给每一行分配排序值。。。如果每一行都包含一个唯一的int值,那么这可能是可行的——不同的解决方案取决于int值是连续的还是不连续的。这是我的猜测,没有办法避免完全索引扫描,以获得百万行表中的前10行。这意味着获取rand中的大量行