[英]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.