web-dev-qa-db-ja.com

述語なしで結合を見つけるのを助ける

(swasheckによる関連する質問 と同様に、これまでパフォーマンスの問題に悩まされてきたクエリがあります。 SSMSでクエリプランを調べていたところ、Nested Loops (Inner Join)に警告が表示されていました。

結合述語なし

いくつかの急ぎの調査に基づいて(自信を刺激する Scary DBA 、および Brent Ozar )この警告は、クエリに非表示のデカルト積があることを示しているようです。クエリを数回確認しましたが、クロス結合が表示されません。これがクエリです:

DECLARE @UserId INT; -- Stored procedure input
DECLARE @Now DATETIME2(7) = SYSUTCDATETIME();
;WITH AggregateStepData_CTE AS -- Considering converting this CTE into an indexed view
(
    SELECT 
        [UA].[UserId] -- FK to the UserId
        , [UA].[DeviceId] -- FK to the Push device's DeviceId (int)
        , SUM(ISNULL([UA].[LatestSteps], 0)) AS [Steps]
    FROM [User].[UserStatus] [UA]
        INNER JOIN [User].[CurrentConnections] [M] ON 
           [M].[Monitored] = [UA].[UserId] AND [M].[Monitor] = @UserId
    WHERE
        [M].[ShareSteps] = 1 -- Only use step data if we are allowed to see.
        AND
        CAST([UA].[ReportedLocalTime] AS DATE) = 
          CAST(DATEADD(MINUTE, DATEPART(TZOFFSET, [UA].[ReportedLocalTime]), @Now) AS DATE)
          -- Aggregate the steps for today based on the device's time zone.         
    GROUP BY
        [UA].[UserId]
        , [UA].[DeviceId]
)
SELECT
    [UA].[UserId] -- FK to the UserId
    , [UA].[ReportedLocalTime]
    , CASE WHEN [M].[ShareLocation] = 1 THEN [UA].[Latitude] ELSE NULL END AS [Latitude]
    , CASE WHEN [M].[ShareLocation] = 1 THEN [UA].[Longitude] ELSE NULL END AS [Longitude]
    , CASE WHEN [M].[ShareLocation] = 1 THEN [UA].[LocationAccuracy] ELSE NULL END 
         AS [LocationAccuracy]
    , CASE WHEN [M].[ShareSteps] = 1 THEN ISNULL([SD].[Steps], 0) ELSE NULL END AS [Steps]
    , CASE WHEN [M].[ShareBattery] = 1 THEN [UA].[BatteryPercentage] ELSE NULL END 
         AS [BatteryPercentage]
    , CASE WHEN [M].[ShareBattery] = 1 THEN [UA].[IsDraining] ELSE NULL END 
         AS [IsDraining]
    , [PD].[DeviceName]
FROM [User].[LatestUserStatus] [UA]
    INNER JOIN [User].[CurrentConnections] [M] WITH (NOEXPAND) ON 
      [M].[Monitored] = [UA].[UserId] AND [M].[Monitor] = @UserId
    INNER JOIN [User].[PushDevice] [PD] ON [PD].[PushDeviceId] = [UA].[DeviceId]
    LEFT JOIN [AggregateStepData_CTE] [SD] ON 
      [M].[Monitored] = [SD].[UserId] AND [SD].[DeviceId] = [UA].[DeviceId]
ORDER BY        
    [UA].[UserId]
    , [UA].[ReportedLocalTime] DESC

クエリプランは次の場所にあります https://Gist.github.com/anonymous/d6ac970b45eb75a88b99

または、すべての推定サブツリーコストが0.05と非常に低いため、 swasheckの質問 の結論のように、警告を恐れないでください。


この回答 も関連しているようです。これはおそらく、これがSQL Serverが私の代わりに行っている最適化であることを意味します。結合を削除できることがわかっているためです。


このブログ投稿 は、ネストされたループの述語の問題は、結合列のUDFが原因ではないことを示唆しています。このクエリではUDFを参照していません。


CurrentConnectionsビューの定義は次のとおりです。

CREATE VIEW [User].[CurrentConnections]
WITH SCHEMABINDING
AS 
    SELECT 
        [M].[Monitor] -- FK to the UserId
        , [M].[Monitored] -- FK to the UserId
        , [M].[MonitoringId]
        , [M].[ShareBattery]
        , [M].[ShareLocation]
        , [M].[ShareSteps]
        , [M].[ShowInSocialFeed]
        , [M].[Created] AS [RelationshipCreated]
        , [AT].[AlertThresholdId]
        , [AT].[EffectiveStartTime]
        , [AT].[EndTime]
        , [AT].[OverNightRedThreshold]
        , [AT].[SendBatteryAlerts]
        , [AT].[SendGeneralAlerts]
        , [AT].[StartTime]
        , [AT].[ThresholdInMinutes]
        , [AT].[Threshold]
        , [U_Monitored].[ProfilePhoto] AS [Monitored_ProfilePhoto]
        , [U_Monitored].[DisplayName] AS [Monitored_DisplayName]
        , [U_Monitored].[Fullname] AS [Monitored_FullName]
        , [U_Monitored].[PhoneNumber] AS [Monitored_PhoneNumber]
    FROM [User].[Monitoring] [M]
        INNER JOIN [User].[AlertThreshold] [AT] ON [AT].[MonitoringId] = [M].[MonitoringId]
        INNER JOIN [User].[User] [U_Monitored] ON [U_Monitored].[UserId] = [M].[Monitored]
    WHERE
        [M].[ArchivedOn] IS NULL
        AND
        [AT].[ArchivedOn] IS NULL

GO

CREATE UNIQUE CLUSTERED INDEX [IDX_User_CurrentConnections_Monitor_Monitored] ON 
   [User].[CurrentConnections]([Monitor], [Monitored]);
GO
CREATE NONCLUSTERED INDEX [IDX_User_CurrentConnections_Monitored] ON 
  [User].[CurrentConnections]([Monitored]) 
  INCLUDE ([Monitor], [ShareBattery], [ShareLocation], [ShareSteps]);
7
Erik

私のCTEでは、WITH (NOEXPAND)クエリヒントがありませんでした。このクエリを追加すると、述語のない結合がクエリプランから消えました。

;WITH AggregateStepData_CTE AS
(
    SELECT
        [UA].[UserId]
        , [UA].[DeviceId]
        , SUM(ISNULL([UA].[LatestSteps], 0)) AS [Steps]
    FROM [User].[UserStatus] [UA]
        INNER JOIN [User].[CurrentConnections] [M] WITH (NOEXPAND) -- Added query hint here
          ON [M].[Monitored] = [UA].[UserId] AND [M].[Monitor] = @UserId
    WHERE
        [M].[ShareSteps] = 1 -- Only use step data if we are allowed to see.
        AND
        CAST([UA].[ReportedLocalTime] AS DATE) = 
          CAST(DATEADD(MINUTE, DATEPART(TZOFFSET, [UA].[ReportedLocalTime]), @Now) AS DATE) 
          -- Aggregate the steps for today based on the device's time zone.         
    GROUP BY
        [UA].[UserId]
        , [UA].[DeviceId]
)
4
Erik

私は同様の状況で、しばらくの間、エンジンが結合アルゴリズムのタイプが最も効率的であるかどうかを判断し、場合によっては記述されたコードよりも効率的に結合述語を削除することを読みました。展開しないことで、ビューに下がらず、最上位のクエリブロックを使用しないようにエンジンに指示しています。

結合述部を持っていることを証明するためにさらに提案が出された場合でも、決して思いとどまらないでください。私も信じて、私は何百回もチェックしました、そして私はそれらが正しかったです。笑。したがって、私の貢献。

結合をテーブル参照スタックの上下に移動して、エイリアス参照が正しい依存関係の順序になっていることを確認してください。つまり、4つの結合がある場合、私は成功し、参照の少ない、または選択性の低いテーブル結合を結合の最後の位置に移動し、その逆も同様に変更しました。

統計とインデックスが最新であることを確認します。 「自動作成/統計の自動作成」自動スケジュールとMSが行うことになっているアルゴリズムに自信がないので、時々、次のステートメントを発行して、それらが最近処理されることを確認します。

Sp_UpdateStats Sp_CreateStats 'indexonly'

強制ヒントは常にテストして文書化する必要がありますが、私は注意して、自分に何が起こったのか、私の調査結果が明らかになったことを通知したいと思いました。

次のリンクは、同じことに関連するスレッドです...プロパティダイアログで、「タイムアウトで十分な計画が見つかりました」、およびその他の警告を確認してください。

SQL Central類似スレッド

問題が私と同じように解決するアイデアがあるかどうか興味があります。

1
SnapJag