优化SQL Server上的数字表创建?

时间:2011-02-01 15:54:29

标签: sql-server-2008 insert

运行SQL Server Express 2008.我为某些实用程序功能创建了“数字表”。由于表填充是自动构建的一部分,因此每次部署时都会花费过多的时间。

冒着“过度优化”的风险,有人可以评论我如何能够尽快实现这一目标吗?也许使用索引填充因子或创建PK时?

IF EXISTS (SELECT *  FROM dbo.sysobjects 
WHERE id = OBJECT_ID(N'Numbers') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
drop TABLE [Numbers]
end

CREATE TABLE [Numbers]
(
      [Number] [int]
    , CONSTRAINT [Index_Numbers] PRIMARY KEY CLUSTERED
        (
            [number] ASC
        ) ON [PRIMARY]
) 
ON [PRIMARY]
Declare @cnt int
Select @cnt=0
SET NOCOUNT ON
while (@cnt<10000)
BEGIN
INSERT INTO NUMBERS(NUMBER) SELECT @cnt
SELECT @cnt=@cnt+1

end

4 个答案:

答案 0 :(得分:12)

SQL, Auxiliary table of numbers 杰夫莫登。这是最快的

--===== Itzik's CROSS JOINED CTE method
   WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT N
   INTO #Tally4
   FROM cteTally
  WHERE N <= 1000000;
GO

答案 1 :(得分:4)

必须做一些测试,在函数中包装东西并使用不同的场景来填充表格。

选项

通过删除WHERE子句WHERE N <= @count并添加TOP子句TOP (@count),可以改进Mikael提出的解决方案。对于我来说,这减少了几乎六倍的执行时间:

  

这是最快的算法!现在你知道了:o)

create function dbo.Numbers2(@count bigint)
RETURNS TABLE RETURN
--===== Itzik's CROSS JOINED CTE method
   WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT TOP (@count) N
   FROM cteTally

与较慢的适应相比:

create function dbo.Numbers3(@count bigint)
RETURNS TABLE RETURN
--===== Itzik's CROSS JOINED CTE method
   WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT N
   FROM cteTally
   WHERE N <= @count

https://stackoverflow.com/a/14386663/481812中提出的解决方案与Numbers2具有相似的性能:

