简体   繁体   English

sql异常非常慢……如何加快速度?

[英]Sql exceptions are very slow… how to speed up?

I have a piece of code that imports data into a sql table. 我有一段代码将数据导入到sql表中。 When there's a problem with the incoming data (wrong data type or would be truncated, for example) it generates an exception as expected, but it's very slow. 当传入数据出现问题时(例如,错误的数据类型或将被截断),它将生成一个预期的异常,但是它非常慢。 I could import 10,000 rows in seconds if they're all good, but sometimes the source data needs a little tlc and so the app imports the good rows and reports the exceptions so they can be fixed. 如果它们都很好,我可以在几秒钟内导入10,000行,但是有时源数据需要一点点tlc,因此应用程序会导入这些行并报告异常,以便对其进行修复。 If, say, all 10,000 rows have a field that needs to be shortened because it would be truncated then it would take more like 10 minutes instead of just 10 or 20 seconds. 例如,如果所有10,000行的一个字段都需要缩短,因为该字段将被截断,那么它将花费大约10分钟而不是10或20秒。

The added time is not because of any code being run in the catch block... it does this even if there is no code in the catch block at all. 增加的时间并不是因为catch块中正在运行任何代码...即使在catch块中根本没有代码,它也会这样做。 Anyone know if this is just the way it is or if there's anything that can be done to make it faster? 有人知道这仅仅是它的方式吗,还是可以做些什么使它更快?

Edit: Okay, per request I'm adding some code below but I think you will quickly see why I didn't think it was necessary to do so at first :) 编辑:好的,根据请求,我在下面添加了一些代码,但是我想您很快就会明白为什么我认为一开始没有必要这样做:)

    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) just returns a string (as you probably already inferred by it's use in the SqlCommand constructor parameter). _importCommand.CreateSqlCommand(r)仅返回一个字符串(您可能已经在SqlCommand构造函数参数中使用它来推断出该字符串)。 And as you can see I have not added any code whatsoever to the catch block yet. 如您所见,我还没有向catch块添加任何代码。 When there is no exception it executes sCom.ExecuteNonQuery(); 如果没有异常,它将执行sCom.ExecuteNonQuery(); very quickly, but when there is an exception there is a short pause. 速度很快,但是如果有例外,则会有短暂的停顿。 The ImportRow method is called in a loop from another function, but I have positively identified the lag as coming from the try block of this method so the other code is not relevant. ImportRow方法是从另一个函数循环调用的,但是我肯定地确定此方法的try块产生了滞后,因此其他代码无关。

When you hit an error during a bulk insert operation the successfully inserted records have to be rolled back. 当您在批量插入操作中遇到错误时,必须回滚成功插入的记录。 As a rule of thumb, the undo of an operation during rollback will always be slower compared to the operation being undone, because it has to read back the log (which is optimized for write, not for read) and compensate the operation. 根据经验,与撤消操作相比,回滚过程中操作的撤消总是比较慢,因为它必须回读日志(为写而不是读进行了优化) 补偿了该操作。 However a different from 10 seconds to 10 minutes cannot be explained by this. 但是,由此无法解释10秒到10分钟之间的差异。 Therefore, unless you can provide some sort of evidence that is the server that takes 10 minutes, I must conclude that is you application code that is doing something for those 10 minutes. 因此,除非您能提供某种证据证明服务器需要10分钟,否则我必须得出结论,这就是您的应用程序代码在这10分钟内正在做什么。

Updated 更新

Lets try 10k inserts that all fail and compare them with 10k inserts that succeed: 让我们尝试全部失败的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

On my test machine I get ~600ms for the 'good' case and about 1400ms for the bad. 在我的测试机上,“好”情况得到约600ms,而坏情况得到1400ms。 So the exceptions double the time, but nowhere near minutes. 因此,例外情况使时间加倍,但几乎没有分钟。

Next, lets do the very same, but from managed client: 接下来,让我们做同样的事情,但是要从托管客户端进行:

    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);
        }
    }

The results (both Retail and Debug builds have similar times): 结果(零售版本和调试版本的时间相似):

Good: 00:00:00.8601303
Bad: 00:01:57.8987760 [Exceptions: 10000]

So the time has gone into nearly 2 minutes from the 1.4 seconds that is the raw SQL Server time. 因此,时间已从原始SQL Server时间1.4秒缩短到将近2分钟。 So case closed, exceptions are expensive, right? 因此,结案时,异常代价很高,对吗? Not so fast. 没那么快。 Try running the application without debugger attached (Ctrl+F5): 尝试运行未连接调试器的应用程序(Ctrl + F5):

