加载大量测试数据的快速方法

Ban*_*San 4 performance sql-server optimization bulk-insert

我试图用大量虚拟数据填充表,以便我可以进行优化等。

我有以下几点:

WHILE @RowCount < 3000000
BEGIN

    SELECT @Random = ROUND(@Upper * RAND(), 0)

    INSERT INTO [dbo].[Test]
               ([Id]
               ,[OtherKey]
               ,[Description])
         VALUES
               (@RowCount
               ,@Random
               ,CAST(@Random AS VARCHAR(max)))

    SET @RowCount = @RowCount + 1
END
Run Code Online (Sandbox Code Playgroud)

然而,这似乎很慢。

有没有更好的方法来自动将半随机行加载到数据库表中?

新脚本

这个似乎很快:

USE [Test]
GO

/****** Object:  Table [dbo].[Test]    Script Date: 17/10/2016 21:22:39 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

USE [Test]
GO

CREATE TABLE [dbo].[Test](
    [Id] [int] NOT NULL,
    [OtherKey] [int] NOT NULL,
    [Description] [varchar](max) NOT NULL,
    [Time] [datetime] NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

-- Add sample data

DECLARE @RowCount INT
DECLARE @Random INT
DECLARE @Upper INT

SET @Upper = 1000
SET @RowCount = 0

WHILE @RowCount < 1000000
BEGIN

    SELECT @Random = ROUND(@Upper * RAND(), 0)

    INSERT INTO [dbo].[Test]
               ([Id]
               ,[OtherKey]
               ,[Description]
               ,[Time])
         VALUES
               (@RowCount
               ,@Random
               ,CAST(@Random AS VARCHAR(max))
               ,GETDATE())

    SET @RowCount = @RowCount + 1
END

GO

/****** Object:  Index [IX_ID]    Script Date: 17/10/2016 22:18:48 ******/
CREATE CLUSTERED INDEX [IX_ID] ON [dbo].[Test]
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO



/****** Object:  Index [IX_OtherKey]    Script Date: 17/10/2016 21:22:46 ******/
CREATE NONCLUSTERED INDEX [IX_OtherKey] ON [dbo].[Test]
(
    [OtherKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Run Code Online (Sandbox Code Playgroud)

如果有人有更快的方法,那么我会很高兴听到它。

Joe*_*ish 6

以下是加载不少测试数据的几个指导原则:

1)尽可能使用最少的日志记录

如果您可以避免将不必要的数据写入事务日志,太好了,为什么不这样做呢?这将要求您的测试数据库使用简单的恢复模式。您还需要小心遵守允许您获得最少日志记录的规则。如果您需要插入具有已包含数据跟踪标志的聚集索引的表,610 会有所帮助。

2) 避免导致不必要开销的表和列 DDL 选择。

正如您发现的那样,有时在数据加载之后而不是在数据加载之前创建聚集索引更有效,尽管它可以根据数据采用任何一种方式。有时创建聚集索引会更快,因为 SQL Server 可以将数据并行插入到现有堆中(从 SQL Server 2016 开始)并且它可以并行创建聚集索引。这不是一个最少记录的操作,它需要对表的所有数据进行排序,因此如果您的开发系统的规模不够大,这可能不起作用。

正如您还发现的,一定要在加载数据之后而不是之前创建非聚集索引。

我相信使用 VARCHAR(MAX) 数据类型会有一些开销,所以如果可能的话我会避免这种情况。

3) 尽可能避免逐行操作。SQL Server 通常使用基于集合的解决方案更高效。

您的代码中有一些逐行操作。您正在执行 WHILE 循环并且一次只处理一行。您还为要插入的每一行创建一个事务。创建和提交事务肯定有一些开销,对吗?为什么要为每一行付钱?如果您的系统足够大,您通常可以使用单个查询插入测试数据,特别是如果您能够使用最少的日志记录。

4) 尽可能使用并行性。

您拥有的代码一次运行一行,无法利用服务器上的多个内核。

我将为您提供一种解决此类问题的方法,但此查询可能无法完全满足您的需求。

WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3),
L5 AS (SELECT 1 AS c FROM L4 A CROSS JOIN L4),
NUMS AS (SELECT 1 AS NUM FROM L5)   
SELECT TOP 1000000
  CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS INT) [ID]
, CAST(t.RAND_VALUE AS INT) [OTHERKEY]
, CAST(t.RAND_VALUE AS VARCHAR(100)) [DESCRIPTION]
, CAST(GETDATE() AS DATETIME) [TIME]
INTO [X_JRO_TEST_RAND_2]
FROM NUMS CROSS JOIN (SELECT ROUND(1000 * RAND(CHECKSUM(NEWID())), 0) RAND_VALUE) t;
Run Code Online (Sandbox Code Playgroud)

我将使用我在帖子顶部列出的指导原则为您分解查询。

1) SELECT INTO 创建一个 HEAP 表作为插入的一部分。如果数据库具有简单的恢复模式,则此操作将被最少记录。我可以通过不将不必要的数据写入事务日志来节省工作。

2)该表在加载数据之前没有任何索引。我也在使用 VARCHAR(100) 而不是 VARCHAR(MAX)。

3)为了有效地生成数字,我使用了Itzik Ben-Gan推广(可能创建)的技术。所有的 CTE 都在那里生成 1000000 行。我会查看文章以了解有关该方法如何工作的更多详细信息。为了使 RAND() 为每一行返回不同的值,我使用了此堆栈溢出帖子中记录的技术之一。

4) 从 SQL Server 2014 开始,当使用 SELECT INTO 语法时,查询优化器可以并行地将数据插入到堆中。从 SQL Server 2016 开始,即使没有 SELECT INTO 语法,查询优化器也可以并行地将数据插入到堆中。在我的测试系统上,查询并行运行。

在此处的测试系统上,您的代码花费了 10:30 处理一百万行,但上面的查询只花费了 23 秒。我只测量了数据加载步骤,并没有考虑索引。值得指出的是,可能有更有效的方法以您想要的形式生成数据,并且我的代码没有为 TIME 列设置不同的值。

还值得指出的是,如果您的解决方案运行速度足以满足您的目的,那就太好了,继续并保留它。有时没有必要从代码中获得所有的最后一点性能,尤其是为了开发目的而在幕后运行的代码。