我试图用大量虚拟数据填充一个表,以便我可以进行优化等。
我有以下内容:
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
但是,这似乎很慢。
有没有更好的方法来自动将半随机行加载到数据库表中?
新脚本
这个似乎很快:
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
如果有人有更快的方法,那么我会很高兴听到它。
以下是加载大量测试数据的一些指导原则:
1) 尽可能使用最少的日志记录。
如果您可以避免将不必要的数据写入事务日志,那太好了,为什么不这样做呢?这将要求您的测试数据库使用简单的恢复模式。您还需要小心遵守允许您获得最少日志记录的规则。如果您需要插入到具有已包含数据跟踪标志 610 的聚集索引的表中,则可以提供帮助。
2) 避免导致不必要开销的表和列 DDL 选择。
正如您发现的那样,有时在加载数据之后而不是在加载之前创建聚集索引更有效,尽管它可以根据数据采用任何一种方式。有时创建聚集索引之后可能会更快,因为 SQL Server 可以将数据并行插入到现有堆中(从 SQL Server 2016 开始),并且它可以并行创建聚集索引。这不会是最低限度的记录操作,它需要对表的所有数据进行排序,因此如果您的开发系统不够大,这可能不起作用。
正如您还发现的那样,一定要在加载数据之后而不是之前创建非聚集索引。
我相信使用 VARCHAR(MAX) 数据类型有一些开销,所以如果可能的话我会避免这种情况。
3) 尽可能避免逐行操作。SQL Server 通常使用基于集合的解决方案更有效。
您的代码中有一些逐行操作。您正在执行 WHILE 循环,一次只处理一行。您还为插入的每一行创建了一个事务。创建和提交事务一定有一些开销,对吧?为什么要为每一行付钱?如果您的系统足够大,您通常可以通过单个查询插入测试数据,特别是如果您能够使用最少的日志记录。
4) 尽可能使用并行。
您的代码一次运行一行,无法利用服务器上的多个内核。
我将为您提供一种解决此类问题的方法,但此查询可能无法完全满足您的需求。
我将使用我在帖子顶部列出的指导原则为您分解查询。
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 列没有不同的值。
还值得指出的是,如果您的解决方案运行得足够快以满足您的目的,那太好了,继续保持它。有时没有必要从代码中获得所有最后一点性能,尤其是为了开发目的而在幕后运行的代码。