create function dbo.Numbers(@count bigint)
RETURNS TABLE RETURN
with byte (n) as (
  -- shortened, see the other answer for full code

测试

  1. 插入临时堆最快(600),
  2. 使用PK聚簇索引慢速插入临时表(3000),最后
  3. 填充索引最慢(10000)的物理表。与
  4. 进行了额外的比较
  5. 使用身份PK填充临时表,正如Gregory建议的那样,从相同的CTE样式行生成与性能中的集群temp#2几乎相同(3000)。
  6. 格雷戈里的提议太慢,无法考虑(21000)。令你惊讶的是,
  7. 原始示例用百万行测量是不现实的;相反,使用10000,比其他测试少100倍,将其降低到合理的时间(15000)。假设它按比例缩放 - 最好的情况 - 它理论上应该在25分钟(1500000)内完成1m行。
  8. 如果没有对数据进行排序,Ben的建议(尝试过)可能会有所不同 - 这里我们插入已排序的数据,因此总是附加,在之后没有建立索引的性能增益。

    测试组1 - 临时堆插入

    CREATE TABLE #Numbers   ([Number] [int]) 
    insert #Numbers select n from dbo.Numbers(1000000)
    drop table #Numbers
    -- 480 - 900 ms, avg 600 ms
    
    CREATE TABLE #Numbers   ([Number] [int]) 
    insert #Numbers select n from dbo.Numbers2(1000000)
    drop table #Numbers
    -- 440 - 800 ms, avg 550 ms
    
        -- just in case you wondered how it scales, 30 times more went in in 14000, 
        -- Numbers and Numbers2 scale linearly
    
    CREATE TABLE #Numbers   ([Number] [int]) 
    insert #Numbers select n from dbo.Numbers3(1000000)
    drop table #Numbers
    -- 2700 - 4000 ms, avg 3200 ms
    

    测试组2 - 插入临时群集

    CREATE TABLE #Numbers   ([Number] [int]  primary key) 
    insert #Numbers select n from dbo.Numbers(1000000)
    drop table #Numbers
    -- 1900 - 4000 ms, avg 3000 ms
    
    CREATE TABLE #Numbers   ([Number] [int]  primary key) 
    insert #Numbers select n from dbo.Numbers2(1000000)
    drop table #Numbers
    -- 1900 - 4000 ms, avg 3000 ms
    
    CREATE TABLE #Numbers   ([Number] [int]  primary key) 
    insert #Numbers select n from dbo.Numbers3(1000000)
    drop table #Numbers
    -- 4000 - 7000 ms, avg 5000 ms
    

    测试组3 - 在物理表簇中插入

    CREATE TABLE PNumbers   ([Number] [int]  primary key) 
    insert PNumbers select n from dbo.Numbers(1000000)
    drop table PNumbers
    -- 7000 - 12000 ms, avg 10000 ms
    
    CREATE TABLE PNumbers   ([Number] [int]  primary key) 
    insert PNumbers select n from dbo.Numbers2(1000000)
    drop table PNumbers
    -- 7000 - 12000 ms, avg 10000 ms
    
    CREATE TABLE PNumbers   ([Number] [int]  primary key) 
    insert PNumbers select n from dbo.Numbers3(1000000)
    drop table PNumbers
    -- 7000 - 12000 ms, avg 10000 ms
    

    测试4 - 从生成的行填充临时聚类标识列

    CREATE TABLE #Numbers ([Number] [int] identity primary key, x bit) 
    INSERT INTO #Numbers(x) select 1 from dbo.Numbers2(1000000)
    drop table #Numbers
    -- 2000 - 4000 ms, avg 3000 ms
    

    测试5 - 在循环中填充临时聚簇标识列

    CREATE TABLE #Numbers ([Number] [int] identity primary key) 
    declare @cnt int = 0
    while (@cnt<1000000) 
    BEGIN
        INSERT INTO #Numbers DEFAULT values
        set @cnt = @cnt+1
    END
    drop table #Numbers
    -- 20000 - 22000 ms, avg 21000 ms
    

    结论

    Numbers(n)和Numbers2(n)选项始终执行相同(最佳)。由于WHERE子句,Numbers3(n)的速度较慢。其他测试过的场景太慢而无法考虑。

    插入未编制索引的临时堆最快。只要您将任何索引(已尝试)添加到列(PK群集或辅助非唯一),它就不再是数字生成,而是关于索引插入(可能还有IO)。最糟糕的是插入物理表。这必须是因为临时表不一定写入磁盘(tempdb)。

    提案

    根据测量结果,你将获得

    1. 使用CTE + JOIN + ROW_NUMBER + TOP解决方案而不是循环或递归CTE,
    2. 不使用索引 - 使用堆和
    3. 使用临时表而不是物理表。
    4. 你可以完全避开物理或临时表,只使用函数,但是在随后的查询中,你将在优化器上进行搜索,因为你没有统计数字。然后你可以用查询提示来解决这个问题,这个问题不好,但有效......但是,看一下执行计划,至少可以正确估算dbo.Numbers的行数。

答案 2 :(得分:0)

您可以做的一件简单事情就是延迟创建索引,直到填充完所有数字。换句话说,将它从CREATE TABLE中删除,并在循环后添加一个CREATE INDEX语句。这将阻止在每个插入时更新索引。

答案 3 :(得分:0)

我不确定我是否同意这个体系结构,从高级别开始,但由于你只是坚持一个字段,数字从0到9999,你可以改变表定义,因此它是一个自动增量播种到从0开始。然后将默认值插入表中。那应该非常快。

您还可以使用正确的行对表进行原型设计,然后通过“复制”行来插入10,000。这意味着另一个“无”的表,可能会或可能不会解决问题。

只是两个想法。