SQL Server執行部分語句的記憶體代價

來源:互聯網
上載者:User
文章目錄
  • 1.1    執行全表選取或者低選擇性選取
  • 1.2    執行高選擇性選取
  • 2.1    執行全表選取或者低選擇性選取
  • 2.2    執行高選擇性選取
  • 3.1    執行全表選取或者低選擇性選取
  • 3.2    執行高選擇性選取
  • 4.1    執行全表選取或者低選擇性選取
  • 4.2    執行高選擇性選取
  • 5.1    執行全表選取或者低選擇性選取
  • 5.2    執行高選擇性選取
  • 6.1    執行全表選取或者低選擇性選取
  • 6.2    執行高選擇性選取
  眾所周知,SQL Server執行SQL語句的效能判定標準主要是IO讀取數大小。本文在不違反這一原則情況下,同時來分析一下部分SQL語句執行時,SQL Server記憶體的變化情況。

首先簡述一下SQL Server記憶體佔用的特點。SQL Server所佔用的記憶體除程式(即SQL Server引擎)外,主要包括緩衝的資料(Buffer)和執行計畫(Cache)。SQL Server以8KB大小的頁為單位儲存資料。這個和SQL Server資料在磁碟上的儲存頁大小相同。當SQL Server執行SQL 陳述式時,如果需要的資料已經在其記憶體中,則直接從記憶體緩衝區讀取並進行必要的運算然後輸出執行結果。如果資料還未在記憶體中,則首先將資料從磁碟上讀入記憶體Buffer中。而我們通常評價SQL效能指標中的IO邏輯讀取數對應的正是從記憶體緩衝區讀取的頁數,而IO物理讀取數則對應資料從磁碟讀取的頁數。

註:以下的實驗在多人共用的開發測試伺服器上也可以進行,因為實際上可以分別看到某個表所佔用的記憶體情況。但為了方便,筆者在做此實驗時,在一個單獨的、確認沒有其它並發任務的資料庫上進行,因此所看到的記憶體變化正是每一次所執行的SQL語句引起的。

我們首先來看一個簡單的執行個體。建立下表:

Create Table P_User

(   UserMobileStatus int NOT NULL,

    MobileNo int NOT NULL,

    LastOpTime DateTime Not NULL

)

然後為該表插入一定的資料:

Declare @i int

Set @i=28000

WHILE @i<29000

BEGIN

    Insert Into P_User

    Select @i % 2,@i,GetUTCDate()

    Set @i=@i+1

END

然後我們在查詢分析器中首先執行:

Set Statistics IO ON

並按下Ctrl+M以顯示實際的執行計畫。

此時,可以開始進行我們的實驗了。為了準確觀察每一次SQL語句變化情況,在執行第一條SQL語句以前,我們首先清空SQL Server所佔用的資料記憶體:

CHECKPOINT

GO

DBCC DROPCLEANBUFFERS

這將清空SQL Server所佔用的資料緩衝區(此語句在生產伺服器上慎用,因為將導致一段時間內後續的SQL語句執行變慢)。

測試1:在沒有索引的表上執行SQL語句1.1    執行全表選取或者低選擇性選取

Select * From P_User

從SQL執行計畫可以看到,由於此時表中沒有任何索引,因此將產生Table Scan。而IO統計結果如下:

(1000 row(s) affected)

表'P_User'。掃描計數1,邏輯讀取4 次,物理讀取4 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。

我們看一下資料庫記憶體中的情況。

首先查詢到我們所操作database的database_id:

Select database_id From sys.databases Where name='TestGDB'

然後使用該database_id從表中查看記憶體情況:

SELECT * FROM sys.dm_os_buffer_descriptors bd

WHERE database_id=5

order by allocation_unit_id,page_id

得到結果如下:

得到的結果中可以看到,除了必要的管理頁(一個PFS_Page和一個IAM_Page)外,記憶體中總共出現了4個Data_Page頁。這和剛才IO統計中看到的結果:邏輯讀為4,物理讀為4相同。由於是全表讀取,表明P_User表全部資料所佔用的資料頁數也正是4,將這4個資料頁的row_count數加起來也可以驗證其總資料行=1000。 

在上例中,如果不清空資料緩衝區,再執行一遍SQL,可以看到記憶體毫無變化,而邏輯讀也不變,只是物理讀變為0,因為已經不需要再從磁碟讀入資料。

