2011-06-03 24 views
13

我有一個SQL查詢(LINQ通過產生的實體),這大致是這樣的:爲什麼我的SQL Server ORDER BY慢,儘管有序列被索引?

SELECT * FROM [mydb].[dbo].[employees] 
JOIN [mydb].[dbo].[industry] 
    ON jobs.industryId = industry.id 
JOIN [mydb].[dbo].[state] 
    ON jobs.stateId = state.id 
JOIN [mydb].[dbo].[positionType] 
    ON jobs.positionTypeId = positionType.id 
JOIN [mydb].[dbo].[payPer] 
    ON jobs.salaryPerId = payPer.id 
JOIN [mydb].[dbo].[country] 
    ON jobs.countryId = country.id 
WHERE countryName = 'US' 
ORDER BY startDatetime 

查詢返回約1200行,我不認爲這是一個巨大的數額。不幸的是,它也需要〜16秒。如果沒有ORDER BY,查詢需要1秒鐘的時間<。我已經使用SQL Server Management Studio在startDatetime列上放置索引,並在「cityId,industryId,startDatetime,positionTypeId,payPerId,stateId」上放置了一個聚集索引(即「作業」中的所有列)我們使用JOIN和我們使用ORDER BY的列)。我在JOIN中使用的每個列上都有單獨的索引。不幸的是,這並沒有使查詢更快。

我跑了showplan和有:

|--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[cityId])) 
     |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[stateId])) 
     | |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[industryId])) 
     | | |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[positionTypeId])) 
     | | | |--Nested Loops(Inner Join, OUTER REFERENCES:([mydb].[dbo].[jobs].[salaryPerId])) 
     | | | | |--Sort(ORDER BY:([mydb].[dbo].[jobs].[issueDatetime] ASC)) 
     | | | | | |--Hash Match(Inner Join, HASH:([mydb].[dbo].[currency].[id])=([mydb].[dbo].[jobs].[salaryCurrencyId])) 
     | | | | |   |--Index Scan(OBJECT:([mydb].[dbo].[currency].[IX_currency])) 
     | | | | |   |--Nested Loops(Inner Join, WHERE:([mydb].[dbo].[jobs].[countryId]=[mydb].[dbo].[country].[id])) 
     | | | | |    |--Index Seek(OBJECT:([mydb].[dbo].[country].[IX_country]), SEEK:([mydb].[dbo].[country].[countryName]='US') ORDERED FORWARD) 
     | | | | |    |--Clustered Index Scan(OBJECT:([mydb].[dbo].[jobs].[PK_jobs])) 
     | | | | |--Clustered Index Seek(OBJECT:([mydb].[dbo].[payPer].[PK_payPer]), SEEK:([mydb].[dbo].[payPer].[id]=[mydb].[dbo].[jobs].[salaryPerId]) ORDERED FORWARD) 
     | | | |--Clustered Index Seek(OBJECT:([mydb].[dbo].[positionType].[PK_positionType]), SEEK:([mydb].[dbo].[positionType].[id]=[mydb].[dbo].[jobs].[positionTypeId]) ORDERED FORWARD) 
     | | |--Clustered Index Seek(OBJECT:([mydb].[dbo].[industry].[PK_industry]), SEEK:([mydb].[dbo].[industry].[id]=[mydb].[dbo].[jobs].[industryId]) ORDERED FORWARD) 
     | |--Clustered Index Seek(OBJECT:([mydb].[dbo].[state].[PK_state]), SEEK:([mydb].[dbo].[state].[id]=[mydb].[dbo].[jobs].[stateId]) ORDERED FORWARD) 
     |--Clustered Index Seek(OBJECT:([mydb].[dbo].[city].[PK_city]), SEEK:([mydb].[dbo].[city].[id]=[mydb].[dbo].[jobs].[cityId]) ORDERED FORWARD) 

重要的線似乎是「| --sort(ORDER BY:。([MYDB] [DBO] [就業] [issueDatetime] ASC ))「—沒有提及該列上的索引。

爲什麼我的ORDER BY讓我的查詢慢得多,以及如何加快查詢速度?

+1

並且加入的所有外鍵列也被索引? – 2011-06-03 06:57:08

+0

@marc_s:是的,單獨表中的所有ID列也被索引。我99.9%確定這不是連接速度慢,因爲刪除ORDER BY(並離開JOIN)會將時間從16秒縮短到1秒以內。 – George 2011-06-06 00:09:54

回答

10

如果您的查詢中沒有包含訂單,那麼它將返回找到它的任何礦商的數據。無法保證當您再次運行查詢時,數據甚至會以相同順序返回。

如果包含order by子句,則dabatase必須按正確順序構建行的列表,然後按該順序返回數據。這可能需要很多額外的處理,這會轉化爲額外的時間。

對您的查詢可能返回的大量列進行排序可能需要更長的時間。在某些時候,你將耗盡緩衝空間,數據庫將不得不開始交換,並且性能將下降。

嘗試返回更少的列(指定您需要的列而不是選擇*)並查看查詢是否運行得更快。

+0

所以我認爲這是一個「正確」的答案 - 我的描述欄非常大(它存儲了一大塊HTML),它將每一行的大小都拉大了,這意味着排序必須發送到磁盤。 – George 2011-06-06 01:23:16

