萬級別的數據真的算不上什么大數據,但是這個檔的數據確實考核了普通的查詢語句的性能,不同的書寫方法有着千差萬別的性能,都在這個級別中顯現出來了,它不僅考核着你sql語句的性能,也考核着程序員的思想。
公司系統的一個查詢界面最近非常慢,界面的響應時間在6-8秒鍾時間,甚至更長。檢查發現問題出現在數據庫端,查詢比較耗時。該界面涉及到多個表中的數據,基本表有150萬數據,關聯子表的最多的一個700多萬數據,其它表數據也在幾十萬到幾百萬之間。其實按這樣的數據級別查詢響應時間應該在毫秒級內,不應該有這么長時間。那么接下來就該進行問題排查了。
由於這個這界面的功能主要是信息檢索,查詢比較復雜,太多的條件組合,使用存儲過程太多的局限性,因此查詢使用的是動態拼接的sql語句。查詢方式是最常用的1、獲取數據總數2、數據分頁。直接上代碼(部分條件)。
select numb=count(distinct t1.tlntcode) from ZWOMMAINM0 t1 inner join ZWOMMLIBM0 t2 on t1.tlntcode=t2.tlntcode join ZWOMEXPRM0 cp on t1.tlntcode=cp.tlntcode join ZWOMILBSM0 i on i.tlntcode=t1.tlntcode join ZWOMILBSM0 p on p.tlntcode=i.tlntcode join ZWOMILBSM0 l on l.tlntcode=i.tlntcode where isnull(t2.deletefg,'0')='0' and cp.companyn like '%IBM%' and cp.sequence=0 and i. mlbscode in('i0100','i0101','i0102','i0103','i0104','i0105','i0106') and i.locatype='10' and p.mlbscode in('p0100','p0102','p0104','p0200','p0600') and p.locatype='10' and l.mlbscode in('l030') and l.locatype='10'
查看執行時間
根據提示得知,整個查詢耗時花費在了分析和編譯為4秒,執行為0.7秒。查詢語句沒有發現什么問題,那么問題出現在了編譯,如果讓SQL語句執行原有的查詢計划,那么跳過編譯,只需0.7秒就能得到結果。那么如何做到預編譯,或者使用現有的執行計划?
SQL Server有一優化算法,它保存了以往執行sql語句的執行計划,所有的執行計划都會在sys.syscacheobjects表中存儲,如果當前sql語句在緩存表中能匹配到,那么它講執行匹配到的執行計划,而不再進行編譯。 那么解決方法我們首先想到的是存儲過程(這就是我們面試或者理論中經常說的存儲過程有預編譯,平時也就是說說,不存在什么深刻印象),是的它能實現預編譯,但是由於條件限制,查詢太過復雜,如果把沒有使用到查詢條件的表都關聯在一起反而影響到性能。排除存儲過程,我們另外想到的就是
EXEC SP_EXECUTESQL @Sql, N'@p NVARCHAR(50)',@p
為什么SP_EXECUTESQL 能復用查詢計划而普通sql語句不能,我們從緩存表中查看就能發現問題
select bucketid,cacheobjtype,objtype,objid,sql,sqlbytes from sys.syscacheobjects where cacheobjtype='Compiled Plan'
表中sql字段就是歷史執行計划的查詢語句,如果sql匹配成功那么就會執行匹配的執行計划。普通sql語句很難與之匹配,因為它不但包含了結構還包含了參數,復用率很低。而SP_EXECUTESQL 執行時只存儲結構,參數不存儲,因此復用率很高。找到了解決方法,那么直接行動。
declare @Sql nvarchar(max),@cpny nvarchar(50)='IBM'
declare @i varchar(1000)='i0100,i0101,i0102,i0103,i0104,i0105,i0106,i0107,i0109', @p varchar(1000)='p0100,p0101,p0102,p0103,p0104,p0107,p0201',@l varchar(1000)='l030' set @Sql='select value into #i from f_CSplit(@i,'','') select value into #p from f_CSplit(@p,'','') select value into #l from f_CSplit(@l,'','') select numb=count(distinct t1.tlntcode) from ZWOMMAINM0 t1 inner join ZWOMMLIBM0 t2 on t1.tlntcode=t2.tlntcode join ZWOMILBSM0 i on i.tlntcode=t1.tlntcode join ZWOMILBSM0 p on p.tlntcode=t1.tlntcode join ZWOMILBSM0 l on l.tlntcode=t1.tlntcode join ZWOMEXPRM0 cp on t1.tlntcode=cp.tlntcode where isnull(t2.deletefg,''0'')=''0'' and i.mlbscode in(select value from #i) and i.locatype=''10'' -- and i.mlbstype=''20'' and p.mlbscode in(select value from #p) and p.locatype=''10'' --and p.mlbstype=''40'' and l.mlbscode in(select value from #l) and l.locatype=''10''-- and l.mlbstype=''50'' and cp.companyn like ''%''+@cpny+''%'' and cp.sequence=0 ' EXEC SP_EXECUTESQL @Sql, N'@cpny NVARCHAR(50),@i NVARCHAR(50),@p NVARCHAR(50),@l NVARCHAR(50)', @cpny,@i,@p,@l
總耗時0.5秒,無論參數如何改變基本都在0.5秒波動,基本符合了我們的要求,如果想進一步優化還可以進行表分區等其他優化方案。
當我們發現查詢速度慢時,有可能是分析和編譯占用了你的太多時間,因此簡化你的查詢語句、復用執行計划能幫你走出困境。