Good: 00:00:00.6640281
Bad: 00:00:02.3746845 [Exceptions: 10000]

Back to 2 seconds. 回到2秒。 so the true culprit is not SQL Server, is not C# exception handling nor the SqlClient layer. 因此,真正的罪魁祸首不是SQL Server,C#异常处理或SqlClient层。 Is the Debugger . 调试器 This is normal, since the debugger does some pretty invasive things and runs a lot of code on each CLR exception thrown. 这是正常现象,因为调试器会做一些相当侵入性的事情,并且会在每次抛出的CLR异常上运行大量代码。 If your code does basically nothing but throw exceptions, the results can have a big impact. 如果您的代码除了抛出异常外什么也不做,那么结果可能会产生很大的影响。 It is called First Chance Exception Handling and other have said it before . 这被称为“ 第一次机会异常处理”其他人之前也曾说过

However, if the application is being debugged, the debugger sees all exceptions before the program does. 但是,如果正在调试应用程序,则调试器会在程序执行之前看到所有异常。 This is the distinction between the first and second chance exception: the debugger gets the first chance to see the exception (hence the name). 这是第一次机会异常与第二次机会异常之间的区别:调试器获得了第一次看到异常的机会(因此得名)。 If the debugger allows the program execution to continue and does not handle the exception, the program will see the exception as usual. 如果调试器允许程序继续执行并且不处理异常,则程序将照常看到异常。

Yet again, proof that when dealing with performance issues, best is to measure . 再一次证明,在处理性能问题时,最好是进行测量

If you perform the same checks on your client, you'll be able to report those find the good records very quickly, and report errors back quickly as well. 如果您对客户执行相同的检查,您将能够快速报告那些发现良好记录的客户,并迅速报告错误。

You can dynamically deduce the layout of the table you're writing to, as to need duplicated the table layout specifications, but I don't think it's worth the bother unless your component is very generic in nature. 您可以动态推断要写入的表的布局,从而需要重复的表布局规范,但是除非您的组件本质上非常通用,否则我认为这样做不值得。

Some tips: 一些技巧:

  1. Create a parameterised query, prepare it and reuse it. 创建一个参数化查询, 准备并重用它。 This way, SQL Server can cache the execution plan 这样,SQL Server可以缓存执行计划
  2. If you've got a lot of data to import, look into using the bulk import functionality ( MERGE and OPENROWSET are my current favourite for cross platform fun) - they are a lot quicker than using individual update/insert statements. 如果你有大量的数据要导入的,考虑使用批量导入功能( MERGEOPENROWSET是我对跨平台好玩目前最喜欢的) -它们比使用单独的更新/插入语句快很多

It seems strange that an exception would be causing so much of a slow-down, unless of course you were rolling back the transaction in which case I can totally understand the slow-down on a problem. 异常会造成如此大的减慢,这似乎很奇怪,除非您当然要回滚事务,在这种情况下,我完全可以理解问题的减慢。

Anyway, one thing you could try is to deduce the length of the columns from the table definition, and truncate the data PRIOR to inserting in to the database. 无论如何,您可以尝试的一件事是从表定义中推断出列的长度,并在将数据插入数据库之前将其截断。 Therefore, you'll only ever been inserting good data. 因此,您只会插入良好的数据。

Exceptions are slow, because while throwing and catching, there is lot of bookkeeping that needs to be done, it's all finalizes that need to execute and all stack frame calculations which takes longer in general anyway. 例外情况很慢,因为在抛出和捕获时,需要完成很多记账工作,需要完成所有定稿工作,而所有堆栈框架计算通常要花费更长的时间。 I don't think you can speed up this in anyway at all by using some inbuilt methods. 我认为您根本无法通过使用某些内置方法来加快此速度。

You will have to run checks by yourself instead of SQL to return you the error. 您必须自己运行检查,而不是SQL才能返回错误。 I know it is little tedious but there is no way to speed up. 我知道这有点乏味,但是无法加快速度。

Another alternative would be to insert rows in parallel threads. 另一种选择是在并行线程中插入行。 This will speed up, as exception in one row will not slow down all pending rows. 这将加快速度,因为一行中的异常不会降低所有挂起的行的速度。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM