1. 程式人生 > >.NET技術面試題系列(2) -sql server數據庫優化規範

.NET技術面試題系列(2) -sql server數據庫優化規範

很難 distinct emp 範圍 第一個 nbsp l數據庫 一行 fresh

1、數據庫優化規範

a.索引

每個表格都要求建立主鍵,主鍵上不一定需要強制建立聚集索引。

聚集索引,表中存儲的數據按照索引的順序存儲,即邏輯順序決定了表中相應行的物理順序,因此聚集索引的字段值應是不會改變的值,並且是順序增長的,否則對數據新增/修改/刪除的影響比較大。

非聚集索引,一般考慮在下列情形下使用非聚集索引:使用JOIN的條件字段、使用GROUP BY的字段、完全匹配的WHERE條件字段、外鍵字段等等。

索引是有900字節大小限制的,因此不要在超長字段上建索引,索引字段的總字節數不要超過900字節,否則插入的數據達到900字節時會報錯。

合理建立索引,合理建立索引,合理建立索引。

b.使用SET NOCOUNT ON 選項

缺省地,每次執行SQL語句時,一個消息會從服務端發給客戶端以顯示SQL語句影響的行數。這些信息對客戶端來說很少有用,甚至有些客戶端會把這些信息當成錯誤信息處理。通過關閉這個缺省值,你能減少在服務端和客戶端的網絡流量,幫助全面提升服務器和應用程序的性能。為了關閉存儲過程級的這個特點,在每個存儲過程的開頭包含“SET NOCOUNT ON”語句。同樣,為減少在服務端和客戶端的網絡流量,生產環境中應該去掉存儲過程中那些在調試過程中使用的SELECT和PRINT語句。

c.正確使用UNION和UNION ALL

許多人沒完全理解UNION和UNION ALL是怎樣工作的,因此,結果浪費了大量不必要的SQL Server資源。當使用UNION時,它相當於在結果集上執行SELECT DISTINCT。換句話說,UNION將聯合兩個相類似的記錄集,然後搜索重復的記錄並排除。如果這是你的目的,那麽使用UNION是正確的。但如果你使用UNION聯合的兩個記錄集本身就沒有重復記錄,那麽使用UNION會浪費資源,因為它要尋找重復記錄,即使你確定它們不存在。

所以如果你知道你要聯合的記錄集裏沒有重復,那麽你要使用UNION ALL,而不是UNION。UNION ALL聯合記錄集,但不搜索重復記錄,這樣減少SQL Server資源的使用,從而提升性能。

d.只返回需要的數據,盡量不用SELECT *

絕大多數情況下,不要用 * 來代替查詢返回的字段列表,用 * 的好處是代碼量少、就算是表結構或視圖的列發生變化,編寫的查詢SQL語句也不用變,都返回所有的字段。但數據庫服務器在解析時,如果碰到 *,則會先分析表的結構,然後把表的所有字段名再羅列出來,這就增加了分析的時間。

另一個問題是,SELECT * 可能包含了不需要的列,增加了網絡流量。如果在視圖創建中使用了SELECT *,在後期如果有對視圖基表的表結構進行了更改,當查詢視圖時,可能會生成意外結果,除非重建視圖或利用sp_refreshview更新視圖的元數據。

e.慎用SELECT DISTINCT

DISTINCT子句僅在特定功能的時候使用,即從記錄集中排除重復記錄的時候。這是因為DISTINCT子句先獲取結果集然後去重,這樣增加了SQL Server資源的消耗。當然,如果你需要去做,那就只有去做了。

如果你知道SELECT語句將從不返回重復記錄,那麽使用DISTINCT語句是對SQL Server資源不必要的浪費。

f.少用遊標

任何一種遊標都會降低SQLServer性能。有些情況不能避免,但大多數情況可以避免,所以如果你的應用程序目前正在使用TSQL遊標,看看這些代碼是否能夠重寫以避免它們。如果你需要一行一行的執行操作,考慮使用批處理來代替遊標的使用。

g.字段前加指定的別名

當在SQL語句中連接多個表時,請將表名或別名加到每個Column前面,這樣可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。

h.SARG你的WHERE條件

ARGE來源於"Search Argument"(搜索參數)的首字母拼成的"SARG",它是指WHERE子句裏,列和常量的比較。如果WHERE子句是sargable(可SARG的),這意味著它能利用索引加速查詢的完成。如果WHERE子句不是可SARG的,這意味著WHERE子句不能利用索引(或至少部分不能利用),執行的是全表或索引掃描,這會引起查詢的性能下降。

在WHERE子句裏不可SARG的搜索條件如"IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT IKE"和"LIKE ‘%500‘",通常(但不總是)會阻止查詢優化器使用索引執行搜索。另外在列上使用包括函數的表達式、兩邊都使用相同列的表達式、或和一個列(不是常量)比較的表達式,都是不可SARG的。

並不是每一個不可SARG的WHERE子句都註定要全表掃描。如果WHERE子句包括兩個可SARG和一個不可SARG的子句,那麽至少可SARG的子句能使用索引(如果存在的話)幫助快速訪問數據。

