5

我有一张有 100,000,000 行的大表。我想从表中选择每第 n 行。我的第一直觉是使用这样的东西:

SELECT id,name FROM table WHERE id%125000=0

检索均匀分布的 800 行(id 是聚集索引)

这种技术适用于较小的数据集,但对于较大的表,查询需要 2.5 分钟。我认为这是因为模运算适用于每一行。有没有更优化的跳行方法?

4

4 回答 4

2

时间不在于取模操作本身,而在于只为您实际需要的每一行读取 124,999 个不必要的行(即,表扫描或聚集索引扫描)。

加速这样的查询的唯一方法是乍一看似乎不合逻辑:仅在该列 ([ID]) 上添加一个额外的非聚集索引。此外,您可能必须添加索引提示以强制它使用该索引。最后,它实际上可能不会使其更快,尽管对于 125,000+ 的模数,它应该是(尽管它永远不会真正快)。


如果您的 ID 不一定是连续的(任何删除的行都会导致这种情况),并且您确实确实需要按 ID order 精确计算每个模行,那么您仍然可以使用上述方法,但您必须重新排序 ID 的查询中使用的模运算ROW_NUMBER() OVER(ORDER BY ID)

于 2013-09-03T19:11:11.817 回答
2

您的查询假定 ID 是连续的(并且可能在您没有意识到这一点的情况下它们不是...)。无论如何,您应该自己生成 ID:

select *
from T
where ID in (0, 250000*1, 250000*2, ...)

也许你需要一个 TVP 来发送所有的 ID,因为有很多。或者,您在 T-SQL 或 SQLCLR 函数或数字表中生成服务器上的 ID。

此技术允许您执行索引查找,并且将是您可能产生的最快的。它读取尽可能少的数据。

模不是 SARGable。如果 Microsoft 需要,SQL Server 可以支持这一点,但这是一个奇特的用例。他们永远不会使模 SARGable 并且他们不应该。

于 2013-09-03T19:22:37.277 回答
1

如果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 个实际可用于您的样品。

于 2013-09-03T20:02:00.553 回答
0
 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)
于 2013-09-03T21:00:30.220 回答