[SQL SERVER]提高動態查詢效能

[SQL SERVER]提高動態查詢效能

現實世界中,使用者希望系統可以提供多個查詢條件欄位,

但系統只要越彈性,免不了就要付出一些效能代價,

所以這篇簡單示範一下我個人如何提高動態查詢效能。

 

1.使用 IF

這方法算是最常見的,基本上就是透過IF(is not null)判斷來組TSQL,

但當前端條件過多時TSQL代碼可讀性就變得很低

ps: is not null會多出篩選運算子

 

declare @orderid int,@TrackingNumber nvarchar(25),@unitp money,@mysql nvarchar(max),@mycondiction nvarchar(max),@para nvarchar(max)
,@breakline char(2)
set @breakline=char(13) + char(10)  
set @orderid=43659
set @TrackingNumber=N'4911-403C-98'
set @unitp=12
set @para='@orderid int,@TrackingNumber nvarchar(25),@unitp money'

set @mysql='SELECT TOP 10000 t.SalesOrderID,t.SalesOrderDetailID,t.CarrierTrackingNumber,t.UnitPrice,t.OrderQty 
FROM  sales.SalesOrderDetail t join  sales.SalesOrderHeader t2 
on t.SalesOrderID=t2.SalesOrderID where 1=1 '+@breakline
set @mycondiction=''

--using if
if @orderid is not null
 set @mycondiction+=' and t.SalesOrderID = @orderid '+@breakline
if @TrackingNumber is not null
 set @mycondiction+=' and t.CarrierTrackingNumber = @TrackingNumber '+@breakline
if @unitp is not null
 set @mycondiction+=' and t.UnitPrice >= @unitp '+@breakline

if @mycondiction>''  
   set @mysql+=@mycondiction+' option(recompile)'


exec sp_executesql @mysql,@para,@orderid,@TrackingNumber,@unitp
 

image

image

 

 

為什麼要使用recompile

如果你還不知道什麼是參數探測,那麼以下兩篇文章建議閱讀一下

[SQL SERVER][Memo]了解參數探測行為(1)

[SQL SERVER][Memo]了解參數探測行為(2)

一般我個人只要在SP中組動態SQL我大部分會加上 recompile 選項,

透過該選項主要是要避免參數探測問題(雖然大部分情況快取執行計畫是有幫助的),

由於每一次都會重新編譯執行計畫,所以你可以很確定該計畫絕對是經過優化的,

但該選項建議測試過後才使用,以避免過度修正執行計畫造成反效果。

 

2.使用 or

如果不想使用IF的話,可以使用 or is null 來提高可讀性,

只要輸入參數null將使條件永遠true,但這裡還是要注意使用recompile來提高效能

declare @orderid int,@TrackingNumber nvarchar(25),@unitp money,@mysql nvarchar(max),@mycondiction nvarchar(max),@para nvarchar(max)
,@breakline char(2)
set @breakline=char(13) + char(10)  
set @orderid=43659
set @TrackingNumber=N'4911-403C-98'
set @unitp=12
set @para='@orderid int,@TrackingNumber nvarchar(25),@unitp money'

set @mysql='SELECT TOP 10000 t.SalesOrderID,t.SalesOrderDetailID,t.CarrierTrackingNumber,t.UnitPrice,t.OrderQty 
FROM  sales.SalesOrderDetail t join  sales.SalesOrderHeader t2 
on t.SalesOrderID=t2.SalesOrderID where (t.SalesOrderID = @orderid or  @orderid is null)
and (t.CarrierTrackingNumber = @TrackingNumber or @TrackingNumber is null)
and (t.UnitPrice >= @unitp or @unitp is null) '

exec sp_executesql @mysql,@para,@orderid,@TrackingNumber,@unitp


 image

image

 

 

加上option(recompile)修正執行計畫

image

image

 

 

3.使用case when

這樣的寫法將浪費更多I/O(參考[SQL SERVER] case when 效能殺手)

declare @orderid int,@TrackingNumber nvarchar(25),@unitp money,@mysql nvarchar(max),@mycondiction nvarchar(max),@para nvarchar(max)
set @orderid=43659
set @TrackingNumber=N'4911-403C-98'
set @unitp=12
set @para='@orderid int,@TrackingNumber nvarchar(25),@unitp money'
set @mycondiction=''

--using case when
set  @mysql='SELECT TOP 10000 t.SalesOrderID,t.SalesOrderDetailID,t.CarrierTrackingNumber,t.UnitPrice,t.OrderQty  
FROM  sales.SalesOrderDetail t join  sales.SalesOrderHeader t2 
on t.SalesOrderID=t2.SalesOrderID
WHERE 1=1 
and t.SalesOrderID =case when @orderid IS NOT NULL then @orderid else t.SalesOrderID  end
and t.CarrierTrackingNumber =case when @TrackingNumber IS NOT NULL then @TrackingNumber else t.CarrierTrackingNumber  end
and t.UnitPrice>=case when @unitp IS NOT NULL then @unitp else t.UnitPrice  end '

exec sp_executesql @mysql,@para,@orderid,@TrackingNumber,@unitp

 

image

image

 

 

要提高動態查詢效能就需搭配正確索引,

我針對動態TSQL設計索引規則是由多到少、觀察統計,

說白話一點就是先設計建立包含所有條件索引,

後調整寬鬆條件索引,並讓估計值和實際值差距<=10倍

 

 

create proc querysalesorder
(
@orderid     int          = NULL,
@fromorderdate    datetime     = NULL,
@toorderdate      datetime     = NULL,
@minprice    money        = NULL,
@maxprice    money        = NULL,
@salesfirstname nvarchar(50)=NULL,
@saleslastname nvarchar(50) = NULL
)
as
set nocount on
select distinct t.SalesOrderID,t.UnitPrice,t.orderqty 
,t1.OrderDate
,t2.salesquota
,t3.FirstName,t3.MiddleName,t3.LastName
from SalesOrderDetail t 
left join SalesOrderHeader t1 on t.SalesOrderID=t1.SalesOrderID
left join SalesPerson t2 on t1.SalesPersonID=t2.BusinessEntityID
left join Person t3 on t3.BusinessEntityID= t2.BusinessEntityID
where (t1.SalesOrderID = @orderid OR @orderid IS NULL)
and (t1.OrderDate >= @fromorderdate OR @fromorderdate IS NULL)
and (t1.OrderDate <= @toorderdate OR @toorderdate IS NULL)
and (t.UnitPrice >= @minprice OR @minprice IS NULL)
and (t.UnitPrice <= @maxprice OR @maxprice IS NULL)
and (t3.FirstName like  @salesfirstname+'%' OR @salesfirstname IS NULL)
and (t3.LastName like  '%'+@saleslastname OR @saleslastname IS NULL)
order by t.SalesOrderID
option(recompile)

 

設計建立索引

create index idx1 on SalesOrderDetail(SalesOrderID,UnitPrice)
include(orderqty)

create index idx1 on SalesOrderHeader(SalesOrderID,SalesPersonID,OrderDate)

create index idx1 on SalesPerson(BusinessEntityID)
include(salesquota)

create index idx1 on Person(BusinessEntityID,FirstName,LastName)
include(MiddleName)

 

測試索引效益

querysalesorder 43659,'2005-07-01 00:00:00','2006-07-01 00:00:00',1.2,100.5,'t','r'

image

image

 
 

 

 

 

參考

Dynamic Search Conditions in T‑SQL