SQL Server在堆表中查詢數據時,是不知道到底有多少數據行符合你所指定的查找條件,它將根據指定的查詢條件把數據表的全部數據都查找一遍。如果有可采用的索引,SQL Server只需要在索引層級查找每個索引分頁的數據,再抓出所需要的少量數據分頁即可。訪問數據表內數以萬計的數據分頁與只訪問少數索引的分頁兩者間的差異,讓索引變成效能調校的最佳工具。
堆表的結果示意圖:
堆表內的數據頁和行沒有任何特定的順序,也不鏈接在一起。數據頁之間唯一的邏輯連接是記錄在 IAM 頁內的信息。
假設訂單明細表中有100萬條數據,需要查詢某個訂單的明細數據,如下:
select * from T_EPZ_INOUT_ENTRY_DETAIL where entry_apply_id='31227000034000090169'
如果在堆表中進行查詢,SQL Server通過掃描 IAM 頁對堆表進行全表掃描,對entry_apply_id比較100萬次,如果以entry_apply_id字段建立索引,則因為索引鍵值數據都必定以B-Tree有順序的擺放,所以可采用二分查找找數據。也就是2的N次方大於記錄數,就可以找到該條數據。而2的20次方大於100萬,因此最多找尋20次就可以找到該條記錄。20次與100萬次的比較,你可以輕松感受出性能的差異。
下面我們舉個實例來做說明:
一、表空間的高度碎片化
1.此表的碎分布信息,從下圖中可以看出此表的有非常多的內部碎片與外部碎片。
a) 此表的平均頁密度只有24%,也就是說平均一頁只有1/4空間才有數據,其他的3/4空間都是空着,有着很多的內部碎片。
b) 此表的掃描密度只有13%,也就是說理論上的區的數量與實現上區的數量之比為1:7.5,也就是說存在非常多的外部碎片,也就是說每個區的利用率相當低,一個區的數據全部加起來,才一個數據頁。
如下圖。
對字段的說明:(例二、例三中的圖中字段說明是一樣的。)
Pages:如果在DBCC SHOWCONTIG 語句中指定了index_id,則將遍歷指定索引的葉級上的頁鏈,索引為葉子層使用的分頁數目。如果只指定 table_id,或者 index_id 為 0,則將掃描指定表的數據頁。
AvgeragePageDensity:平均頁密度(為百分比)。該值考慮行大小,所以它是頁的填滿程度的更准確表示。百分比越大越好。
ScanDensity:掃描密度(為百分比)。這是“BestCount”與“ActualCount”的比率。如果所有內容都是連續的,則該值為 100;如果該值小於 100,則存在一些碎片。
2. SQL查詢語句與查詢執行計划成本
--查詢語句:
SET STATISTICS IO on
go
SET STATISTICS TIME on
go
select * from T_EPZ_INOUT_ENTRY_DETAIL where entry_apply_id='31227000034000090169'
go
SET STATISTICS IO off
go
SET STATISTICS TIME off
go
3.查詢所需要的時間與I/O
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 16 毫秒,耗費時間 = 76 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
(所影響的行數為 13 行)
表 'T_EPZ_INOUT_ENTRY_DETAIL'。掃描計數 1,邏輯讀 4825 次,物理讀 6 次,預讀 19672 次。
SQL Server 執行時間:
CPU 時間 = 47 毫秒,耗費時間 = 10544 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
二、表低度碎片化
1. 此表的碎分布信息,從下圖中可以看出此表的有非常多的內部碎片與外部碎片。
a) 此表的平均頁密度只有97%,也就是說數據差不多把一個數據頁都塞滿了,沒有多余的空間,沒有內部碎片。
b) 此表的掃描密度只有98%,也就是說理論上的區的數量與實現上區的數量之比為1:1,也就是說基本上沒有外部碎片,也就是說每個區的利用率相當高。
如下圖。
備注:對於上圖中的一些字段說明,見(一)。
2.SQL查詢語句與查詢執行計划成本
--查詢語句:
SET STATISTICS IO on
go
SET STATISTICS TIME on
go
select * from T_EPZ_INOUT_ENTRY_DETAIL where entry_apply_id='31227000034000090169'
go
SET STATISTICS IO off
go
SET STATISTICS TIME off
go
3.查詢所需要的時間與I/O
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 92 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
(所影響的行數為 13 行)
表 'T_EPZ_INOUT_ENTRY_DETAIL'。掃描計數 1,邏輯讀 1205 次,物理讀 0 次,預讀 1209 次。
SQL Server 執行時間:
CPU 時間 = 16 毫秒,耗費時間 = 390 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
說明:邏輯讀取的數值十分接近數據庫中數據頁數字,預讀的次數也十分接近數據頁的數字,物理讀取值為0,即所需要查詢的數據全部在預讀的數據中間。
三、表添加主鍵,我們看一下有索引的查詢
1. 此表的碎分布信息,從下圖中可以看出此表的有非常多的內部碎片與外部碎片。
a) 此表的平均頁密度只有97%,也就是說數據差不多把一個數據頁都塞滿了,沒有多余的空間,沒有內部碎片。
b) 此表的掃描密度只有98%,也就是說理論上的區的數量與實現上區的數量之比為1:1,也就是說基本上沒有外部碎片,也就是說每個區的利用率相當高。
如下圖:
備注:對於上圖中的一些字段說明,見(一)。
2.SQL查詢語句與查詢執行計划成本
--查詢語句:
SET STATISTICS IO on
go
SET STATISTICS TIME on
go
select * from T_EPZ_INOUT_ENTRY_DETAIL where entry_apply_id='31227000034000090169'
go
SET STATISTICS IO off
go
SET STATISTICS TIME off
go
3.查詢所需要的時間與I/O
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 98 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
(所影響的行數為 13 行)
表 'T_EPZ_INOUT_ENTRY_DETAIL'。掃描計數 1,邏輯讀 3 次,物理讀 2 次,預讀 0 次。
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 30 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 執行時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
SQL Server 分析和編譯時間:
CPU 時間 = 0 毫秒,耗費時間 = 0 毫秒。
比較以上三者的各種關鍵值,就可以看出性能的提升程度。
物理操作 |
邏輯操作 |
I/O成本 |
CPU成本 |
成本 |
子樹成本 |
邏輯讀 |
物理讀 |
預讀 |
|
例一:堆表高度碎片化 |
Table Scan 邏輯運算符和物理運算符檢索 Argument 列內指定表中的所有行 |
同左 |
3.61 |
0.0342 |
3.645123 |
3.65 |
4825 |
6 |
19672 |
例二:堆表低度碎片化 |
同上 |
同左 |
0.464 |
0.0171 |
0.963642 |
0.963 |
1205 |
0 |
1209 |
例三:表(帶主鍵) |
Clustered Index Seek 邏輯運算符和物理運算符利用索引的查找能力從聚集索引中檢索行 |
同左 |
0.0032 |
0.000086 |
0.003289 |
0.00328 |
3 |
2 |
0 |
例一/例二/例三 | 1128/145/1 |
397/198/1 |
1108/292/1 |
1112/293/1 |
1608/401/1 |
3/0/1 |
16/1/0 |
對表中列的說明:
物理操作:使用的物理運算符,例如 Hash Join 或 Nested Loops。
邏輯操作:與物理運算符匹配的邏輯運算符,如 Join 運算符。
I/O 成本:用於操作的所有 I/O 活動的預計成本。該值應盡可能低。
CPU 成本:用於操作的所有 CPU 活動的預計成本。
成本:查詢優化器執行此操作的成本,包括此操作的成本占查詢總成本的百分比。由於查詢引擎選擇最高效的操作執行查詢或執行語句,因此該值應盡可能低。
子樹成本:查詢優化器執行此操作及同一子樹內位於此操作之前的所有操作的總成本。
在本文的三個例子中,預讀值最高的為19672,最低的為0,物理讀的值最高為6,最低為0,而邏輯讀的值最高為4825,最低為3。
那么我在服務器上 執行查詢時的過程是怎么樣的呢?以例一為例。
首先,SQL Server會開始檢查完成查詢所需要的數據是否在數據緩沖區中,它會很快地發現這些數據不在數據緩沖區中, 並啟動預讀機制將它所需要的數據頁讀取到數據緩沖區中,但是由於數據頁碎片嚴重情況,需要多次切區,大大提升了I/O的消耗,如例一中讀取19672次,所以當碎片非常嚴重時,I/O讀取非常頻繁,多讀取了4倍的數據頁。
其次,如例一,當SQL Server檢查是否所需要的全部數據都已經在數據緩沖區時,會發現已經有4819個數據頁在數據緩沖區中,還有六個數據頁不在,它就會立即再次讀取磁盤,所以有了6次的物理讀,在將所需要的頁讀到數據緩沖區。一旦所有的數據都在數據緩沖區后,SQL Server就可以處理查詢了。