2009-10-05 101 views
15

我知道這個話題已被打死,但似乎互聯網上的許多文章往往尋找最優雅的方式,而不是如何解決它的最有效的方法。這是問題。我們正在構建一個應用程序,其中一個常見數據庫查詢將涉及基於用戶提供的ID列表的操作(SELECT和UPDATE)。有問題的表預計會有數十萬行,用戶提供的ID列表可能無界限,他們最有可能是數十或數百(我們可能會因性能原因而限制它)。在哪裏ID(1,2,3,4,5,...)效率最高?

如果我理解數據庫的工作原理是正確的,那麼最有效的方法是簡單地使用WHERE ID IN (1, 2, 3, 4, 5, ...)構造並動態構建查詢。問題的核心是ID的輸入列表將是真正任意的,因此無論數據庫有多聰明或者我們實現它的巧妙程度如何,我們總是有一個隨機的子集來開始,所以最終每個方法都必須無論如何,內部歸結爲類似WHERE ID IN (1, 2, 3, 4, 5, ...)

人們可以在網絡上找到很多方法。例如,需要聲明一個表變量,將ID列表作爲逗號分隔的字符串傳遞給存儲過程,在存儲過程中拆分它,將ID插入表變量並將主表連接到它上面,即類似這樣的:

-- 1. Temporary table for ID’s: 
DECLARE @IDS TABLE (ID int); 

-- 2. Split the given string of ID’s, and each ID to @IDS. 
-- Omitted for brevity. 

-- 3. Join the main table to @ID’s: 
SELECT MyTable.ID, MyTable.SomeColumn 
FROM MyTable INNER JOIN @IDS ON MyTable.ID = @IDS.ID; 

把與字符串操作的問題不談,我認爲實際上發生的事情在這種情況下,在第三步中,SQL Server的說:「謝謝你,這很好,但我只需要列表該ID「,並且它掃描表變量@IDS,然後n找到MyTable其中n是該ID的編號。我已經完成了一些基本的性能評估,並檢查了查詢計劃,而這似乎是發生了什麼事情。所以表變量,字符串連接和分割以及所有額外的INSERT都沒有用處。

我正確嗎?或者我錯過了什麼?有沒有真正的一些聰明和更有效的方法?基本上,我所說的是SQL Server必須做n索引搜索,無論是什麼和制定查詢作爲WHERE ID IN (1, 2, 3, 4, 5, ...)是要求它的最直接的方式。

+0

一個評論我會做,似乎在Web上的搜索中缺少的是,當您爲ID聲明臨時表時,應該將ID列聲明爲主鍵。然後SQL Server在其上放置一個索引。如果你不這樣做,它會進行表掃描,如果表中包含很多行,這會嚴重影響性能。 – 2009-10-05 20:23:18

+0

我不想讓我原來的問題更長,但我也測試了這個。似乎將索引放在ID列上並沒有什麼區別,因爲來自表變量(或臨時表)的所有服務器需求都是其中所有ID的列表。所以在這種情況下索引似乎沒有幫助(或者至少在ID的列表比主表短得多的情況下)。 – 2009-10-05 20:32:46

回答

11

那麼,這取決於真正發生了什麼。用戶如何選擇這些ID?

此外,它不僅僅是效率;還有擔心的安全性和正確性。用戶什麼時候以及如何告訴數據庫他們的ID選擇?你如何將它們合併到查詢中?

將所選ID放入一個單獨的表中,您可以參與反對(或使用WHERE EXISTS),這可能會更好。

我會告訴你,對於一個小的(用戶生成的)n,你不可能比IN (1,2,3..n)做得更好。但是你需要考慮如何生成該查詢。你打算使用動態SQL嗎?如果是這樣,你將如何保護它免受注射?服務器能夠緩存執行計劃嗎?

此外,使用額外的表格往往更容易。假設您正在爲電子商務網站構建購物車。不用擔心跟蹤購物車客戶端或會話,最好每次用戶進行選擇時更新ShoppingCart表。這也避免瞭如何安全地爲查詢設置參數值的問題,因爲您一次只進行一次更改。

不要忘了一句古老的格言(與道歉Benjamin Franklin):

性能值得既不

+0