大多數情況下,如果表上有包括查詢裏所有SELECT、JOIN、WHERE子句用到的列的覆蓋索引,那麽覆蓋索引能夠代替全表掃描去返回查詢的數據,即使它有不可SARG的WHERE子句。但記住覆蓋索引尤其自身的缺陷,如此經常產生寬索引會增加讀磁盤I/O。某些情況下,可以把不可SARG的WHERE子句重寫成可SARG的子句。例如:

WHERE SUBSTRING(firstname,1,1) = ‘m‘ 可以寫成:WHERE firstname like ‘m%‘

這兩個WHERE子句有相同的結果,但第一個是不可SARG的(因為使用了函數)將運行得慢些,而第二個是可SARG的,將運行得快些。

如果你不知道特定的WHERE子句是不是可SARG的,在查詢分析器裏檢查查詢執行計劃。這樣做,你能很快的知道查詢是使用了索引還是全表掃描來返回的數據。仔細分析,許多不可SARG的查詢能寫成可SARG的查詢。

i.避免或簡化排序

應當簡化或避免對大型表進行重復的排序。當能夠利用索引自動以適當的次序產生輸出時,優化器就避免了排序的步驟。以下是一些影響因素:

索引中不包括一個或幾個待排序的列;

group by或order by子句中列的次序與索引的次序不一樣;

排序的列來自不同的表。

為了避免不必要的排序,就要正確地增建索引,合理地合並數據庫表(盡管有時可能影響表的規範化,但相對於效率的提高是值得的)。如果排序不可避免,那麽應當試圖簡化它,如縮小排序的列的範圍等。

j.註意事務和鎖

事務是數據庫應用中和重要的工具,它有原子性、一致性、隔離性、持久性這四個屬性,很多操作我們都需要利用事務來保證數據的正確性。在使用事務中我們需要做到盡量避免死鎖、盡量減少阻塞。具體以下方面需要特別註意:

1)事務操作過程要盡量小,能拆分的事務要拆分開來。

2)事務操作過程不應該有交互,因為交互等待的時候,事務並未結束,可能鎖定了很多資源。

3)事務操作過程要按同一順序訪問對象,比如在一事務中要按順序更新A、B兩表,那麽在別的事務中就不要按B、A的順序去更新這兩個表。

4)提高事務中每個語句的效率,利用索引和其他方法提高每個語句的效率可以有效地減少整個事務的執行時間。

5)盡量不要指定鎖類型和索引,SQL SERVER允許我們自己指定語句使用的鎖類型和索引,但是一般情況下,SQL Server優化器選擇的鎖類型和索引是在當前數據量和查詢條件下是最優的,我們指定的可能只是在目前情況下更優,但是數據量和數據分布在將來是會變化的。

k.用存儲過程代替直接寫查詢語句

存儲過程為開發人員提供了很多好處,包括:

減少網絡流量和響應時間,提升應用程序性能。例如,通過網絡發送一個存儲過程調用,而不是發送500行的TSQL將更快,資源使用更少。當每次執行SQL時,都會執行解析SQL語句、估算索引的利用率、綁定變量、讀數據塊等等工作。

存儲過程執行計劃能夠重用,駐留在SQL Server內存的緩存裏,減少服務器開銷。

客戶端執行請求更有效率。例如,如果應用程序需要插入大量的二進制值到一個image數據列而不使用存儲過程,它必須轉化二進制為字符串(大小會增加一倍),然後發送給SQL Server。當SQL Server接收到後,它必須把字符串值轉回二進制格式。大量的浪費開銷。存儲過程能消除這個問題通過將應用程序傳給SQL Server的二進制格式作為參數,從而減少開銷提升性能。

存儲過程幫助提供代碼重用。雖然這些不直接提升應用程序的性能,通過減少代碼量和減少調試時間來提升開發人員的效率。

存儲過程能封裝邏輯。你能夠改變存儲過程代碼而不影響客戶端(假定你保持參數相同也不移除任何結果集的列)。這節約開發人員的時間。

存儲過程為你的數據提供更好的安全性。如果你僅使用存儲過程,你可以移除直接對表的SELECT、INSERT、UPDATE和DELETE權限從而強迫開發人員使用存儲過程訪問數據。這會節約DBA的時間。

l.不要在子查詢中使用count()求和執行存在性檢查

不要使用這樣的語句:

SELECT column_list FROM tablename WHERE 0 < (SELECT count(*) FROM table2 WHERE ..)

應使用這樣的語句代替:

SELECT column_list FROM tablename WHERE EXISTS(SELECT * FROM table2 WHERE ...)

當你使用count()時,SQL Server不知道你要做的是存在性檢查,它會計算所有匹配的值,要麽會執行全表掃描,要麽會掃描最小的非聚集索引。當你使用EXISTS時,SQL Server知道你要執行存在性檢查,當它發現第一個匹配的值時,就會返回TRUE,並停止查詢。

m.臨時表和表變量的用法

在復雜系統中,臨時表和表變量很難避免,關於臨時表和表變量的用法,需要註意:

