Этот вопрос связан с моим предыдущим (опубликованным как анонимный пользователь - теперь у меня есть аккаунт), и, прежде чем я начну, я хотел бы отдать кредит Робу Фарли за предоставление правильной схемы индексации.
Но проблема не в схеме индексации.
Это оптимизатор запросов!
Запрос:
SELECT s.ID_i
, s.ShortName_v
, sp.Path_v
, ( SELECT TOP 1 1 -- has also user access on subsites ?
FROM SitePath_T usp
, UserSiteRight_t usr
WHERE usr.SiteID_i = usp.SiteID_i
AND usp.Path_v LIKE sp.Path_v + '%_'
AND usr.UserID_i = 1 )
FROM Site_T s
, SitePath_T sp
WHERE sp.SiteID_i = s.ID_i
AND s.ShortName_v LIKE '[a-y]%'
AND s.ParentID_i = 1
AND EXISTS ( SELECT *
FROM SitePath_T usp
, UserSiteRight_t usr
WHERE usr.SiteID_i = usp.SiteID_i
AND usp.Path_v LIKE sp.Path_v + '%'
AND usr.UserID_i = 1 )
... выполняется в:
CPU Reads Writes Duration
2073 49572 0 2241 -- more than 2 sec
План выполнения:
|--Compute Scalar(DEFINE:([Expr1014]=[Expr1014]))
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([sp].[Path_v]))
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Expr1016], [Expr1017], [Expr1018], [Expr1019]))
| |--Merge Join(Inner Join, MERGE:([sp].[SiteID_i])=([s].[ID_i]), RESIDUAL:([dbo].[SitePath_T].[SiteID_i] as [sp].[SiteID_i]=[dbo].[Site_T].[ID_i] as [s].[ID_i]))
| | |--Compute Scalar(DEFINE:([Expr1016]=[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1017]=LikeRangeStart([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1018]=LikeRangeEnd([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1019]=LikeRangeInfo([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
| | | |--Index Scan(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_SiteID_<Path>] AS [sp]), ORDERED FORWARD)
| | |--Sort(ORDER BY:([s].[ID_i] ASC))
| | |--Clustered Index Seek(OBJECT:([dbo].[Site_T].[IDXC_Site_ParentID+ShortName+ID] AS [s]), SEEK:([s].[ParentID_i]=(1) AND [s].[ShortName_v] >= '9þþþþþ' AND [s].[ShortName_v] < 'Z'), WHERE:([dbo].[Site_T].[ShortName_v] as [s].[ShortName_v] like '[a-y]%') ORDERED FORWARD)
| |--Nested Loops(Inner Join, OUTER REFERENCES:([usp].[SiteID_i], [Expr1020]) WITH UNORDERED PREFETCH)
| |--Clustered Index Scan(OBJECT:([dbo].[SitePath_T].[IDXC_SitePath_Path+SiteID] AS [usp]), WHERE:([dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1016]))
| |--Index Seek(OBJECT:([dbo].[UserSiteRight_T].[IDX_UserSiteRight_UserID+SiteID] AS [usr]), SEEK:([usr].[UserID_i]=(1) AND [usr].[SiteID_i]=[dbo].[SitePath_T].[SiteID_i] as [usp].[SiteID_i]) ORDERED FORWARD)
|--Compute Scalar(DEFINE:([Expr1014]=(1)))
|--Top(TOP EXPRESSION:((1)))
|--Nested Loops(Inner Join, OUTER REFERENCES:([usp].[SiteID_i], [Expr1021]) WITH UNORDERED PREFETCH)
|--Clustered Index Scan(OBJECT:([dbo].[SitePath_T].[IDXC_SitePath_Path+SiteID] AS [usp]), WHERE:([dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_'))
|--Index Seek(OBJECT:([dbo].[UserSiteRight_T].[IDX_UserSiteRight_UserID+SiteID] AS [usr]), SEEK:([usr].[UserID_i]=(1) AND [usr].[SiteID_i]=[dbo].[SitePath_T].[SiteID_i] as [usp].[SiteID_i]) ORDERED FORWARD)
Но если я приведу в исполнение индексы, будет выполняться следующий запрос:
SELECT s.ID_i
, s.ShortName_v
, sp.Path_v
, ( SELECT TOP 1 1 -- has also user access on subsites ?
FROM SitePath_T usp WITH ( INDEX ( [IDX_SitePath_Path+SiteID] ) )
-- same performance when using WITH ( INDEX ( [IDX_SitePath_Path_INC<SiteID>] ) )
, UserSiteRight_t usr WITH ( INDEX ( [IDX_UserSiteRight_UserID+SiteID] ) )
WHERE usr.SiteID_i = usp.SiteID_i
AND usp.Path_v LIKE sp.Path_v + '%_'
AND usr.UserID_i = 1)
FROM Site_T s
, SitePath_T sp WITH ( INDEX ( [IDX_SitePath_SiteID+Path] ) )
-- same performance when using WITH ( INDEX ( [IDX_SitePath_SiteID_INC<Path>] ) )
WHERE sp.SiteID_i = s.ID_i
AND s.ShortName_v LIKE '[a-y]%'
AND s.ParentID_i = 1
AND EXISTS ( SELECT *
FROM SitePath_T usp WITH ( INDEX ( [IDX_SitePath_Path+SiteID] ) )
-- same performance when using WITH ( INDEX ( [IDX_SitePath_Path_INC<SiteID>] ) )
, UserSiteRight_t usr WITH ( INDEX ( [IDX_UserSiteRight_UserID+SiteID] ) )
WHERE usr.SiteID_i = usp.SiteID_i
AND usp.Path_v LIKE sp.Path_v + '%'
AND usr.UserID_i = 1 )
:
CPU Reads Writes Duration
50 11237 0 55
продолжительность упадет до 55 миллисекунд (с более чем 2 секунд) !!!!
И я доволен этим результатом!
План выполнения:
|--Compute Scalar(DEFINE:([Expr1014]=[Expr1014]))
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([sp].[Path_v]))
|--Nested Loops(Left Semi Join, OUTER REFERENCES:([Expr1016], [Expr1017], [Expr1018], [Expr1019]))
| |--Merge Join(Inner Join, MERGE:([sp].[SiteID_i])=([s].[ID_i]), RESIDUAL:([dbo].[SitePath_T].[SiteID_i] as [sp].[SiteID_i]=[dbo].[Site_T].[ID_i] as [s].[ID_i]))
| | |--Compute Scalar(DEFINE:([Expr1016]=[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1017]=LikeRangeStart([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1018]=LikeRangeEnd([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1019]=LikeRangeInfo([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
| | | |--Index Scan(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_SiteID_<Path>] AS [sp]), ORDERED FORWARD)
| | |--Sort(ORDER BY:([s].[ID_i] ASC))
| | |--Clustered Index Seek(OBJECT:([dbo].[Site_T].[IDXC_Site_ParentID+ShortName+ID] AS [s]), SEEK:([s].[ParentID_i]=(1) AND [s].[ShortName_v] >= '9þþþþþ' AND [s].[ShortName_v] < 'Z'), WHERE:([dbo].[Site_T].[ShortName_v] as [s].[ShortName_v] like '[a-y]%') ORDERED FORWARD)
| |--Nested Loops(Inner Join, OUTER REFERENCES:([usp].[SiteID_i], [Expr1023]) WITH UNORDERED PREFETCH)
| |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1017], [Expr1018], [Expr1019]))
| | |--Compute Scalar(DEFINE:([Expr1017]=[Expr1017], [Expr1018]=[Expr1018], [Expr1019]=[Expr1019]))
| | | |--Constant Scan
| | |--Index Seek(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_Path+SiteID] AS [usp]), SEEK:([usp].[Path_v] > [Expr1017] AND [usp].[Path_v] < [Expr1018]), WHERE:([dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1016]) ORDERED FORWARD)
| |--Index Seek(OBJECT:([dbo].[UserSiteRight_T].[IDX_UserSiteRight_UserID+SiteID] AS [usr]), SEEK:([usr].[UserID_i]=(1) AND [usr].[SiteID_i]=[dbo].[SitePath_T].[SiteID_i] as [usp].[SiteID_i]) ORDERED FORWARD)
|--Compute Scalar(DEFINE:([Expr1014]=(1)))
|--Top(TOP EXPRESSION:((1)))
|--Nested Loops(Inner Join, OUTER REFERENCES:([usp].[SiteID_i], [Expr1027]) WITH UNORDERED PREFETCH)
|--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1024], [Expr1025], [Expr1026]))
| |--Compute Scalar(DEFINE:([Expr1024]=LikeRangeStart([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_'), [Expr1025]=LikeRangeEnd([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_'), [Expr1026]=LikeRangeInfo([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_')))
| | |--Constant Scan
| |--Index Seek(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_Path+SiteID] AS [usp]), SEEK:([usp].[Path_v] > [Expr1024] AND [usp].[Path_v] < [Expr1025]), WHERE:([dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_') ORDERED FORWARD)
|--Index Seek(OBJECT:([dbo].[UserSiteRight_T].[IDX_UserSiteRight_UserID+SiteID] AS [usr]), SEEK:([usr].[UserID_i]=(1) AND [usr].[SiteID_i]=[dbo].[SitePath_T].[SiteID_i] as [usp].[SiteID_i]) ORDERED FORWARD)
Следующий шаг - запустить его для разных пользователей,таким образом, я объявлю UserID_i как переменную:
DECLARE @UserID_i INT
SELECT @UserID_i = 1
НО СЕЙЧАС НИЖЕ ЗАПРОС СТАНОВИТСЯ БЕЗУМНЫМ МЕДЛЕННЫМ !!!
SELECT s.ID_i
, s.ShortName_v
, sp.Path_v
, ( SELECT TOP 1 1 -- has also user access on subsites ?
FROM SitePath_T usp WITH ( INDEX ( [IDX_SitePath_Path+SiteID] ) )
, UserSiteRight_t usr WITH ( INDEX ( [IDX_UserSiteRight_UserID+SiteID] ) )
WHERE usr.SiteID_i = usp.SiteID_i
AND usp.Path_v LIKE sp.Path_v + '%_'
AND usr.UserID_i = @UserID_i)
FROM Site_T s
, SitePath_T sp WITH ( INDEX ( [IDX_SitePath_SiteID+Path] ) )
WHERE sp.SiteID_i = s.ID_i
AND s.ShortName_v LIKE '[a-y]%'
AND s.ParentID_i = 1
AND EXISTS ( SELECT *
FROM SitePath_T usp WITH ( INDEX ( [IDX_SitePath_Path+SiteID] ) )
, UserSiteRight_t usr WITH ( INDEX ( [IDX_UserSiteRight_UserID+SiteID] ) )
WHERE usr.SiteID_i = usp.SiteID_i
AND usp.Path_v LIKE sp.Path_v + '%'
AND usr.UserID_i = @UserID_i )
Продолжительность теперь за 7 секунд !!!
CPU Reads Writes Duration
7421 149984 35 7625
И план выполнения:
|--Compute Scalar(DEFINE:([Expr1014]=[Expr1014]))
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([sp].[Path_v]))
|--Nested Loops(Left Semi Join, WHERE:([dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1016]))
| |--Merge Join(Inner Join, MERGE:([sp].[SiteID_i])=([s].[ID_i]), RESIDUAL:([dbo].[SitePath_T].[SiteID_i] as [sp].[SiteID_i]=[dbo].[Site_T].[ID_i] as [s].[ID_i]))
| | |--Compute Scalar(DEFINE:([Expr1016]=[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1017]=LikeRangeStart([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1018]=LikeRangeEnd([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1019]=LikeRangeInfo([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%')))
| | | |--Index Scan(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_SiteID+Path] AS [sp]), ORDERED FORWARD)
| | |--Sort(ORDER BY:([s].[ID_i] ASC))
| | |--Clustered Index Seek(OBJECT:([dbo].[Site_T].[IDXC_Site_ParentID+ShortName+ID] AS [s]), SEEK:([s].[ParentID_i]=(1) AND [s].[ShortName_v] >= '9þþþþþ' AND [s].[ShortName_v] < 'Z'), WHERE:([dbo].[Site_T].[ShortName_v] as [s].[ShortName_v] like '[a-y]%') ORDERED FORWARD)
| |--Table Spool
| |--Hash Match(Inner Join, HASH:([usr].[SiteID_i])=([usp].[SiteID_i]))
| |--Index Seek(OBJECT:([dbo].[UserSiteRight_T].[IDX_UserSiteRight_UserID+SiteID] AS [usr]), SEEK:([usr].[UserID_i]=[@UserID_i]) ORDERED FORWARD)
| |--Index Scan(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_Path+SiteID] AS [usp]))
|--Compute Scalar(DEFINE:([Expr1014]=(1)))
|--Top(TOP EXPRESSION:((1)))
|--Nested Loops(Inner Join, WHERE:([dbo].[UserSiteRight_T].[SiteID_i] as [usr].[SiteID_i]=[dbo].[SitePath_T].[SiteID_i] as [usp].[SiteID_i]))
|--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1020], [Expr1021], [Expr1022]))
| |--Compute Scalar(DEFINE:([Expr1020]=LikeRangeStart([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_'), [Expr1021]=LikeRangeEnd([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_'), [Expr1022]=LikeRangeInfo([dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_')))
| | |--Constant Scan
| |--Index Seek(OBJECT:([dbo].[SitePath_T].[IDX_SitePath_Path+SiteID] AS [usp]), SEEK:([usp].[Path_v] > [Expr1020] AND [usp].[Path_v] < [Expr1021]), WHERE:([dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%_') ORDERED FORWARD)
|--Table Spool
|--Index Seek(OBJECT:([dbo].[UserSiteRight_T].[IDX_UserSiteRight_UserID+SiteID] AS [usr]), SEEK:([usr].[UserID_i]=[@UserID_i]) ORDERED FORWARD)
План выполнения полностью меняется, когда я использую переменную вместо жесткого кодированиязначение UserID_i!
Почему оптимизатор запросов ведет себя так?
Как заставить план выполнения совпадать со вторым быстрым запросом?
Спасибо.
ОБНОВЛЕНИЕ 1
Удалено (неактуально)
ОБНОВЛЕНИЕ 2
Кажется, я не единственный, у кого есть эта проблема.
Пожалуйста, проверьте следующие разделы:
Почему оптимизатор SqlServer так путается с параметрами?
Известная проблема ?: Хранимая процедура SQL Server 2005 не завершаетсяс параметром
ОБНОВЛЕНИЕ 3
Отличная статья от Команда оптимизации SQL Server , охватывающая анализ параметров: I Smellпараметр!