我們正在構建一個用戶界面,它將非常簡單地選擇真正的任意集合。但我猜測,在很多情況下,實際設置實際上會有幾個間隔。所以我們可以建立quires:(1 <= ID AND ID <= 8)或(38 <= ID AND ID <= 89)...值得考慮這種方法。謝謝。 – 2009-10-05 20:36:11

6

要小心,他誰也交易的正確性;在許多數據庫中,IN(...)僅限於IN子句中固定數量的事物。例如,我認爲它在Oracle中是1000。這很大,但可能值得了解。

+0

感謝您指點我。我一定會調查它。 – 2009-10-05 20:38:30

+1

我剛剛在IN子句中測試了大約20000個項目,並且它工作正常。但是,當我測試了100000個項目時,SQL Server一直在考慮很長時間,然後說:「查詢處理器耗盡了內部資源,無法生成查詢計劃。這是一個罕見的事件,並且只能用於引用大量表或分區的極其複雜的查詢或查詢。請簡化查詢。「因此,本身沒有明確的限制,但查詢處理器扼殺了它。 – 2009-10-06 07:41:54

2

一臺VAR有問題:使用索引臨時表有統計數據的好處。

一臺VAR假設總是有一個排,而一個臨時表有統計數據的優化器可以使用。

解析CSV很簡單:看到正確的問題...

+0

解析一個CSV字符串:http://stackoverflow.com/questions/1456192/comparing-a-column-to-a-list-of-values-in-t-sql/1456404#1456404 – 2009-10-05 20:25:46

0

多年來,我用3的方法,但是當我開始使用OR/M這似乎是不必要的。

通過ID即使裝載的每一行是沒有那麼多像低效它看起來像。

0

如果用字符串處理問題的推杆之外,我認爲:

WHERE ID = 1或ID = 2或ID = 3 ...

是更有效的,不過我不會這樣做。

您可以比較兩種方法之間的性能。

+2

No. IN beats OR ,因爲數據庫可以針對該組進行優化(基本上把它當作一個小桌子來對待)。 – 2009-10-05 20:24:11

+0

爲什麼你認爲這比IN子句更有效? – 2009-10-05 23:16:53

+0

在20k行表中進行了一些測試'EXPLAIN SELECT * from product where id in(1,2,3,4,5);'與'EXPLAIN SELECT * from product where id = 1 or id = 2 or id = 3 or id = 4 or id = 5;'完全相同';'所以我想我錯了 – Juparave 2009-10-05 23:43:01

1

基本上,我會同意你的觀察; SQL Server的優化最終將挑選最好的計劃,分析值的列表,它通常會等同於同樣的計劃,無論您是否正在使用

WHERE IN 

WHERE EXISTS 

JOIN someholdingtable ON ... 

顯然,還有其他因素影響計劃選擇(如覆蓋指標等)。人們有多種方法將這個值列表傳遞給存儲過程的原因是,在SQL 2008之前,確實沒有簡單的方法傳遞多個值。你可以做一個參數列表(WHERE IN(@ param1,@ param2)...),或者你可以解析一個字符串(上面顯示的方法)。從SQL 2008開始,你也可以傳遞表變量,但總體結果是一樣的。

所以,不管你如何得到查詢的變量列表,然而,一旦你在那裏得到變量列表,還有其他因素可能對所述查詢的性能產生一些影響。

5

IN子句不保證INDEX SEEK。在使用SQL Mobile版本的Pocket內存很少的情況下,我遇到了這個問題。使用OR子句列表替換IN(list)將我的查詢提高了400%aprox。

另一種方法是使用臨時表來存儲ID並將其與目標表連接起來,但如果使用此操作,則永久/索引表可以幫助優化器。

0

要直接回答問題,無法將(動態)參數列表傳遞給SQL Server 2005過程。因此,大多數人在這些情況下所做的是通過逗號分隔的標識符列表,我也這樣做了。

