I have an important SQL query that is performing too slowly. I pinpointed its performance issues to a view. Here is (roughly) what the view looks like:
-- the 'top 100' isn't part of the view, but I've added it for testing purposes
SELECT top 100
fs.*,
fss.Status,
fss.CreateDateTimeUtc StatusDateTimeUtc,
fss.IsError,
fss.CorrelationId
FROM dbo.FormSubmission fs WITH (NOLOCK)
CROSS APPLY (
SELECT TOP 1
FormId,
SubmissionId,
Status,
CreateDateTimeUtc,
IsError,
CorrelationId
FROM dbo.FormSubmissionStatus x WITH (NOLOCK)
WHERE x.FormId = fs.FormId AND x.SubmissionId = fs.SubmissionId
ORDER BY CreateDateTimeUtc DESC
) fss
If I run this, it's pretty quick. Here are some metrics and the execution plan:
However, as soon as I add this WHERE clause, it gets much slower.
where status in ('Transmitted', 'Acknowledging')
Metrics and exeuction plan:
I tried various types of new indexes and I haven't seen any real improvements. Here is an example of one:
create index ix_fss_datetime_formId_submissionId_status
on FormSubmissionStatus (CreateDateTimeUtc) include (formId, submissionId, status)
where status in ('Transmitted', 'Acknowledging')
What else can I try to speed this up?
If it helps to know, the PK for this table is a composite of FormId (uniqueidentifier), SubmissionId (varchar50), Status(varchar50), and CreateDateTimeUtc(datetime2)
Per @J.Salas's suggestion in the comments, I tried putting the WHERE clase in the subquery and saw a massive improvement (~700ms execution time vs the ~15s).
This isn't a solution, since I can't have that where clause in my view (the query that uses this view adds the WHERE clause). However, it does point to the subquery being a problem. Is there a way I could restructure it? Maybe do the subquery as a temp table and join on fs?
Looking at the query plan I do not hold hold much hope that the following could help. But your view query could be reformulated to use a CTE and ROW_NUMBER()
instead of CROSS APPLY
. I believe the following is equivalent in meaning:
WITH fss AS (SELECT
FormId,
SubmissionId,
Status,
CreateDateTimeUtc,
IsError,
CorrelationId,
ROW_NUMBER() OVER (PARTITION BY FormId, SubmissionId ORDER BY CreateDateTimeUtc DESC) AS RN
FROM dbo.FormSubmissionStatus)
SELECT
fs.*,
fss.Status,
fss.CreateDateTimeUtc StatusDateTimeUtc,
fss.IsError,
fss.CorrelationId
FROM dbo.FormSubmission fs
INNER JOIN fss
ON fss.FormId = fs.FormId
AND fss.SubmissionId = fs.SubmissionId
WHERE fss.RN = 1;
The APPLY
operator in your original query is saying for ever row in fs
run this query. Which if taken literally would cause that second query to run many many times. However SQL Server is free to optimize the plan so that the results are as if the subquery fss
was run once per row of fs
. So it may not be able to optimize the above any better.
For indexes I would try on (FormId, SubmissionId, CreateDateTimeUtc DESC)
maybe with INCLUDE (Status)
. But really anything besides the FormId, SubmissionId, and CreateDateTimeUtc would depend on how the view is used.
Query tuning is a matter of educated guesses combined with trial and error. To get better information for making informed guesses something like Brent Ozar's SQL Server First Responder Kit can help get information on what is actually happening in production. How to use is beyond the scope of a single StackOverflow answer.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.