同樣的SQL語句在查詢分析器執行很快,但是網站上執行超時的詭異問題
同樣的SQL語句在查詢分析器執行很快,但是網站上執行超時,這個問題以前遇到過,解決辦法是重新啟動伺服器,但過一段時間後(時間長短不一定,一般為一天後),這次又出現了,不能總是重新啟動伺服器了事吧,決定探個究竟。
首先,開啟SQLSERVER 事務探查器,找到那個執行超時的SQL語句:
exec sp_executesql N' SELECT a.WorkNo,a.理財經理網點,a.理財經理姓名,a.序號,CAST( ROUND(a.金額/10000,2) as float) 金額 FROM [GetStatisticsAnalysis_ManagerWorkFeatTop3PM] ( @trantype , @manageid , @startime , @endtime , @Roleid ) a ',N'@trantype nvarchar(200),@manageid nvarchar(38),@startime nvarchar(21),@endtime nvarchar(21),@Roleid nvarchar(38)',@trantype=N'認購',@manageid=N'32800085',@startime=N'2010-01-01',@endtime=N'2010-12-31',@Roleid=N'5BBBBD85-27E4-4679-A010-0076FAD1589F'
怎麼看到的都是些 exe sp_executesql 的系統儲存過程呼叫?查閱資料得知,SQL SERVER 會把所有帶引數化查詢的SQL語句使用sp_executesql來執行,因為它能夠分析並快取查詢計劃,從而優化查詢效率,這也是為什麼通常說的“引數化查詢比拼接SQL要快”的原因。
將上面的SQL語句再拿到查詢分析器裡面執行,速度很快,不到1秒就出來了,將它再拿到另外一個.NET寫的資料庫查詢工具程式中執行,卻報出了跟網站一樣的錯誤:查詢超時!
百思不得其解,網上搜索了一下,果然有人遇到過通用的問題:
http://social.microsoft.com/Forums/zh-CN/visualcshartzhchs/thread/fcdc74d7-0e82-4d34-94c2-d22ba5946d3c
裡面有人說:
在sql server 2005裡執行儲存過程後,訊息視窗有這麼一句話“警告: 聚合或其他 SET 操作消除了空值。”。
ADO.net可能因為這個警告導致出結果很慢,雖然在sql server裡執行沒什麼問題。
原因是sum裡面沒有isnull一下。改了一下sql語句就好了。
再看看我們的這個SQL自定義函式GetStatisticsAnalysis_ManagerWorkFeatTop3PM,裡面果然有大段的聚合函式:
函式定義 ALTER FUNCTION [dbo].[GetStatisticsAnalysis_ManagerWorkFeatTop3PM] ( -- Add the parameters for the function here @TradeType varchar(200), @WorkNo varchar(38), @StartDate varchar(21), @EndDate varchar(21), @RoleGUId varchar(38) ) RETURNS TABLE AS RETURN ( -- Add the SELECT statement with parameter references here select * from ( select ROW_NUMBER()over(order by 金額 desc) 序號,* from ( select a.WorkNo,b.RealName 理財經理姓名,b.NetworkNO 理財經理網點 ,case when @TradeType='認購' then sum(a.認購金額) when @TradeType='申購' then sum(a.申購金額) when @TradeType='定投' then sum(a.定投金額) when @TradeType='銷售' then sum(a.買入金額) when @TradeType='贖回' then sum(a.賣出金額) end 金額 from [WFT_Batch_ManagerWorkFeatDetails] a inner join (select * from Tb_Common_User a where a.RoleGUId in('5BBBBD85-27E4-4679-A010-0076FAD1589F','9C2728D4-1E0A-40CD-95AC-6C20029F0871','A105F9F8-F9BB-4B68-9426-76E5D10DC1C7')) b on a.WorkNo=b.WorkNo where a.WorkNo is not null and b.WorkNo is not null and a.jjdm is not null and a.交易日期>=cast(@StartDate as datetime) and a.交易日期<=cast(@EndDate as datetime) group by a.WorkNo,b.RealName ,b.NetworkNO ) a ) a where a.序號<=3 union all select 0,a.WorkNo,'名下'+@TradeType+'客戶',b.NetworkNO 理財經理網點 ,case when @TradeType='認購' then sum(a.認購金額) when @TradeType='申購' then sum(a.申購金額) when @TradeType='定投' then sum(a.定投金額) when @TradeType='銷售' then sum(a.買入金額) when @TradeType='贖回' then sum(a.賣出金額) end 金額 from [WFT_Batch_ManagerWorkFeatDetails] a left join Tb_Common_User b on a.WorkNo=b.WorkNo where a.WorkNo =@WorkNo and a.jjdm is not null and a.交易日期>=cast(@StartDate as datetime) and a.交易日期<=cast(@EndDate as datetime) and a.WorkNo is not null group by a.WorkNo,b.RealName ,b.NetworkNO union all select 99,'','全轄平均','' 理財經理網點,a.金額/ (select case when count(*)=0 then 1 else count(*) end from Tb_Common_User a where a.RoleGUId =@RoleGUId) from( select case when @TradeType='認購' then sum(a.認購金額) when @TradeType='申購' then sum(a.申購金額) when @TradeType='定投' then sum(a.定投金額) when @TradeType='銷售' then sum(a.買入金額) when @TradeType='贖回' then sum(a.賣出金額) end 金額 from [WFT_Batch_ManagerWorkFeatDetails] a inner join (select * from Tb_Common_User a where a.RoleGUId =@RoleGUId) b on a.WorkNo=b.WorkNo where a.WorkNo is not null and a.jjdm is not null and a.交易日期>=cast(@StartDate as datetime) and a.交易日期<=cast(@EndDate as datetime) ) a ) GO
將sum裡面的欄位先ISNULL轉換下,修改這個SQL自定義函式,儲存,再呼叫這個函式,OK,不超時了!
但是,DBA告訴我,不可以這麼做,因為NULL值在業務上有特別的含義,不能隨便轉換!
沒法,只能將函式恢復原樣。
(補充:
執行procedure過程,出現“警告:聚合或其它 SET 操作消除了空值”警告
會導致儲存過程的結果集無法得到。
使用 set ansi_warnings off 可以遮蔽這個錯誤。。
在儲存過程的結尾再使用 set ansi_warnings on 恢復原來的設定
使用這個方法,可以解決本文標題的問題.
)
再次呼叫函式,還是沒有超時?難道跟這個NULL在聚合函式裡面的問題無關?
猜想應該是SQLSERVER將上次的查詢結果快取了,等等看。
第二天,問題又出現了,查詢超時,但這次既不能重新啟動伺服器,也不能修改這個自定義函式,怎麼辦?
同事幫我在網上搜索了一下,找到這篇文章:
裡面說,是引數型別不正確,必須設定為資料庫一致的引數型別。
我們的系統使用PDF.NET資料開發框架做的,所以要改這個問題只需要在SQL-MAP配置檔案裡面修改一下就可以了:
<?xml version="1.0" encoding="utf-8"?>
<!--
PWMIS SqlMap Ver 1.1.2 ,2006-11-22,http://www.pwmis.com/SqlMap/
Config by SqlMap Builder,Date:2010/9/19
請在VS的IDE選單 XML-》架構 裡面選擇架構檔案 SqlMap.xsd,這樣直接編輯本檔案將就可以有智慧提示了。
-->
<SqlMap EmbedAssemblySource="FTWebDAL,FTWebDAL.SqlMap.config">
<Script Type="SqlServer" Version="2005" ConnectionString="Server=192.168.1.2;uid=sa;pwd=sasa;database=XXDB;">
<CommandClass Name="StatisticalAnalysisDAL" Class="StatisticalAnalysisDAL" Description="" Interface="">
<!--省略N多SQL-MAP指令碼.-->
<Select CommandName="GetInfobyTranType" CommandType="Text" Method="" Description="根據交易型別獲取詳細資訊" ResultClass="DataSet">
<![CDATA[
SELECT a.WorkNo,a.理財經理網點,a.理財經理姓名,a.序號,CAST( ROUND(a.金額/10000,2) as float) 金額
FROM [GetStatisticsAnalysis_ManagerWorkFeatTop3PM] (
#trantype:String,String,200# ,
#manageid:String,String,38# ,
#startime:String,String,21# ,
#endtime:String,String,21# ,
#Roleid:String,String,38#
) a order by 序號 asc]]></Select>
</CommandClass>
</Script>
</SqlMap>
將上面的引數型別稍作修改:
<?xml version="1.0" encoding="utf-8"?>
<!--
PWMIS SqlMap Ver 1.1.2 ,2006-11-22,http://www.pwmis.com/SqlMap/
Config by SqlMap Builder,Date:2010/9/19
請在VS的IDE選單 XML-》架構 裡面選擇架構檔案 SqlMap.xsd,這樣直接編輯本檔案將就可以有智慧提示了。
-->
<SqlMap EmbedAssemblySource="FTWebDAL,FTWebDAL.SqlMap.config">
<Script Type="SqlServer" Version="2005" ConnectionString="Server=192.168.1.2;uid=sa;pwd=sasa;database=XXDB;">
<CommandClass Name="StatisticalAnalysisDAL" Class="StatisticalAnalysisDAL" Description="" Interface="">
<!--省略N多SQL-MAP指令碼.-->
<Select CommandName="GetInfobyTranType" CommandType="Text" Method="" Description="根據交易型別獲取詳細資訊" ResultClass="DataSet">
<![CDATA[
SELECT a.WorkNo,a.理財經理網點,a.理財經理姓名,a.序號,CAST( ROUND(a.金額/10000,2) as float) 金額
FROM [GetStatisticsAnalysis_ManagerWorkFeatTop3PM] (
#trantype:String,AnsiString,200# ,
#manageid:String,AnsiString,38# ,
#startime:String,AnsiString,21# ,
#endtime:String,AnsiString,21# ,
#Roleid:String,AnsiString,38#
) a order by 序號 asc]]></Select>
</CommandClass>
</Script>
</SqlMap>
把第二個String引數修改成AnsiString即可,對於SQL-MAP而言,引數格式是:
#ParameterName:Type,DbType,Length#,
所以相當於修改了DbType的型別。
儲存配置檔案,重新編譯,OK,問題解決!!
為什麼將DbType.String 修改成DbType.AnsiString就可以大大提高查詢效率呢?
查詢了資料,有下面的說法:
正如所述,ansistring是存放非unicode字元,而通常情況下,中文也是以ansi字元方式來存放的。
unicode的關鍵是裡面有 0 這個程式碼,而ansi裡面是以 0 表示結束。同樣,unicode裡需要 0 0 (連續的兩個0)來表示結束。
DbType.AnsiString指明瞭是ansi字符集,中間不會在進行轉換。
DbType.String沒有指明字符集,輸入的內容會根據資料庫來轉換(如連線時用的字符集、表的字符集等)
-----------------------------------------------------
麼資料庫裡面的字符集預設使用系統的字符集,也就是ANSI字符集,如果是中文作業系統,那麼它就是GB2312格式的。
顯然,GB2312不是Unicode字符集,但我們的程式裡面預設的String型別是Unicode型別的,因此會在程式的字符集和資料庫的字符集直接做轉換,有可能導致資料庫查詢效率大大降低。
----------------------------------------------------
另外也有人說,資料庫欄位是varchar型別,程式中設定成DbType.String奇慢,但是設定成DbType.AnsiString將很快:
使用DbParameter傳遞引數撈SQL Server資料速度異常的慢
http://adyhpq.blog.163.com/blog/static/3866700201062331034769/
c#Dbtype與SQL dbtype一一對應關係,提高效率關鍵
http://blog.csdn.net/luofuxian/archive/2010/11/02/5981539.aspx
- DbType:SqlDbType
- AnsiString:VarChar
- Binary:VarBinary
- Byte:TinyInt
- Boolean:Bit
- Currency:Money
- Date:DateTime
- DateTime:DateTime
- Decimal:Decimal
- Double:Float
- Guid:UniqueIdentifier
- Int16:SmallInt
- Int32:Int
- Int64:BigInt
- Object:Variant
- Single:Real
- String:NVarChar
- Time:DateTime
- AnsiStringFixedLength:Char
- StringFixedLength:NChar
- Xml:Xml
- DateTime2:DateTime2
- DateTimeOffset:DateTimeOffset
============================================
還有一種說法,可能跟SQLSERVER 2005的一個Bug有關:
FIX: 系統效能可能很慢時應用程式送出許多查詢中針對使用簡單的引數化的 SQL Server 2005 資料庫
http://support.microsoft.com/kb/920206/zh-tw