0

I have an SQL request which executes usually with less than 1,5s. But sometimes, it takes more than 30s and this behaviour last hours.

Here is the actual request :

set dateformat ymd; 

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 

SELECT  Categories.Id as CategoryId, 
    ( 
     CASE when VeryBigChildTable.Type = 9 
    then 
        Categories.Name
    else 
        NULL
    end
    ) as CategoryName, 
    OS1.Id,
    OS1.StartDateUTC,
    VeryBigChildTable.StartDateUTC as StartDateUTCSP,
    Categories.Client_Id,
    VeryBigChildTable.ArticleScriptArticleSPError_ArticleSP_Id as FailedStep, 
    VeryBigChildTable.[Order] as Position,
    (
      CASE when Categories.Id IS null
        then 
        ( cast(TSHeaders.Id as varchar(3)) + '__' + cast(OS1.Id as varchar(15) ))
        else
    ( cast(Categories.Id as varchar(3)) + '_' + cast(OS1.Id as varchar(15) ))
    end )
     as GroupKey,
    ( 
     CASE when VeryBigChildTable.Type = 9 
    then 
         TSHeaders.CurrentName
    else 
        NULL
    end
    ) , 
    SPHeaders.Id,
 stuff((
    SELECT '|' + ArticleCategories.Name
    from ArticleCategories 
   inner join ArticleCategoryArticleScript 
    on ArticleCategoryArticleScript.BigTable_Id = OS1.Id
    and ArticleCategoryArticleScript.ArticleCategories_Id = ArticleCategories.Id
    for xml path('')),1,1,'')

from BigTable as OS1
inner join VeryBigChildTable on VeryBigChildTable.ArticleScript_Id = OS1.Id
inner join TSHeaders on TSHeaders.Id = OS1.TSArticle_Id
inner join SPHeaders on SPHeaders.Id = VeryBigChildTable.SPHeader_Id
inner join Categories on Categories.TSHeader_Id = OS1.TSArticle_Id
left outer join ArticleNetworks on ArticleNetworks.ArticleSPArticleNetwork_ArticleNetwork_Id = VeryBigChildTable.Id
where 
OS1.StartDateUTC >= '2015-06-18 10:12:15'  
and OS1.StartDateUTC <= '2015-06-19 10:12:15'  
and TSHeaders.Id in (319,318,322,323,324,326,328,343,345,346,347,550,552,561,565,595,612,613)  
and Categories.Id in (494,491,484,487,511,235,241,245,265,539,540,242,236,239,240,267,268,269) 

This query returns about 20K line.

VeryBigChildTable is 260 millions line table and BigTable is 60 millions line table. Other tables are very small (less than 2K line).

My configuration : SQL Server 2008 R2 (Mirrored) on Windows 2008 Server, Xeon 16 cores and 32GB RAM.

What may cause this request run sometimes more than 30 s ? (Knowing that I did not found any indexation task during that period)

What can I do to optimize this request ?

All your remarks are welcome.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Pablo Honey
  • 1,074
  • 1
  • 10
  • 23
  • 1
    During the few hours that this takes 30 secs do run, do all other queries run OK or they get slower as well? – artm Jun 19 '15 at 12:02
  • 1
    Do you have memory pressure in the mean time due to your low server memory? – Ionic Jun 19 '15 at 12:03
  • other query seems to run normally on that period. however, this is the only big query I have. – Pablo Honey Jun 19 '15 at 12:04
  • @Ionic : I can't verify that because SQL Server always use the whole memory all the time. – Pablo Honey Jun 19 '15 at 12:05
  • 1
    @PabloHoney Well you can verify. I've added you some queries below. You can also take a look at `SELECT * FROM sys.dm_os_process_memory`. SQL Server is designed to use all memory and manage it on it's own. – Ionic Jun 19 '15 at 12:10
  • 1
    You can declare a table var with the content of TSHeaders.Id in (319,318,322,323,324,326,328,343,345,346,347,550,552,561,565,595,612,613) and first make an inner join of OS1 with that table var on OS1.TSArticle_Id ... this can make your query a bit faster (check your index of OS1 for this) – Tjasun Jun 19 '15 at 12:13

2 Answers2

2

It could have many causes.

  • many other processes running in parallel and you have cpu pressure. You can check the is_idle flag for example: SELECT * FROM sys.dm_os_schedulers WHERE scheduler_id <= 256
  • you may have memory pressure due to other bigger queries. You can check your current memory allocations SELECT * FROM sys.dm_exec_query_memory_grants. Maybe you'll see some more queries requesting for memory. You can also take a look at SELECT * FROM sys.dm_os_process_memory.
  • you may try to RECOMPILE your query by using OPTION(RECOMPILE) at the end of your query to get a new query plan which fits the current situation.
Ionic
  • 3,884
  • 1
  • 12
  • 33
  • 2
    Sudden changes in performance in query execution are in most cases the result of sql not being able to determin the right way to execute the query. Apart from maybe missing some indexes or index information (included columns) this can also be caused by having too many joins. The recompile option may help, but is not the final solution. Check to see if the indexes (if any) are being used right. – Tom Jun 19 '15 at 12:11
  • Yes I know that it's not the final solution. But it will help to solve the problem quickly and then analyze it afterwards. For a analysis of indices etc. he provided less information. A execution plan would be helpful. But anyway, I provided some queries to analyze the current situation too. ;-) – Ionic Jun 19 '15 at 12:13
  • I just changed the order of the join tables and it changed everything. Ty – Pablo Honey Aug 31 '15 at 11:48
2

If this happens time to time, and it seems everything else is working just fine except the one query, my guess is that for some reason the query has a new query plan. To be sure, I would recommend checking what the query plan is normally and what it is when the performance is bad.

To get a query plan when the process is running you can use something like this:

SELECT qp.* 
FROM sys.dm_exec_requests r 
cross apply sys.dm_exec_query_plan(r.plan_handle) qp
WHERE session_id = <spid of the process here>;

If you want to check what's been happening, and also get the query plan later, you can look at dm_exec_query_stats. It also shows nice statistics like total logical reads, worker time and execution count per statement:

select top 100
SUBSTRING(t.text, (s.statement_start_offset/2)+1,
((CASE s.statement_end_offset
WHEN -1 THEN DATALENGTH(t.text)
ELSE s.statement_end_offset
END - s.statement_start_offset)/2) + 1) as statement_text,
t.text,
s.total_logical_reads, 
s.total_logical_reads / s.execution_count as avg_logical_reads,
s.total_worker_time, 
s.total_worker_time / s.execution_count as avg_worker_time,
s.execution_count,
creation_time,
last_execution_time,
cast(p.query_plan as xml) as query_plan
from sys.dm_exec_query_stats s
cross apply sys.dm_exec_sql_text (sql_handle) t
cross apply sys.dm_exec_text_query_plan (plan_handle, statement_start_offset, statement_end_offset) p
where t.text like '%VeryBigChildTable%'
-- this way you'll get everything related to the big table
order by s.total_logical_reads desc

The statistics are available only for those plans that exist in cache, so if for some reason the plan gets dropped out, the statistics are lost too.

James Z
  • 12,209
  • 10
  • 24
  • 44