1.2    執行高選擇性選取

另外,在沒有索引的情況下,如果將上例修改為:

Select Top 1 * From P_Order 或者Select * From P_Order Where MobileNo=28502

可以看到,系統同樣要讀取全部的資料頁到記憶體。

 如果使用Select Top 1 * From P_Order Where MobileNo=28502這樣的選取方式,有可能會出現唯讀取部分資料頁到記憶體的情況。但由於在沒有索引情況下,資料實際上是無序存放在堆上,所以結果很不穩定,也有可能發生讀取所有的資料頁到記憶體。

測試2:建立叢集索引情況下,執行SQL語句2.1    執行全表選取或者低選擇性選取

修改表結構,在MobileNo欄位上建立叢集索引。然後再次執行剛才的SQL語句。得到的執行計畫變為叢集索引掃描。IO統計訊息為:

(1000 row(s) affected)

表'P_User'。掃描計數1,邏輯讀取6 次,物理讀取1 次,預讀4 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。

這裡的邏輯讀取變為6次。

記憶體情況如下:

記憶體中的變化是增加了一個非葉級的叢集索引頁,而葉級的叢集索引則會和資料放在一起。

另外,可以查看該表索引的層級:

SELECT database_id,object_id,index_id,index_level,page_count,record_count

FROM sys.dm_db_index_physical_stats

    (DB_ID(N'TestGDB'), OBJECT_ID(N'dbo.P_User'), NULL, NULL , 'DETAILED');

從結果可以看到該表的叢集索引總共分2級。

因而邏輯讀增加了2——(由於發生Clustered Index Scan,除了根層級的叢集索引頁佔用1次外,從根層級叢集索引定位到分葉層級的叢集索引也將額外佔用1次邏輯讀)。 

另外一個變化是只發生了一次物理讀,即讀取根層級的叢集索引頁,另外4個資料頁則通過預讀方式而不是物理讀從磁碟裝入記憶體Buffer。這使得有叢集索引的情況下,執行SQL所直接花費的代價實際上更小。

2.2    執行高選擇性選取

在建立叢集索引情況下,對效能有益的變化是:

對於Select Top 1 * From P_Order 或者Select * From P_Order Where MobileNo=28702這樣的語句,在有叢集索引情況下,只會將最終記錄所在的頁讀入記憶體。

測試3:建立非叢集索引情況下,執行SQL語句3.1    執行全表選取或者低選擇性選取

如果將表中同一欄位的叢集索引換成非叢集索引,則可以看到如下特點:

執行全表掃描將和沒有任何索引的情況相似,將讀取所有的資料頁到記憶體。此時,SQL Server的查詢引擎實際上無法使用非叢集索引。

3.2    執行高選擇性選取

將唯讀取最終資料所在的頁到記憶體。通過查詢計劃可以看到,SQL Server在非叢集索引上使用INDEX SEEK,然後通過lookup 得到資料實際所在行(索引覆蓋情況下例外,因為不需要定位到實際資料行)。

測試4:執行Nested Loop Join

在進行測試前,我們先準備另外一張表和資料。

Create Table P_Order

(   UserStatus int NOT NULL,

    MobileNo int NOT NULL,

    Sid int Not NULL,

    LastSubTime DateTime

)

插入資料:

Declare @i int

Set @i=20000

WHILE @i<30000

BEGIN

    Insert Into P_Order

    Select @i % 2,@i,@i-19999,GetUTCDate()

    set @i=@i+1

END

可以看到,在執行全表掃描情況下,該表10000條資料總共佔用38個記憶體資料頁。

4.1    執行全表選取或者低選擇性選取

Select * From P_Order A

Inner Loop JOIN P_User B ON A.MobileNo=B.MobileNo

對於此種高選擇性選擇,預設情況下SQL Server不會執行Loop Join。因此,使用了強制性的聯結提示。

在兩個表都沒有任何索引的情況下,可以看到:

兩個表所有的資料頁都將被載入到記憶體。邏輯讀取代價高達6萬多次——對於P_Order表中的每一條記錄,都將在P_User表中進行遍曆。

在其中一個表有叢集索引情況下,儘管邏輯讀取相比剛才的6萬多次已經大大下降,但仍然達到2萬次。而且聯結的次序對查詢效能影響很大。因為其實際執行的是將SQL語句中前面的表作為聯結的外部輸入,而後面的表作為聯結的內部輸入。

