2016-10-03 23 views
1

慢我有這個簡單的定義表:SQL查詢單表值參數上大的輸入

CREATE TABLE Related 
(
    RelatedUser NVARCHAR(100) NOT NULL FOREIGN KEY REFERENCES User(Id), 
    RelatedStory BIGINT NOT NULL FOREIGN KEY REFERENCES Story(Id), 
    CreationTime DateTime NOT NULL, 

    PRIMARY KEY(RelatedUser, RelatedStory) 
); 

這些指數:

CREATE INDEX i_relateduserid 
    ON Related (RelatedUserId) INCLUDE (RelatedStory, CreationTime) 

CREATE INDEX i_relatedstory 
    ON Related(RelatedStory) INCLUDE (RelatedUser, CreationTime) 

,我需要查詢表所有與一系列UserIds相關的故事,按創建時間排序,然後僅提取X並跳過Y.

我有這個存儲過程:

CREATE PROCEDURE GetStories 
    @offset INT, 
    @limit INT, 
    @input UserIdInput READONLY 
AS 
BEGIN 
    SELECT RelatedStory 
    FROM Related 
    WHERE EXISTS (SELECT 1 FROM @input WHERE UID = RelatedUser) 
    GROUP BY RelatedStory, CreationTime 
    ORDER BY CreationTime DESC 
    OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY; 
END; 

使用此用戶自定義表類型:

CREATE TYPE UserIdInput AS TABLE 
(
    UID nvarchar(100) PRIMARY KEY CLUSTERED 
) 

表有13萬行,並使用一些用戶ID作爲輸入時得到我的好成績,但很糟糕(30秒以上)產生時提供數百或數千個用戶ID作爲輸入。主要問題似乎是它使用了63%的排序工作。

我錯過了什麼索引?這似乎是在單個表上的一個非常簡單的查詢。

+1

你有沒有考慮改變你的WHERE EXISTS爲連接。聯賽往往表現更好,尤其是對於大型聯賽。 –

+0

據我已經能夠給谷歌,EXISTS如果你檢查存在,就像這裏的答案是首選表明: http://stackoverflow.com/questions/7082449/exists-vs-join-and-使用存在條款 這不是這種情況嗎? – bech

+0

是的,但正如你所提到的,當你的集合擴展時,性能會受到影響。如果JOIN鍵沒有被索引,那麼使用EXISTS可能會得到更好的結果。我的想法是這樣的......「就像雞湯感冒一樣......不會傷到試試。」 –

回答

2

所以,我終於找到了解決辦法。

雖然@srutzky了標準化,通過改變NVARCHAR用戶ID爲一個整數,以儘量減少成本比較表的好的建議,這不是什麼解決我的問題。爲了增加理論性能,我肯定會在某些時候做到這一點,但是我馬上實施它後,性能幾乎沒有什麼變化。

@Paparazzi建議我增加了(RelatedStory,創建時間)的指數,而沒有做什麼,我需要兩種。 的原因是,我還需要同時指數RelatedUser因爲這是查詢不言而喻的方式,因此需要IT部門和訂單雙方CREATIONTIME和RelatedStory,所有三個。所以:

CREATE INDEX i_idandtime ON Related (RelatedUser, CreationTime DESC, RelatedStory) 

解決我的問題,帶來了超過15秒,我不能接受的查詢時間降低到大都1秒或者幾秒querytimes的。

我想給我的啓示是@srutzky指出:

記住,「包括」列不用於排序或比較,只用於覆蓋 。

這讓我意識到我需要索引中的所有groupby和orderby列。

因此,雖然我不能標記,或者上面的海報張貼的答案,我想真誠地感謝他們的時間。

2

你對RelatedUser/UID有什麼類型的值?爲什麼,確切地說,你使用NVARCHAR(100)嗎?對於PK/FK領域來說,NVARCHAR通常是一個可怕的選擇。即使該值是一個簡單的字母數字代碼(例如ABTY1245),也有更好的方法來處理這個問題。 NVARCHAR(對於這個特定問題,甚至VARCHAR)的一個主要問題是,除非你使用二進制排序規則(例如Latin1_General_100_BIN2),否則每種排序和比較操作都將應用全部的語言規則,這可以很好地在使用字符串時值得,但在使用代碼時不必要地昂貴,尤其是,當使用通常默認的不區分大小寫排序規則時。

一些「更好」(但不理想)解決方案將是:

  1. 如果你真的需要Unicode字符,至少指定二進制排序,如Latin1_General_100_BIN2
  2. 如果你不需要Unicode字符,那麼切換到使用VARCHAR,這將佔用一半的空間和排序/比較更快。此外,仍然使用二進制排序。