由於SQL 2005年,雖然我更喜歡傳球和XML字符串,它也很容易地創建一個客戶端(C#,Python和另一個SQL SP),以及「原生」一起工作自2005年以來:

CREATE PROCEDURE myProc(@MyXmlAsSTR NVARCHAR(MAX)) AS BEGIN 
    DECLARE @x XML 
    SELECT @x = CONVERT(XML, @MyXmlAsSTR) 

然後你可以用XML直接加入您的基本查詢選擇爲(未測試):

SELECT  t.* 
FROM  myTable t 
INNER JOIN @x.nodes('/ROOT/ROW') AS R(x) 
     ON t.ID = x.value('@ID', 'INTEGER') 

路過<ROOT><ROW ID="1"/><ROW ID="2"/></ROOT>時。請記住XML是CaSe-SensiTiv。

+0

真的不知道這是一個非常有效的解決方案。有趣也許,但效率不高 – 2009-10-05 20:50:01

+0

我同意。我看起來效率不高,但看起來非常優雅。你不會因爲字符串操作而弄髒手(至少不是直接)。 – 2009-10-05 21:06:41

+0

你是什麼意思「看起來效率不高」?你有沒有測試過它? 鑑於自sql-2005以來對XML數據類型的本地支持(自sql2k,btw以來得到了很大改進),我嚴重懷疑大多數人使用的字符串分割函數真的「更」高效。 但我的口味不同。就像**偏見一樣**(有些人認爲只要他們看到** xml **就會慢**)。 – van 2009-10-06 05:30:31

0
select t.* 
from (
    select id = 35 union all 
    select id = 87 union all 
    select id = 445 union all 
    ... 
    select id = 33643 
) ids 
join my_table t on t.id = ids.id 

如果要搜索的ids的集合很小,這可以通過允許查詢引擎執行索引查找來提高性能。如果優化器判斷表掃描比例如一百個索引尋找更快,那麼優化器將指示查詢引擎。

注意查詢引擎傾向於把

select t.* 
from my_table t 
where t.id in (35, 87, 445, ..., 33643) 

等同於

select t.* 
from my_table t 
where t.id = 35 or t.id = 87 or t.id = 445 or ... or t.id = 33643 

,並注意查詢引擎往往不能夠執行與轉折搜索條件查詢索引搜索。例如,Google AppEngine數據存儲將不會執行具有不連續搜索條件的查詢,因爲它只會執行查詢,並知道如何執行索引查找。

+0

我不確定,但我認爲這可能會更高性能,如果你使用union all而不是簡單的union。我認爲,當你使用一個直接的聯盟時,它必須先測試它是否是重複的。 – wcm 2009-10-05 20:58:09

3

對於我來說,IN(...)並不是首選,因爲很多原因,包括限制參數的數量。

月Zich使用各種臨時表實現關於性能的說明跟進,這裏是從SQL執行計劃的一些數字:

  • XML解決方案:99%的時間 - XML解析
  • 使用UDF從CodeProject的逗號分隔過程:50%臨時表掃描,50%索引查找。如果這是字符串解析的最佳實現,那麼我們可以得到更多的答案,但我不想自己創建一個(我會愉快地測試另一個)。
  • CLR UDF拆分字符串:98% - 索引查找。

下面是CLR UDF代碼:

public class SplitString 
{ 
    [SqlFunction(FillRowMethodName = "FillRow")] 
    public static IEnumerable InitMethod(String inputString) 
    { 
     return inputString.Split(','); 
    } 

    public static void FillRow(Object obj, out int ID) 
    { 
     string strID = (string)obj; 
     ID = Int32.Parse(strID); 
    } 
} 

所以我將與揚同意,XML解決方案的效率不高。因此,如果以逗號分隔的列表作爲過濾器傳遞,則簡單的CLR UDF在性能方面似乎是最佳的。

我在一張200K的表中測試了1K記錄的搜索。

+0

+1:謝謝。這很有趣。我已經意識到在SQL中運行.NET的能力,但我從來沒有使用過它,所以這並不是我想到的。 – 2009-10-06 13:11:50

1

很久以前,我發現在我正在使用的特定DBMS上,IN列表在某個閾值(即IIRC,類似30-70)是更有效的,之後,使用臨時表來保存值列表並加入臨時表更爲有效。 (DBMS非常容易創建臨時表,但即使創建和填充臨時表的開銷,查詢的運行速度也更快)。這是對主數據表的最新統計信息(但它也有助於更新臨時表的統計數據)。

在現代DBMS中可能會有類似的效果;閾值水平可能已經發生了變化(我正在談論的是令人沮喪的接近二十年前),但是你需要做你的測量並考慮你的策略或策略。請注意,自那時以來優化器已經得到了改進 - 他們可能能夠合理使用更大的IN列表,或自動將IN列表轉換爲匿名臨時表。但測量將是關鍵。