簡體   English   中英

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

[英]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異常上運行大量代碼。 如果您的代碼除了拋出異常外什么也不做,那么結果可能會產生很大的影響。 這被稱為“ 第一次機會異常處理”其他人之前也曾說過

但是,如果正在調試應用程序,則調試器會在程序執行之前看到所有異常。 這是第一次機會異常與第二次機會異常之間的區別:調試器獲得了第一次看到異常的機會(因此得名)。 如果調試器允許程序繼續執行並且不處理異常,則程序將照常看到異常。

再一次證明,在處理性能問題時,最好是進行測量

如果您對客戶執行相同的檢查,您將能夠快速報告那些發現良好記錄的客戶,並迅速報告錯誤。

您可以動態推斷要寫入的表的布局,從而需要重復的表布局規范,但是除非您的組件本質上非常通用,否則我認為這樣做不值得。

一些技巧:

  1. 創建一個參數化查詢, 准備並重用它。 這樣,SQL Server可以緩存執行計划
  2. 如果你有大量的數據要導入的,考慮使用批量導入功能( MERGEOPENROWSET是我對跨平台好玩目前最喜歡的) -它們比使用單獨的更新/插入語句快很多

異常會造成如此大的減慢,這似乎很奇怪,除非您當然要回滾事務,在這種情況下,我完全可以理解問題的減慢。

無論如何,您可以嘗試的一件事是從表定義中推斷出列的長度,並在將數據插入數據庫之前將其截斷。 因此,您只會插入良好的數據。

例外情況很慢,因為在拋出和捕獲時,需要完成很多記賬工作,需要完成所有定稿工作,而所有堆棧框架計算通常要花費更長的時間。 我認為您根本無法通過使用某些內置方法來加快此速度。

您必須自己運行檢查,而不是SQL才能返回錯誤。 我知道這有點乏味,但是無法加快速度。

另一種選擇是在並行線程中插入行。 這將加快速度,因為一行中的異常不會降低所有掛起的行的速度。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM