回答

收藏

SQL Server:存储过程变得非常慢,原始SQL查询还是很快的

技术问答 技术问答 285 人阅读 | 0 人回复 | 2023-09-14

当原始的时候,我们正在为一个奇怪的问题而挣扎SQL当执行相当快时,存储过程将变得非常缓慢。
" \7 b% q- I* d# L" ]我们有& V  _5 p2 `2 }( M( s8 N* l
SQL Server 2008 R2 Express Edition SP1 10.50.2500.上面有多个数据库。8 j  d. _9 i4 M4 [' m( H
一个数据库(大小约747Mb) ; l# f8 w# @; K. I+ `3 O4 z
存储过程采用不同的参数,确实从数据库中的多个表中选择。
代码:! p4 V5 w" b$ g" E* T9 y% p. G
ALTER Procedure [dbo].[spGetMovieShortDataList](   @MediaID int = null,  @Rfa nvarchar(8) = null,  @LicenseWindow nvarchar(8) = null,  @OwnerID uniqueidentifier = null,  @LicenseType nvarchar(max) = null,  @PriceGroupID uniqueidentifier = null,  @Format nvarchar(max) = null,  @GenreID uniqueidentifier = null,  @Title nvarchar(max) = null,  @Actor nvarchar(max) = null,  @ProductionCountryID uniqueidentifier = null,        @DontReturnMoviesWithNoLicense bit = 0,  @DontReturnNotReadyMovies bit = 0,  @take int = 10,  @skip int = 0,  @order nvarchar(max) = null,  @asc bit = 1)as begin  declare @SQLString nvarchar(max);  declare @ascending nvarchar(5);  declare @ParmDefinition nvarchar(max);  set @ParmDefinition = '@MediaID int, declare @now DateTime;  declare @Rfa nvarchar(8)           @LicenseWindow nvarchar(8)           @OwnerID uniqueidentifier,                                    @LicenseType nvarchar(max),         @PriceGroupID uniqueidentifier,         @Format nvarchar(max),         @GenreID uniqueidentifier,         @Title nvarchar(max),         @Actor nvarchar(max),         @ProductionCountryID uniqueidentifier,         @DontReturnMoviesWithNoLicense bit = 0,    @DontReturnNotReadyMovies bit = 0,    @take int,         @skip int,         @now DateTime';   set @ascending = case when @asc = 1 then 'ASC' else 'DESC' end     set @now = GetDate();   set @SQLString = 'SELECT distinct m.ID,m.EpisodNo,m.MediaID,p.Dubbed,pf.Format,t.OriginalTitle into #temp                FROM Media m                inner join Asset a1 on m.ID=a1.ID                inner join Asset a2 on a1.ParentID=a2.ID                inner join Asset a3 on a2.ParentID=a3.ID                inner join Title t on t.ID = a3.ID                inner join Product p on a2.ID = p.ID                left join AssetReady ar on ar.AssetID = a1.ID                left join License l on l.ProductID=p.ID                left join ProductFormat pf on pf.ID = p.Format             CASE WHEN @PriceGroupID IS NOT NULL THEN               left join LicenseToPriceGroup lpg on lpg.LicenseID = l.ID ' ELSE '' END                  CASE WHEN @Title IS NOT NULL THEN               left join LanguageAsset la on la.AssetID = m.ID ' ELSE '' END                  CASE WHEN @LicenseType IS NOT NULL THEN               left join LicenseType lt on lt.ID=l.LicenseTypeID ' ELSE '' END                  CASE WHEN @Actor IS NOT NULL THEN               left join Cast c on c.AssetID = a1.ID ' ELSE '' END                  CASE WHEN @GenreID IS NOT NULL THEN               left join ListToCountryToAsset lca on lca.AssetID=a1.ID ' ELSE '' END                  CASE WHEN @ProductionCountryID IS NOT NULL THEN               left join ProductionCountryToAsset pca on pca.AssetID=t.ID ' ELSE '' END                           where          = case                      when @Rfa = ''All'' then                 when @Rfa = ''Ready'' then ar.Rfa                    when @Rfa = ''NotReady'' and (l.TbaWindowStart is null OR l.TbaWindowStart = 0) and ar.Rfa = 0 and ar.SkipRfa = 0 then                 when @Rfa = ''Skipped'' and ar.SkipRfa = 1 then              end)                            CASE WHEN @LicenseWindow IS NOT NULL THEN         AND           = (case                     when (@LicenseWindow = 1 And (l.WindowEnd  @now))) then                 when (@LicenseWindow = 4 And ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now))) then                 when (@LicenseWindow = 3 And ((l.WindowEnd  @now)))) then                 when (@LicenseWindow = 5 And ((l.WindowEnd  @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then                 when (@LicenseWindow = 6 And ((l.TbaWindowStart = 0 and l.WindowStart  @now)) or ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then                 when ((@LicenseWindow = 7 Or @LicenseWindow = 0) And ((l.WindowEnd  @now)) or ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then 1                 end) ' ELSE '' END                  CASE WHEN @OwnerID IS NOT NULL THEN               AND (l.OwnerID = @OwnerID) ' ELSE '' END                  CASE WHEN @MediaID IS NOT NULL THEN               AND (m.MediaID = @MediaID) ' ELSE '' END                  CASE WHEN @LicenseType IS NOT NULL THEN               AND (lt.Name = @LicenseType) ' ELSE '' END                  CASE WHEN @PriceGroupID IS NOT NULL THEN               AND (lpg.PriceGroupID = @PriceGroupID) ' ELSE '' END                  CASE WHEN @Format IS NOT NULL THEN               AND (pf.Format = @Format) ' ELSE '' END                  CASE WHEN @GenreID IS NOT NULL THEN               AND (lca.ListID = @GenreID) ' ELSE '' END                  CASE WHEN @DontReturnMoviesWithNoLicense = 1 THEN               AND (l.ID is not null) ' ELSE '' END                  CASE WHEN @Title IS NOT NULL THEN               AND (t.OriginalTitle like N   @Title    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''OR la.LocalTitle like N   @Title  ELSE '' END                  CASE WHEN @Actor IS NOT NULL THEN               AND (rtrim(ltrim(replace(c.FirstName     c.MiddleName     c.LastName,like '''''''   rtrim(ltrim(replace(@Actor,   ''%'') ' ELSE '' END                  CASE WHEN @DontReturnNotReadyMovies = 1 THEN               AND ((ar.ID is not null) AND (ar.Ready = 1) AND (ar.CountryID = l.CountryID))' ELSE '' END                  CASE WHEN @ProductionCountryID IS NOT NULL THEN               AND (pca.ProductionCountryID = @ProductionCountryID)' ELSE '' END                                                               select #temp.* ,ROW_NUMBER() over (order by if @order = 'Title          begin                    set @SQLString = @SQLString   'OriginalTitle          end                else if @order = 'MediaID          begin                    set @SQLString = @SQLString   'MediaID          end                else                begin                    set @SQLString = @SQLString   'ID          end                set @SQLString = @SQLString        @ascending              rn                into #numbered                from #temp                declare @count int;                select @count = MAX(#numbered.rn) from #numbered                while (@skip >= @count )                   begin                    set @skip = @skip - @take;                end                select ID,MediaID,EpisodNo,Dubbed,Format,OriginalTitle,@count TotalCount from #numbered                where rn between @skip and @skip   @take                drop table #temp                    drop table #numbered          execute sp_executesql @SQLString,@ParmDefinition,@MediaID,@Rfa,@LicenseWindow,@OwnerID,@LicenseType,@PriceGroupID,@Format,@GenreID,                    @Title,@Actor,@ProductionCountryID,@DontReturnMoviesWithNoLicense,@DontReturnNotReadyMovies,@take,@skip,@now            end存储过程运行得非常好,速度非常快(通常需要1)-2秒)。
0 u3 R' J; ~/ {. ]" n通话示例
' h6 V. _$ W% k5 ?6 F  B8 ^DBCC FREEPROCCACHEEXEC    value = [dbo].[spGetMovieShortDataList]        @LicenseWindow =N          @Rfa = N'NotReady  @DontReturnMoviesWithNoLicense = False,       @DontReturnNotReadyMovies = True,       @take = 20,         @skip = 0,   @asc = False,       @order = N'ID'基本上,在执行存储过程中执行了三次SQL查询,第一个Select Into查询花费了99%的时间。3 L6 z$ @4 b! [4 G
该查询是  r( m1 N. A+ M- z; z& ?0 T  \- Q& L
declare @now DateTime;set @now = GetDate();SELECT DISTINCT    m.ID,m.EpisodNo,m.MediaID,p.Dubbed,pf.Format,t.OriginalTitleFROM Media mINNER JOIN Asset a1 ON m.ID = a1.IDINNER JOIN Asset a2 ON a1.ParentID = a2.IDINNER JOIN Asset a3 ON a2.ParentID = a3.IDINNER JOIN Title t ON t.ID = a3.IDINNER JOIN Product p ON a2.ID = p.IDLEFT JOIN AssetReady ar ON ar.AssetID = a1.IDLEFT JOIN License l on l.ProductID = p.IDLEFT JOIN ProductFormat pf on pf.ID = p.Format WHERE   ((l.TbaWindowStart is null OR l.TbaWindowStart = 0)     and ar.Rfa = 0 and ar.SkipRfa = 0)   And (l.WindowEnd 大量的数据更新(许多表和行受到更新的影响,但是DB大小几乎不变,现在是752)之后,存储过程变得非常缓慢。现在需要20到90秒。# S9 _: N/ v  L' I7 r
如果我从存储过程中得到原始的SQL查询-它会在1-2秒内执行。
6 ^  C! Q5 {, Q; M- u4 \7 x我们试过:
: N; `0 L1 D3 R, j6 J" u  {[ol]使用参数创建存储过程[/ol]SET ANSI_NULLS ON SET QUOTED_IDENTIFIER ON
$ S) h5 c: H. Q) k' o+ w[ol]用参数重新创建存储过程 with recompile
& ^( _, e, \/ O! a, k清除产品缓存后的存储过程DBCC FREEPROCCACHE
7 _' ]; H3 S7 v. f将where子句的一部分移动到连接部分
  U! U3 z7 N1 ~4 W. X# w重新索引表% N9 c# _6 {0 D; ~
从查询中更新类似以下句子的统计信息 UPDATE STATISTICS Media WITH FULLSCAN[/ol]然而,存储过程的执行仍然存在>> 30秒。& ]0 d# w0 n- u6 E- t3 M: K
但是,如果我运行的话SP生成的SQL查询-它执行不到2秒。8 n( q/ m9 J. c
我已经比较过了SP和原始SQL的执行计划-它们完全不同。在执行中RAW SQL的过程中-优化器正在使用合并连接,但正在执行中SP时-
$ ~; V9 B& w2 A, G哈希匹配(内部连接)用于优化器,就像没有索引一样。" S% G. a. C% J  y
RAW SQl的执行计划-快速
6 B/ f; T+ F2 B" y" L: ~SP的执行计划-慢
如果有人知道会发生什么-请提供帮助。提前感谢。!
* _, G9 S2 B% X5 Q* {. [/ j' a                                                               
3 F0 V5 z2 Z0 w    解决方案:                                                                $ ~9 u7 P5 t) L( y) g
                                                                尝试使用提示OPTIMIZE FORUNKNOWN。如果可行,可能比每次强制重新编译要好。问题是,最有效的查询计划取决于日期参数的实际值。SP时,SQL# v0 p, Y" G5 l8 g
Server必须猜测将提供什么实际值,并且在这里可能会做出错误的猜测。OPTIMIZE FOR UNKNOWN解决这个确切的问题。$ n- J$ M3 L7 t* |: S
查询结束时,添加
' [5 C$ L7 D( }' i% Q7 \3 JOPTION (OPTIMIZE FOR (@now UNKNOWN))
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则