我有一张有 100,000,000 行的大表。我想从表中选择每第 n 行。我的第一直觉是使用这样的东西:
SELECT id,name FROM table WHERE id%125000=0
检索均匀分布的 800 行(id 是聚集索引)
这种技术适用于较小的数据集,但对于较大的表,查询需要 2.5 分钟。我认为这是因为模运算适用于每一行。有没有更优化的跳行方法?
我有一张有 100,000,000 行的大表。我想从表中选择每第 n 行。我的第一直觉是使用这样的东西:
SELECT id,name FROM table WHERE id%125000=0
检索均匀分布的 800 行(id 是聚集索引)
这种技术适用于较小的数据集,但对于较大的表,查询需要 2.5 分钟。我认为这是因为模运算适用于每一行。有没有更优化的跳行方法?
时间不在于取模操作本身,而在于只为您实际需要的每一行读取 124,999 个不必要的行(即,表扫描或聚集索引扫描)。
加速这样的查询的唯一方法是乍一看似乎不合逻辑:仅在该列 ([ID]) 上添加一个额外的非聚集索引。此外,您可能必须添加索引提示以强制它使用该索引。最后,它实际上可能不会使其更快,尽管对于 125,000+ 的模数,它应该是(尽管它永远不会真正快)。
如果您的 ID 不一定是连续的(任何删除的行都会导致这种情况),并且您确实确实需要按 ID order 精确计算每个模行,那么您仍然可以使用上述方法,但您必须重新排序 ID 的查询中使用的模运算ROW_NUMBER() OVER(ORDER BY ID)
。
您的查询假定 ID 是连续的(并且可能在您没有意识到这一点的情况下它们不是...)。无论如何,您应该自己生成 ID:
select *
from T
where ID in (0, 250000*1, 250000*2, ...)
也许你需要一个 TVP 来发送所有的 ID,因为有很多。或者,您在 T-SQL 或 SQLCLR 函数或数字表中生成服务器上的 ID。
此技术允许您执行索引查找,并且将是您可能产生的最快的。它读取尽可能少的数据。
模不是 SARGable。如果 Microsoft 需要,SQL Server 可以支持这一点,但这是一个奇特的用例。他们永远不会使模 SARGable 并且他们不应该。
如果id
在索引中,那么我正在考虑以下内容:
with ids as (
select 1 as id
union all
select id + 125000
from ids
where id <= 100000000
)
select ids.id,
(select name from table t where t.id = ids.id) as name
from ids
option (MAXRECURSION 1000);
我认为这个公式将使用表上的索引。
编辑:
当我考虑这种方法时,您实际上可以使用它来获取表中的实际随机 id,而不仅仅是均匀间隔的:
with ids as (
select 1 as cnt,
ABS(CONVERT(BIGINT,CONVERT(BINARY(8), NEWID()))) % 100000000 as id
union all
select cnt + 1, ABS(CONVERT(BIGINT,CONVERT(BINARY(8), NEWID()))) % 100000000
from ids
where cnt < 800
)
select ids.id,
(select name from table t where t.id = ids.id) as name
from ids
option (MAXRECURSION 1000);
实际随机数生成器的代码来自这里。
编辑:
由于 SQL Server 中的怪癖,即使在您的场景中,您仍然可以获得不连续的 id。这个公认的答案解释了原因。简而言之,身份值不是一次分配一个,而是分组分配。服务器可能会失败,甚至会跳过未使用的值。
我想做随机抽样的一个原因是为了帮助避免这个问题。据推测,上述情况在大多数系统上相当罕见。您可以使用随机抽样来生成 900 个 ID。从这些中,您应该能够找到 800 个实际可用于您的样品。
DECLARE @i int, @max int, @query VARCHAR(1000)
SET @i = 0
SET @max = (SELECT max(id)/125000 FROM Table1)
SET @query = 'SELECT id, name FROM Table1 WHERE id in ('
WHILE @i <= @max
BEGIN
IF @i > 0 SET @query = @query + ','
SET @query = @query + CAST(@i*125000 as varchar(12))
SET @i = @i + 1
END
SET @query = @query + ')'
EXEC(@query)
编辑 :
为避免在非连续 ID 情况下出现任何“漏洞”,您可以尝试以下操作:
DECLARE @i int, @start int, @id int, @max int, @query VARCHAR(1000)
SET @i = 0
SET @max = (SELECT max(id)/125000 FROM Table1)
SET @query = 'SELECT id, name FROM Table1 WHERE id in ('
WHILE @i <= @max
BEGIN
SET @start = @i*125000
SET @id = (SELECT TOP 1 id FROM Table1 WHERE id >= @start ORDER BY id ASC)
IF @i > 0 SET @query = @query + ','
SET @query = @query + CAST(@id as VARCHAR(12))
SET @i = @i + 1
END
SET @query = @query + ')'
EXEC(@query)