1)如果語句很復雜,連接太多,可以考慮用臨時表和表變量分步完成。

2)如果需要多次用到一個大表的同一部分數據,考慮用臨時表和表變量暫存這部分數據。

3)如果需要綜合多個表的數據,形成一個結果,可以考慮用臨時表和表變量分步匯總這多個表的數據。

4)其他情況下,應該控制臨時表和表變量的使用。

5)關於臨時表和表變量的選擇,很多說法是表變量在內存,速度快,應該首選表變量,但是在實際使用中發現,這個選擇主要考慮需要放在臨時表的數據量,在數據量較多的情況下,臨時表的速度反而更快。

6)關於臨時表產生使用SELECT INTO和CREATE TABLE + INSERT INTO的選擇,我們做過測試,一般情況下,SELECT INTO會比CREATE TABLE + INSERT INTO的方法快很多,但是SELECT INTO會鎖定TEMPDB的系統表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,在多用戶並發環境下,容易阻塞其他進程,所以我的建議是,在並發系統中,盡量使用CREATE TABLE + INSERT INTO,而大數據量的單個語句使用中,使用SELECT INTO。

註意排序規則,用CREATE TABLE建立的臨時表,如果不指定字段的排序規則,會選擇TEMPDB的默認排序規則,而不是當前數據庫的排序規則。如果當前數據庫的排序規則和TEMPDB的排序規則不同,連接的時候可能會出現排序規則的沖突錯誤。一般可以在CREATE TABLE建立臨時表時指定字段的排序規則為DATABASE_DEFAULT來避免上述問題。

ps:SQL數據庫中臨時表、臨時變量和WITH AS關鍵詞創建“臨時表”的區別

1).with tempTableName as方法(05之後出現):

  with temptable as 其實並沒有建立臨時表,只是子查詢部分(subquery factoring),定義一個SQL片斷,該SQL片斷會被整個SQL語句所用到。有的時候,是為了讓SQL語句的可讀性更高些,也有可能是在UNION ALL的不同部分,作為提供數據的部分。特別對於UNION ALL比較有用。因為UNION ALL的每個部分可能相同,但是如果每個部分都去執行一遍的話,則成本太高,所以可以使用WITH AS短語,則只要執行一遍即可。

2).臨時表方法

  臨時表與永久表相似,只是它的創建是在Tempdb中,它只有在一個數據庫連接結束後或者由SQL命令DROP掉,才會消失,否則就會一直存在(臨時表一般被創建後,如果在執行的時候,沒有通過DROP Table的操作,第二次就不能再被創建)。臨時表在創建的時候都會產生SQL Server的系統日誌,雖它們在Tempdb中體現,是分配在內存中的,它們也支持物理的磁盤,但用戶在指定的磁盤裏看不到文件。

  臨時表分為本地和全局兩種,本地臨時表的名稱都是以“#”為前綴,只有在本地當前的用戶連接中才是可見的,當用戶從實例斷開連接時被刪除。全局臨時表的名稱都是以“##”為前綴,創建後對任何用戶都是可見的,當所有引用該表的用戶斷開連接時被刪除。

3).表變量方法

  表變量創建的語法類似於臨時表,區別就在於創建的時候,必須要為之命名。表變量是變量的一種,表變量也分為本地及全局的兩種,本地表變量的名稱都是以“@”為前綴,只有在本地當前的用戶連接中才可以訪問。全局的表變量的名稱都是以“@@”為前綴,一般都是系統的全局變量,像我們常用到的,如@@Error代表錯誤的號,@@RowCount代表影響的行數。

臨時表和表變量的區別:

  1)表變量是存儲在內存中的,當用戶在訪問表變量的時候,SQL Server是不產生日誌的,而在臨時表中是產生日誌的;

  2)在表變量中,是不允許有非聚集索引的;

  3)表變量是不允許有DEFAULT默認值,也不允許有約束;

  4)臨時表上的統計信息是健全而可靠的,但是表變量上的統計信息是不可靠的;

  5)臨時表中是有鎖的機制,而表變量中就沒有鎖的機制。

  臨時表和表變量的選擇:

  1)使用表變量主要需要考慮的就是應用程序對內存的壓力,如果代碼的運行實例很多,就要特別註意內存變量對內存的消耗。我們對於較小的數據或者是通過計算出來的推薦使用表變量。如果數據的結果比較大,在代碼中用於臨時計算,在選取的時候沒有什麽分組的聚合,就可以考慮使用表變量。

  2)一般對於大的數據結果,或者因為統計出來的數據為了便於更好的優化,我們就推薦使用臨時表,同時還可以創建索引,由於臨時表是存放在Tempdb中,一般默認分配的空間很少,需要對tempdb進行調優,增大其存儲的空間。

歡迎大家關註微信號,微信公眾號名稱:DotNet修煉寶典。 掃下面的二維碼或者收藏下面的二維碼關註吧(長按下面的二維碼圖片、並選擇識別圖中的二維碼)

技術分享圖片

.NET技術面試題系列(2) -sql server數據庫優化規範