[英]Sql exceptions are very slow… how to speed up?
我有一段代码将数据导入到sql表中。 当传入数据出现问题时(例如,错误的数据类型或将被截断),它将生成一个预期的异常,但是它非常慢。 如果它们都很好,我可以在几秒钟内导入10,000行,但是有时源数据需要一点点tlc,因此应用程序会导入这些行并报告异常,以便对其进行修复。 例如,如果所有10,000行的一个字段都需要缩短,因为该字段将被截断,那么它将花费大约10分钟而不是10或20秒。
增加的时间并不是因为catch块中正在运行任何代码...即使在catch块中根本没有代码,它也会这样做。 有人知道这仅仅是它的方式吗,还是可以做些什么使它更快?
编辑:好的,根据请求,我在下面添加了一些代码,但是我想您很快就会明白为什么我认为一开始没有必要这样做:)
public void ImportRow(DataRow r, SqlConnection conn, SqlTransaction trx)
{
var sqlCmd = _importCommand.CreateSqlCommand(r);
SqlCommand sCom = new SqlCommand(sqlCmd, conn, trx);
try
{
sCom.ExecuteNonQuery();
}
catch (Exception e)
{
}
}
_importCommand.CreateSqlCommand(r)
仅返回一个字符串(您可能已经在SqlCommand
构造函数参数中使用它来推断出该字符串)。 如您所见,我还没有向catch块添加任何代码。 如果没有异常,它将执行sCom.ExecuteNonQuery();
速度很快,但是如果有例外,则会有短暂的停顿。 ImportRow
方法是从另一个函数循环调用的,但是我肯定地确定此方法的try块产生了滞后,因此其他代码无关。
当您在批量插入操作中遇到错误时,必须回滚成功插入的记录。 根据经验,与撤消操作相比,回滚过程中操作的撤消总是比较慢,因为它必须回读日志(为写而不是读进行了优化) 并补偿了该操作。 但是,由此无法解释10秒到10分钟之间的差异。 因此,除非您能提供某种证据证明服务器需要10分钟,否则我必须得出结论,这就是您的应用程序代码在这10分钟内正在做什么。
更新
让我们尝试全部失败的10k插入,然后将其与成功的10k插入进行比较:
set nocount on;
use master;
if db_id('test') is not null
begin
alter database test set single_user with rollback immediate;
drop database test;
end
go
create database test;
go
use test;
go
create table good (a int, b char(1000));
go
declare @start datetime = getdate(), @i int =0;
begin transaction;
while @i < 10000
begin
insert into good (a) values (@i);
set @i += 1;
end
commit;
declare @end datetime = getdate();
select 'good: ', datediff(ms, @start, @end);
go
create table bad (a int, b char(1000), constraint fail check (a<0));
go
declare @start datetime = getdate(), @i int =0;
begin transaction;
while @i < 10000
begin
insert into bad (a) values (@i);
set @i += 1;
end
commit;
declare @end datetime = getdate();
select 'bad: ', datediff(ms, @start, @end);
go
在我的测试机上,“好”情况得到约600ms,而坏情况得到1400ms。 因此,例外情况使时间加倍,但几乎没有分钟。
接下来,让我们做同样的事情,但是要从托管客户端进行:
static void Main(string[] args)
{
try
{
using (SqlConnection conn = new SqlConnection(@"..."))
{
conn.Open();
Stopwatch sw = new Stopwatch();
sw.Start();
using (SqlTransaction trn = conn.BeginTransaction())
{
SqlCommand cmd = new SqlCommand(
"insert into good (a) values (@i)", conn, trn);
SqlParameter p = new SqlParameter("@i", SqlDbType.Int);
cmd.Parameters.Add(p);
for (int i = 0; i < 10000; ++i)
{
p.Value = i;
cmd.ExecuteNonQuery();
}
trn.Commit();
sw.Stop();
}
Console.WriteLine("Good: {0}", sw.Elapsed);
int excount = 0;
sw.Reset();
sw.Start();
using (SqlTransaction trn = conn.BeginTransaction())
{
SqlCommand cmd = new SqlCommand(
"insert into bad (a) values (@i)", conn, trn);
SqlParameter p = new SqlParameter("@i", SqlDbType.Int);
cmd.Parameters.Add(p);
for (int i = 0; i < 10000; ++i)
{
p.Value = i;
try
{
cmd.ExecuteNonQuery();
}
catch (SqlException s)
{
++excount;
}
}
trn.Commit();
sw.Stop();
}
Console.WriteLine("Bad: {0} [Exceptions: {1}]",
sw.Elapsed, excount);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
结果(零售版本和调试版本的时间相似):
Good: 00:00:00.8601303
Bad: 00:01:57.8987760 [Exceptions: 10000]
因此,时间已从原始SQL Server时间1.4秒缩短到将近2分钟。 因此,结案时,异常代价很高,对吗? 没那么快。 尝试运行未连接调试器的应用程序(Ctrl + F5):
Good: 00:00:00.6640281
Bad: 00:00:02.3746845 [Exceptions: 10000]
回到2秒。 因此,真正的罪魁祸首不是SQL Server,C#异常处理或SqlClient层。 是调试器 。 这是正常现象,因为调试器会做一些相当侵入性的事情,并且会在每次抛出的CLR异常上运行大量代码。 如果您的代码除了抛出异常外什么也不做,那么结果可能会产生很大的影响。 这被称为“ 第一次机会异常处理” , 其他人之前也曾说过 。
但是,如果正在调试应用程序,则调试器会在程序执行之前看到所有异常。 这是第一次机会异常与第二次机会异常之间的区别:调试器获得了第一次看到异常的机会(因此得名)。 如果调试器允许程序继续执行并且不处理异常,则程序将照常看到异常。
再一次证明,在处理性能问题时,最好是进行测量 。
如果您对客户执行相同的检查,您将能够快速报告那些发现良好记录的客户,并迅速报告错误。
您可以动态推断要写入的表的布局,从而需要重复的表布局规范,但是除非您的组件本质上非常通用,否则我认为这样做不值得。
异常会造成如此大的减慢,这似乎很奇怪,除非您当然要回滚事务,在这种情况下,我完全可以理解问题的减慢。
无论如何,您可以尝试的一件事是从表定义中推断出列的长度,并在将数据插入数据库之前将其截断。 因此,您只会插入良好的数据。
例外情况很慢,因为在抛出和捕获时,需要完成很多记账工作,需要完成所有定稿工作,而所有堆栈框架计算通常要花费更长的时间。 我认为您根本无法通过使用某些内置方法来加快此速度。
您必须自己运行检查,而不是SQL才能返回错误。 我知道这有点乏味,但是无法加快速度。
另一种选择是在并行线程中插入行。 这将加快速度,因为一行中的异常不会降低所有挂起的行的速度。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.