+0

+1因爲**列數很多**。您應該先返回已排序列的Id,然後您可以爲每個ID選擇整個元組。 – 2015-07-12 05:21:01

1

新聞快訊:索引列無助於快速排序。

如果你想讓你的查詢更快地反轉你的表的順序。具體來說,列表表格country首先在您的連接表中。原因? where子句可以過濾來自第一個表的行,而不必使所有這些聯接,,然後過濾行。

+0

索引是否以排序順序存儲?添加它時,我可以選擇「排序:升序」和「排序:降序」。我已經在SQLite中使用索引使查詢中的ORDER BY快得多。 我交換了連接的順序,並將查詢時間從16秒縮短到7秒 - 但是ORDER BY仍然佔用了當時所有的7秒。 真的沒有辦法讓ORDER BY更快嗎? – George 2011-06-03 05:16:15

+1

@George:索引按排序順序存儲,是的,但通常每個表只能選擇一個索引,在這種情況下,它選擇聚集索引「PK_jobs」,因爲它是一個覆蓋索引。如果存在更多緊迫的問題(如聯接),索引的存在對「ORDER BY」沒有幫助。 – 2011-06-03 05:18:36

2

聚簇索引中包含哪些字段?由於您希望選擇一個countryId(間接地,通過countryName),然後按startDateTime排序,您需要先將startDateTime字段放在第一位,以使ORDER BY與之匹配,或者在此例中爲(countryId, startDateTime)

+0

我已將第一個聚集索引中的列重新排序,以便countryId爲first,startDateTime爲第二個,並且還在countryId和startDateTime上一起添加了單獨的索引。 看看我的QUERY PLAN,查詢似乎是在JOINED to表和PK我在'employees'表上的PK索引,但沒有任何其他(而不是我的聚集索引)。 速度沒有提高。 – George 2011-06-03 05:29:13

+1

儘管聚簇索引中的列也被添加到該表上每個非聚簇索引的每個條目,並且聚簇索引變得臃腫,由多個大型列組成,那麼整個表的索引結構就會變得臃腫,從而導致整體性能受到嚴重損害。如果可能的話,我會盡量避免使用複合聚集鍵,而且絕對是,我會不惜一切代價避免使用VARCHAR列> = 10個字符。 – 2011-06-03 06:59:37

+0

@marc_s:是的,這是它 - 我的描述領域是巨大的(索引是一個紅色的鯡魚)。謝謝你的幫助。 – George 2011-06-06 01:26:49

1

你應該試試下面的代碼也

插入記錄到臨時表如果不使用ORDER BY子句

SELECT * into #temp FROM [mydb].[dbo].[employees] 
JOIN [mydb].[dbo].[industry] 
    ON jobs.industryId = industry.id 
JOIN [mydb].[dbo].[state] 
    ON jobs.stateId = state.id 
JOIN [mydb].[dbo].[positionType] 
    ON jobs.positionTypeId = positionType.id 
JOIN [mydb].[dbo].[payPer] 
    ON jobs.salaryPerId = payPer.id 
JOIN [mydb].[dbo].[country] 
    ON jobs.countryId = country.id 
WHERE countryName = 'US' 

現在運行使用秩序的聲明條款

Select * from #temp ORDER BY startDatetime 
+0

運行速度明顯加快 - 整個查詢現在需要<1秒。有什麼辦法可以讓LINQ to SQL來生成這個查詢,還是我需要手動編寫一些SQL?我一直在做一些谷歌搜索 - 物化/索引視圖會有幫助嗎? – George 2011-06-03 05:33:51

+0

你可以在單獨的線程中發佈這個嗎? :)如果它對你有幫助,請將其標記爲答案。 :) – Pankaj 2011-06-04 09:57:14

+0

我不確定這實際上是否有幫助 - 最初看起來這是讓事情變得更快, 但: SELECT * into #temp FROM [atr]。[dbo]。[jobs] doesn'將不起作用 - 「列名必須是唯一」(多 「ID」 列) 所以,我想: SELECT標題,的cityName,Statename的INTO #TEMP ... 這是快(<1秒),但如果我添加在「描述」(varchar(MAX))列中,我回到慢: SELECT title,cityName,stateName,description INTO #temp ... 所以它看起來像查詢速度慢如果(1)ORDER BY存在* AND *(2)存在VARCHAR(MAX)字段。 – George 2011-06-06 01:09:00

6

Bec使用查詢項目的所有列(*),它需要5列用於連接條件,並且對於可能是連接表列的子句有非選擇性WHERE子句,因此它會觸發Index Tipping Point:優化程序決定它的成本較低掃描整個表,對其進行過濾並對其進行排序,以便對範圍內的索引進行掃描,然後查找表中的每個關鍵字以檢索所需的額外列(連接數爲5,其餘爲*)。

一個更好的指標,部分覆蓋此查詢可能是:

CREATE INDEX ... ON .. (countryId, startDatetime); 

傑弗裏的建議,使聚集索引將覆蓋查詢100%,肯定會提高性能,但改變聚集索引有許多副作用。我將從上面的非聚集索引開始。除非其他查詢需要它們,否則可以刪除您創建的所有其他非聚簇索引,它們將無助於此查詢。

相關問題