你最好的選擇是:

  1. 添加INT IDENTITY列到User表,命名爲UseID
  2. UserID集羣化PK
  3. 添加INT(無IDENTITY)列到Related表格名爲UserID
  4. 從添加FK回到UserUserID
  5. Related表中刪除RelatedUser列。
  6. 添加非羣集,唯一索引到User表上UserCode柱(這使得它的「備用鍵」)
  7. 刪除並重新創建UserIdInput用戶定義的表型有一個INT數據類型而不是NVARCHAR(100)
  8. 如果可能的話,改變User表的ID柱爲具有二進制排序規則(即Latin1_General_100_BIN2
  9. 如果可能,在表User重命名當前Id柱是UserCode或S就像那樣。
  10. 如果用戶輸入「代碼」值(意思是:不能保證他們將始終使用全部大寫或全部小寫),那麼最好在User表上添加一個AFTER INSERT, UPDATE觸發器,以確保這些值是總是全部大寫(或全部小寫)。這也意味着在搜索「代碼」時需要確保所有傳入的查詢使用相同的全部大寫或全部小寫值。但是這一點額外的工作將會得到回報。

整個系統會感謝你,並通過提高效率向你表示感謝:-)。

要考慮的另一件事: TVP是一個表變量,並且默認情況下,查詢優化器中只有一行出現。因此,在TVP中添加幾千個條目會降低速度,這是有道理的。在這種情況下幫助加速TVP的一個技巧是將OPTION (RECOMPILE)添加到查詢中。使用表變量重新編譯查詢將導致查詢優化器查看真正的行數。如果沒有任何幫助,另一個技巧就是將TVP表變量轉儲到本地臨時表(即#TempUserIDs)中,因爲這些表保持統計數據並且在其中有少於幾行的情況下優化得更好。

從這個答案OP的評論:

[UID]是在我們的系統中使用的ID(XXX-Y-ZZZZZZZZZZ ...),XXX是字母,Y是一個數和Z被編號

是的,我想它是某種ID或代碼,所以這並不改變我的建議。 NVARCHAR,尤其是如果使用非二進制,不區分大小寫的排序規則,可能是此值的最差數據類型之一。此ID應位於User表中名爲UserCode的列中,其中定義了非聚簇索引。這使得它成爲一個「備用」鍵,並且一次從應用層快速輕鬆地查找該行的「內部」整數值,INT IDENTITY列作爲實際的UserID(通常最好將ID列命名爲{table_name} ID以確保一致性/易於維護)。 UserID INT值是所有相關表格中的FK值。一個INT列將加入很多NVARCHAR更快。即使使用二進制排序規則,該列雖然比當前的實現速度快,但仍至少爲32個字節(基於給出的示例XXX-Y-ZZZZZZZZZZ),而INT將僅爲4個字節。是的,那些額外的28字節有所作爲,尤其是當你有1300萬行。請記住,這不僅僅是這些值佔用的磁盤空間,它還是內存,因爲爲查詢讀取的所有數據都通過緩衝池(即物理內存!)。

但是,在這種情況下,我們沒有在任何地方跟隨外鍵,而是直接查詢它們。如果它們被編入索引,它是否重要?

是的,它仍然很重要,因爲你基本上在做與JOIN相同的操作:你將主表中的每個值與表變量/ TVP中的值進行比較。與二進制比較相比,這仍然是一個非二進制,不區分大小寫(我認爲)的比較。每個字母都需要針對大小寫進行評估,而不是針對所有其他可能與每個字母相同的Unicode代碼點(並且這些代碼點的數量超過您認爲的匹配A - Z!)。索引會比沒有索引更快,但與比較沒有其他表示的簡單值無關。

+0

PLUS1 - 甚至沒有注意到nvarchar的 –

+0

當我看到你對NVARCHAR點爲外鍵,這是在我們的系統中使用的ID(XXX-Y-ZZZZZZZZZZ ...),XXX是字母,Y是一個數字, Z是數字。 但是,在這種情況下,我們沒有在任何地方跟隨外鍵,而是直接查詢它們。如果它們被編入索引,它是否重要? – bech

+0

@srutzky感謝您的輸入(和更新說明)。我目前正在試圖在數據庫的副本上查看差異。從直覺上來說,你認爲我們可以獲得相同的結果,比如我們可以比較100個輸入行,這個變化是什麼? (我知道,有點猜測這些東西,但仍然..) – bech

1

主要的問題似乎是它使用排序的63%的努力。

ORDER BY CreationTime DESC 

我建議和指數CREATIONTIME

或者嘗試一個指數RelatedStory,CREATIONTIME

+0

嗨,是的,我試過了,但它似乎並不想使用它,即使在我使用索引字段添加了where子句並重新編譯了執行計劃之後,還有什麼想法嗎? – bech

+0

你試過了這兩個嗎?我想用一個連接來存在。 – Paparazzi

+0

我實際上只在CreationTime字段嘗試了索引。將嘗試RelatedStory和CreationTime。 – bech