在兩個表都有叢集索引情況下,相比較而言,邏輯讀仍然達到數千次(取決於最終輸出的資料大小),但相比較已經大大改善。而且表中的資料只有最終需要輸出的部分才會被讀入記憶體Buffer中。

4.2    執行高選擇性選取

執行如下的SQL語句:

Select * From P_Order A

Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

Where A.MobileNo=28913

在兩個表都沒有任何索引情況下,兩張表都將執行全表掃描。要讀入所有的資料頁到記憶體。總體邏輯讀取決於兩表的資料頁數。

在一個表有叢集索引或者非叢集索引情況下,該表將執行Index Seek,另一個表將出現全表掃描。記憶體資料緩衝區中,將有一張表唯讀入最終資料所在的資料頁、一張表讀入全部資料頁。邏輯讀數取決於表在聯結中的秩序、以及無索引表的資料頁數。

在兩個表都有叢集索引情況下,邏輯讀最小,每個表只有2到3次。而且只有實際需要輸出的資料才會被讀入記憶體頁。兩個表都有非叢集索引情況下,消耗的邏輯讀和記憶體資源近似。

測試5:執行Merge Join5.1    執行全表選取或者低選擇性選取

執行SQL:

Select * From P_Order A

Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

如果兩張表都沒有任何索引,則兩張表都要進行全表掃描。所有的資料都要讀入記憶體頁。

邏輯讀數近似等於兩張表的資料頁總和。SQL Server處理過程中將使用到暫存資料表。

只有一張表有叢集索引的情形類似,SQL Server處理過程中將使用到暫存資料表。並且讀入所有的資料頁到記憶體。

如果兩張表都有叢集索引,儘管兩表的資料都會被讀入記憶體頁,但邏輯讀數已經大大減少,等於其中一張表總資料記憶體頁數加上最終輸出的資料頁數。而且SQL Server處理過程中將不需要再使用暫存資料表。

5.2    執行高選擇性選取

對於這樣的高選擇性SQL語句,SQL Server 將提示無法產生執行計畫。

Select * From P_Order A

Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

Where A.MobileNo=28913

但可以執行:

Select * From P_Order A

Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

Where A.MobileNo<=28001 (註:最終結果只有2條)

這樣的屬於低選擇性語句,但最終結果也很少的語句。如前面所述,這種情況下,採用netsted loop聯結效率可能更高。

測試6:執行Hash Join6.1    執行全表選取或者低選擇性選取

對於兩表聯結,如果兩張表都沒有索引,不寫明聯結提示的情況下,SQL Server預設使用hash join。而對於兩表聯結,如果兩張表都有叢集索引,則SQL Server預設使用Merge Join。

執行SQL:

Select * From P_Order A

Inner hash JOIN P_User B ON A.MobileNo=B.MobileNo

在使用hash join情況下,無論兩張表有無索引,都將讀取所有的資料頁到記憶體,SQL Server將使用暫存資料表進行處理。邏輯讀數近似等於兩張表的資料頁總和。

6.2    執行高選擇性選取

和merge join執行高選擇性選取情況類似,也無法直接執行:

Select * From P_Order A

Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

Where A.MobileNo=28913 

但可以執行這樣的結果很少的低選擇性指令碼:

Select * From P_Order A

Inner merge JOIN P_User B ON A.MobileNo=B.MobileNo

Where A.MobileNo<=28001 (註:最終結果只有2條)

但此情況下,採用netsted loop聯結效率更高。

測試總結

本次測試的主要意義在於,通過分析具體的記憶體變化結合執行計畫、IO讀取等資訊,可以更清楚地瞭解SQL Server執行SQL 陳述式過程。

另外,也驗證了一些通過分析SQL 陳述式的IO讀取、執行計畫曾經得到的經驗:

(1)       在執行單表查詢時,如果是高選取查詢,要建立非叢集索引或者叢集索引(推薦非叢集索引,是獨立於資料存放的)。如果是低選擇性查詢,則需要建立叢集索引。

(2)       在執行聯結查詢時,如果最終輸出結果很少,則適宜使用nested loop join;如果輸出結果較多,則通過建立叢集索引,而以merge join方式查詢能得到好的效能。對於效能較低的hash join,最好通過轉換成merge join或者nested loop join方式提高查詢效能。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.