2010-01-03 48 views
6

儘管實現的樹結構在SQL 2005服務器數據庫,查詢響應時間太長使用時(以下查詢談話超過5秒) LIKE子句加上EXISTS子句。在[UserSiteRight_T]優化數據庫模式/索引使用LIKE時和EXISTS子句

CREATE TABLE [dbo].[UserSiteRight_T](
     [UserID_i] [int] NOT NULL 
    , [SiteID_i] [int] NOT NULL 
    , CONSTRAINT [PKC_UserSiteRight_UserIDSiteID] PRIMARY KEY CLUSTERED ([UserID_i] ASC, [SiteID_i] ASC) 
    , CONSTRAINT [FK_UserSiteRight_UserID] FOREIGN KEY([UserID_i]) REFERENCES [dbo].[User_T] ([ID_i]) 
    , CONSTRAINT [FK_UserSiteRight_SiteID] FOREIGN KEY([SiteID_i]) REFERENCES [dbo].[Site_T] ([ID_i]) 
) 

的行數(權利)爲UserID_i = 2484[SitePath_T][UserSiteRight_T] -

慢查詢涉及兩個表表是相當小:545
UserID_i = 2484被隨機選擇的)

此外,數據庫是相對較小 - 在[SitePath_T]表只有23000行:)

