SQL 服务器请求在本地数据库中插入 200 行需要 20 秒并且还在增长
SQL Server request inserting 200 rows in a local database takes 20 seconds and growing
我正在开发连接到本地 SQL 服务器数据库的控制台应用程序(C#,asp-core 2.1,Entity Framework Core),默认 (localdb)\MSSQLLocalDB
(SQL Server 2016 v13.0) 随 Visual Studio 一起提供。
我面临的问题是将数据插入 table 需要相当长的时间。 table 有 400.000 行,6 列,我一次插入 200 个。
现在,请求需要 20 秒才能执行。而且这个执行时间不断增加。考虑到我还有 20.000 x200 行要插入,有必要弄清楚这个问题是从哪里来的!
一些事实:
- table
上没有索引
- 我的电脑不是新的,但我有一个非常好的硬件(i7、16 Go RAM)并且我在插入时没有达到 100%CPU
所以,我的问题是:
- 40 万行算作 'large' 数据库吗?我以前从未使用过这么大的 table,但我认为拥有这样的数据集很常见。
- 如何查看插入时间从何而来?到目前为止我只安装了 Visual Studio(但我对其他选项持开放态度)
这是有问题的 table 的 SQL 代码:
CREATE TABLE [dbo].[KfStatDatas]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[DistrictId] INT NOT NULL,
[StatId] INT NOT NULL,
[DataSourceId] INT NOT NULL,
[Value] NVARCHAR(300) NULL,
[SnapshotDate] DATETIME2(7) NOT NULL
);
编辑
我 运行 SQL Server Management Studio,我发现请求正在减慢整个过程。这是插入请求。
但是,通过查看 Entity Framework 创建的 SQL 请求,它看起来像是在进行内部连接并遍历整个 table,这可以解释为什么处理时间随着table的增加而增加。
我可能漏掉了一点,但为什么您需要枚举整个 table 来添加行?
正在执行原始请求:
SELECT [t].[Id]
FROM [KfStatDatas] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position]
编辑和解决方案
我最终找到了问题,这是一个愚蠢的错误:我的 Id
字段没有声明为主键!因此系统必须为每个插入的行遍历整个数据库。我添加了 PK,现在 200 行需要 100 毫秒,这个持续时间是 stable。
感谢您的宝贵时间!
没有 400K 行并不大。
从 .NET 插入大量行的最有效方法是使用 SqlBulkCopy。对于 400K 行,这应该需要几秒钟而不是几分钟。
通过批处理单个插入,在单个事务中执行整个批处理以提高吞吐量。否则,每个插入都是单独提交的,需要为每个插入将日志缓冲区同步刷新到磁盘以强化事务。
编辑:
我从您的评论中看出您正在使用 Entity Framework。 This answer 可以帮助您将 SqlBulkCopy 与 EF 结合使用。
我认为您可能只是缺少主键。您已向 EF 声明 Id 是实体键,但您在 table 上没有唯一索引来强制执行此操作。
当 EF 想要获取插入的 ID 时,如果没有索引,它的开销很大。所以这个查询
SELECT t.id from KfStatDatas t
inner join @inserted0 i
on t.id = i.id
order by i._Position
执行 38K 次逻辑读取,平均耗时 16 秒。
所以尝试:
ALTER TABLE [dbo].[KfStatDatas]
ADD CONSTRAINT PK_KfStatDatas
PRIMARY KEY (id)
顺便说一句,你确定这是 EF6 吗?这看起来更像是 EF Core 批量插入。
我正在开发连接到本地 SQL 服务器数据库的控制台应用程序(C#,asp-core 2.1,Entity Framework Core),默认 (localdb)\MSSQLLocalDB
(SQL Server 2016 v13.0) 随 Visual Studio 一起提供。
我面临的问题是将数据插入 table 需要相当长的时间。 table 有 400.000 行,6 列,我一次插入 200 个。
现在,请求需要 20 秒才能执行。而且这个执行时间不断增加。考虑到我还有 20.000 x200 行要插入,有必要弄清楚这个问题是从哪里来的!
一些事实:
- table 上没有索引
- 我的电脑不是新的,但我有一个非常好的硬件(i7、16 Go RAM)并且我在插入时没有达到 100%CPU
所以,我的问题是:
- 40 万行算作 'large' 数据库吗?我以前从未使用过这么大的 table,但我认为拥有这样的数据集很常见。
- 如何查看插入时间从何而来?到目前为止我只安装了 Visual Studio(但我对其他选项持开放态度)
这是有问题的 table 的 SQL 代码:
CREATE TABLE [dbo].[KfStatDatas]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[DistrictId] INT NOT NULL,
[StatId] INT NOT NULL,
[DataSourceId] INT NOT NULL,
[Value] NVARCHAR(300) NULL,
[SnapshotDate] DATETIME2(7) NOT NULL
);
编辑 我 运行 SQL Server Management Studio,我发现请求正在减慢整个过程。这是插入请求。
但是,通过查看 Entity Framework 创建的 SQL 请求,它看起来像是在进行内部连接并遍历整个 table,这可以解释为什么处理时间随着table的增加而增加。
我可能漏掉了一点,但为什么您需要枚举整个 table 来添加行?
正在执行原始请求:
SELECT [t].[Id]
FROM [KfStatDatas] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position]
编辑和解决方案
我最终找到了问题,这是一个愚蠢的错误:我的 Id
字段没有声明为主键!因此系统必须为每个插入的行遍历整个数据库。我添加了 PK,现在 200 行需要 100 毫秒,这个持续时间是 stable。
感谢您的宝贵时间!
没有 400K 行并不大。
从 .NET 插入大量行的最有效方法是使用 SqlBulkCopy。对于 400K 行,这应该需要几秒钟而不是几分钟。
通过批处理单个插入,在单个事务中执行整个批处理以提高吞吐量。否则,每个插入都是单独提交的,需要为每个插入将日志缓冲区同步刷新到磁盘以强化事务。
编辑:
我从您的评论中看出您正在使用 Entity Framework。 This answer 可以帮助您将 SqlBulkCopy 与 EF 结合使用。
我认为您可能只是缺少主键。您已向 EF 声明 Id 是实体键,但您在 table 上没有唯一索引来强制执行此操作。
当 EF 想要获取插入的 ID 时,如果没有索引,它的开销很大。所以这个查询
SELECT t.id from KfStatDatas t
inner join @inserted0 i
on t.id = i.id
order by i._Position
执行 38K 次逻辑读取,平均耗时 16 秒。
所以尝试:
ALTER TABLE [dbo].[KfStatDatas]
ADD CONSTRAINT PK_KfStatDatas
PRIMARY KEY (id)
顺便说一句,你确定这是 EF6 吗?这看起来更像是 EF Core 批量插入。