2013-07-09 31 views
0

我有一個叫讀數有> 7600萬行中它是我上運行該查詢表:查詢慢上聚集索引一定的標準

declare @tunnel_id int = 13 
SELECT TOP 1 local_time, recorded_time 
FROM readings 
WHERE tunnel_id = @tunnel_id 
ORDER BY id DESC 

的id列是一個BIGINT,設置爲主鍵,並且具有聚集索引,並且在tunnel_id字段上還有一個索引。

作品很棒,在我嘗試的20個不同的tunnel_id中,大約有16個返回不到一秒鐘。但是,在最後4個左右,查詢需要40秒,並使用數十萬次讀取。

我試着修改查詢到這一點:

SELECT TOP (1) local_time, recorded_time 
FROM readings 
where id = (
    SELECT TOP 1 id 
    FROM readings 
    WHERE tunnel_id = 13 
    ORDER BY id DESC 
) 

這再一次只有幾tunnel_id的慢。更讓我更困惑的是內部選擇快速運行緩慢的ID,如果我硬編碼最大的ID而不是子查詢它也快速運行。

我在這裏錯過了什麼讓這個查詢執行得不好?

編輯意見:

Tunnel_id不是唯一的,每個通道都有數以百萬計的行。這是在Sql Server 2012上運行的。

我包含來自快速和慢速運行的實際執行計劃,它們是相同的。

快速: sql fast execution plan

慢:

sql fast execution plan

但你可以看到,在不到一秒鐘的第一執行而第二個需要51秒。

+0

我猜它的很大一部分是你的'ORDER BY ID DESC' - 默認情況下,你可能在'id ASC'上有聚簇索引,所以有可能在後臺發生排序。 – JNK

+0

爲什麼你首先有一個子查詢? –

+0

建議嘗試使用tunel_id的參數封裝在存儲過程中進行查詢。 otimizer可能會發生一些奇怪的事情,以及如何緩存執行計劃。一般來說,根據Micrsoft Press的Book 70-461,查詢Microsoft SQL Server,與特定查詢相比,查詢計劃對存儲過程的緩存效率更高。只是一個想法。 – Karl

回答

0

剛剛發現,你可以提示使用tunnel_id指數:

declare @tunnel_id int = 13 
SELECT TOP 1 local_time, recorded_time 
FROM readings 
WITH (INDEX(idx_tunnel_id)) 
WHERE tunnel_id = @tunnel_id 
ORDER BY id DESC 

如預期在不到1秒的回報,其工作原理。

1

該計劃基本上掃描整個聚集索引從開始到結束並查找與tunnel_id = @ tunnel_id的第一行。

我的教育猜測是'慢'隧道在聚集索引的開頭沒有任何行,因此它必須掃描更多的行。

此非聚集索引應該加快速度:

CREATE NONCLUSTERED INDEX [IX_FOO] ON [readings] 
(
    tunnel_id, 
    ID 
) 
INCLUDE 
(
    local_time, 
    recorded_time 
) 

這可能替換tunnel_id現有的索引。

+0

幾乎在同一時間幾乎相同的答案:P 有趣的是在關鍵字處添加id,但是如果它在desc處(如在查詢中)會更好嗎? – Alejandro

+0

@Ajjandro - 哈! :)是將ID放入索引而不是包含在內,可以直接查找單行。關於DESC的好處。我相信它可以使用它(只是做一個反向掃描),但是這當然應該被測試。 –

+0

我同意。匹配'tunnel_id = 13'的值可能都在索引的末尾。 SQL Server會假定它們是平均分佈的。 [使用跟蹤標誌9130](http://sqlblogcasts.com/blogs/sqlandthelike/archive/2012/12/06/my-new-favourite-traceflag.aspx)將顯示在「TOP 1」之前實際掃描了多少行'被找到了。順便說一句,如果'ID'是聚簇索引鍵,那麼它已經被自動包含在非唯一非聚簇索引中作爲鍵列。 –

1

這裏有趣的部分是SQL根本不使用tunnel_id中的索引,而只是在整個表中掃描表,如果它大到76百萬行那麼速度很慢。 我認爲它沒有使用它的真正原因是因爲按ID排序,因爲它必須執行查找,然後進行額外的排序。我首先懷疑參數嗅探是這裏的主要問題。

我會嘗試改變索引,而不是覆蓋。如果可能的話,在索引中包括本地時間,記錄時間和id(不管100%確定是否需要,因爲它是集羣密鑰)。

CREATE NONCLUSTERED INDEX IX_tunnel_id ON dbo.readings (tunnel_id) INCLUDE (id, local_time, recorded_time) 

需要注意的是,雖然這樣可以提高這個特定的查詢,它會使插入和更新慢一點,並且需要額外的存儲空間。