CREATE TABLE [dbo].[SitePath_T] (
    [SiteID_i] INT NOT NULL, 
    [Path_v] VARCHAR(255) NOT NULL, 
    CONSTRAINT [PK_SitePath_PathSiteID] PRIMARY KEY CLUSTERED ([Path_v] ASC, [SiteID_i] ASC), 
    CONSTRAINT [AK_SitePath_Path] UNIQUE NONCLUSTERED ([Path_v] ASC), 
    CONSTRAINT [FK_SitePath_SiteID] FOREIGN KEY([SiteID_i]) REFERENCES [Site_T] ([ID_i]) 


DB Schema http://i46.tinypic.com/258aqfm.png

我試圖讓只有SiteIDs其中有子網站accessib樂受到了一定用戶名(由[UserSiteRight_T]表所示)爲:

SELECT sp.SiteID_i 
    FROM SitePath_t sp 
WHERE EXISTS (SELECT * 
       FROM [dbo].[SitePath_T] usp 
       , [dbo].[UserSiteRight_T] uusr 
      WHERE uusr.SiteID_i = usp.SiteID_i 
       AND uusr.UserID_i = 2484 
       AND usp.Path_v LIKE sp.Path_v+'%') 

下面你可以找到其中僅需要列sp.SiteID_i的結果的一部分/返回 - 我也加入相關的相應Path_vUserSiteRight_T。SiteID_i其中userid = 2484和相應SitePath_TSiteID_iPath_v匹配LIKE條件:

|--Nested Loops(Left Semi Join, WHERE:([MyTestDB].[dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1007])) 
     |--Compute Scalar(DEFINE:([Expr1007]=[MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1008]=LikeRangeStart([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1009]=LikeRangeEnd([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1010]=LikeRangeInfo([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'))) 
     | |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [sp])) 
     |--Table Spool 
      |--Hash Match(Inner Join, HASH:([uusr].[SiteID_i])=([usp].[SiteID_i])) 
       |--Clustered Index Seek(OBJECT:([MyTestDB].[dbo].[UserSiteRight_T].[PKC_UserSiteRight_UserIDSiteID] AS [uusr]), SEEK:([uusr].[UserID_i]=(2484)) ORDERED FORWARD) 
       |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [usp])) 

sp.SiteID_i sp.Path_v  [UserSiteRight_T].SiteID_i  usp.SiteID_i  usp.Path_v 
1   '1.'      NULL    10054    '1.10054.' 
10054  '1.10054.'     10054    10054    '1.10054.' 
10275  '1.10275.'     10275    10275    '1.10275.' 
1533  '1.1533.'     NULL    2697    '1.1533.2689.2693.2697.' 
2689  '1.1533.2689.'    NULL    2697    '1.1533.2689.2693.2697.' 
2693  '1.1533.2689.2693.'   NULL    2697    '1.1533.2689.2693.2697.' 
2697  '1.1533.2689.2693.2697.' 2697    2697    '1.1533.2689.2693.2697.' 
1580  '1.1580.'     NULL    1581    '1.1580.1581.' 
1581  '1.1580.1581.'    1581    1581    '1.1580.1581.' 
1585  '1.1580.1581.1585.'   1585    1585    '1.1580.1581.1585.' 
222   '1.222.'     222     222     '1.222.' 
223   '1.222.223.'    223     223     '1.222.223.' 
224   '1.222.223.224.'   224     224     '1.222.223.224.' 
3103  '1.3103.'     NULL    3537    '1.3103.3529.3533.3537.' 
3529  '1.3103.3529.'    NULL    3537    '1.3103.3529.3533.3537.' 
3533  '1.3103.3529.3533.'   NULL    3537    '1.3103.3529.3533.3537.' 
3537  '1.3103.3529.3533.3537.' 3537    3537    '1.3103.3529.3533.3537.' 

用於上述查詢執行計劃

而重寫的查詢:

SELECT DISTINCT 
     sp.SiteID_i 
    FROM [dbo].[SitePath_t] sp 
    , [dbo].[SitePath_T] usp 
    , [dbo].[UserSiteRight_T] uusr 
WHERE (uusr.SiteID_i = usp.SiteID_i 
    AND uusr.UserID_i = 2484 
    AND usp.Path_v LIKE sp.Path_v+'%') 
ORDER BY SiteID_i ASC 

執行計劃:

|--Hash Match(Aggregate, HASH:([sp].[SiteID_i])) 
     |--Nested Loops(Inner Join, WHERE:([MyTestDB].[dbo].[SitePath_T].[Path_v] as [usp].[Path_v] like [Expr1006])) 
      |--Hash Match(Inner Join, HASH:([uusr].[SiteID_i])=([usp].[SiteID_i])) 
      | |--Clustered Index Seek(OBJECT:([MyTestDB].[dbo].[UserSiteRight_T].[PKC_UserSiteRight_UserIDSiteID] AS [uusr]), SEEK:([uusr].[UserID_i]=(2484)) ORDERED FORWARD) 
      | |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [usp])) 
      |--Table Spool 
       |--Compute Scalar(DEFINE:([Expr1006]=[MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%', [Expr1007]=LikeRangeStart([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1008]=LikeRangeEnd([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'), [Expr1009]=LikeRangeInfo([MyTestDB].[dbo].[SitePath_T].[Path_v] as [sp].[Path_v]+'%'))) 
         |--Index Scan(OBJECT:([MyTestDB].[dbo].[SitePath_T].[AK_SitePath_Path] AS [sp])) 

各項指標都到位 - 數據庫引擎優化顧問不建議新的架構修改 - 但兩者查詢更多有5秒鐘後將返回正確的結果 - 和,因爲它是Ajax請求的響應 - 在更新導航樹時感覺(並且)非常緩慢

有關優化/修改數據庫模式/索引/查詢以獲得更快響應的任何建議?

謝謝

+0

我不知道如果遞歸CTE的網站效果會更好,但我無法從你的數據告訴你怎麼知道哪根'SITEPATH_T'值。 – 2010-01-03 08:17:38

回答

3

基於:

SELECT sp.SiteID_i 
    FROM SitePath_t sp 
WHERE EXISTS (SELECT * 
       FROM [dbo].[SitePath_T] usp 
       , [dbo].[UserSiteRight_T] uusr 
      WHERE uusr.SiteID_i = usp.SiteID_i 
       AND uusr.UserID_i = 2484 
       AND usp.Path_v LIKE sp.Path_v+'%') 

(這是蠻好的基礎上的事實,你正在做一個半加入)。

它首先聚焦(正確)uusr表,找到該用戶的記錄。它已經在做CIX Seek,這很好。從那裏,它根據SiteID_i字段在usp中找到相應的記錄。

所以接下來考慮一下這樣一個事實,即它想要通過SiteID_i查找網站,以及希望獲得什麼樣的連接。

合併加入如何?這很好,但要求數據在兩邊進行排序。這很好,如果索引是正確的順序...

...之後,你想要找到基於路徑的東西。因此,如何:

CREATE INDEX ix_UUSR on [dbo].[UserSiteRight_T] (UserID_i, SiteID_i); 
CREATE INDEX ix_usp on [dbo].[SitePath_T] (SiteID_i) INCLUDE (Path_v); 

,然後SitePath_T另一個索引找到你想要的的siteId:

CREATE INDEX ix_sp on [dbo].[SitePath_T] (Path_v) INCLUDE (SiteID_i); 

有可能是在這最後一個使用了嵌套循環,但那希望不是太糟糕了。這將影響你的系統的東西將是前兩個索引,它應該讓你看到你的EXISTS子句中兩個表之間的合併連接。

+0

因此......考慮一下這樣一個事實,即您希望來自uusp的行按SiteID_i順序排列,並且來自usp的行將按照相同的順序排列,從而極大地幫助連接。然後你有自己的路徑,那裏可能還有一些痛苦,但可能不會太多。 – 2010-01-07 05:10:21

+0

我也打算說,「在SitePath_T(Path_v)上拋出一個覆蓋索引」,但他已經有了一個w /非集羣唯一約束。它仍然會進行索引掃描!所以問題仍然存在,我們如何儘可能早地減少執行計劃中的數據量? – 2010-01-08 18:04:18

+0

您好Rob, 我很好奇知道然後影響,而不是SitePath_t上的兩個索引,我們只有一個...... CREATE INDEX ix_sp on [dbo]。[SitePath_T](Path_v,SiteID_i); 考慮到Path_v可能有更多的過濾,所以我們它會先過濾然後進行合併連接。如果我錯了,請糾正我。在我看來,如果我們可以通過使用複合索引來節省傳統查找,那麼我們應該這樣做。 – 2010-01-11 06:49:07

0

我會嘗試在您的UserSiteRight_T表的外鍵添加一個索引 - 他們沒有索引,並在這些領域的指標應加快查找:

CREATE NONCLUSTERED INDEX IX01_UserSiteRight 
    ON UserSiteRight_T(UserID_i) 

CREATE NONCLUSTERED INDEX IX02_UserSiteRight 
    ON UserSiteRight_T(SiteID_i) 

並在您SitePath_T表,以及:

CREATE NONCLUSTERED INDEX IX01_SitePath 
    ON dbo.SitePath_T(SiteID_i) 

嘗試把這些到位,然後再次運行查詢,並比較運行時間和執行計劃 - 做喲你看到任何改善?

這是一個普遍的誤解,但SQL Server 自動把索引外鍵的列(如SiteID_iSitePath_T),即使一般的共識是,一個外鍵是有用的和潛在的加快雙方執法的引用完整性,以及連接這些外鍵。

0

SitePath_T上的自加入以查找父母正在殺死您。也許你應該爲ParentSiteID_i添加一個列並使用正常的遞歸CTE?

然後,它變爲:

WITH Recurse_CTE AS (
    SELECT 
    us.SiteID_i 
    , us.ParentSiteID_i 
    , 0 AS RecurseDepth_i 
    FROM dbo.SitePath_T us 
    JOIN dbo.UserSiteRight_T uusr ON us.SiteID_i = uusr.SiteID_i 
    WHERE uusr.UserID_i = 2484 
    UNION ALL 
    SELECT 
    us.SiteID_i 
    , us.ParentSiteID_i 
    , rcs.RecurseDepth_i+1 AS RecurseDepth_i 
    FROM dbo.SitePath_T us 
    JOIN Recurse_CTE rcs ON us.SiteID_i = rcs.ParentSiteID_i 
) 
SELECT * FROM Recurse_CTE 

上SitePath_T(ParentSiteID_i)拋出的索引和性能應活潑的。