下圖是我結合本身的經驗以及蒐集整理的數據庫優化相關內容的思惟導圖,若是圖片不清楚,能夠在瀏覽器中右鍵,在新窗口中查看(Chrome)或者查看圖像(FireFox)。html
在編寫T-SQL的時候,會使用不少功能相似的關鍵字,好比COUNT
和EXISTS
、IN
和BETWEEN AND
等,咱們每每會根據需求直奔主題地來編寫查詢腳本,完成需求要求實現的業務邏輯便可,可是,咱們編寫的腳本中卻存在着不少的可優化的空間。數據庫
不要在子查詢中使用COUNT()
執行存在性檢查,不要使用相似於以下這樣的語句:瀏覽器
SELECT COLUMN_LIST FROM TABLENAME WHERE 0 < (SELECT COUNT(*) FROM TABLE2 WHERE ..)
緩存
而應該採用這樣的語句代替:服務器
SELECT COLUMN_LIST FROM TABLENAME WHERE EXISTS(SELECT
微信COLUMN_LIST
FROM TABLE2 WHERE ...)
當你使用COUNT()
時,SQL SERVER不知道你要作的是存在性檢查,它會計算全部匹配的值,要麼會執行全表掃描,要麼會掃描最小的非彙集索引。當你使用EXISTS
時,SQL SERVER知道你要執行存在性檢查,當它發現第一個匹配的值時,就會返回TRUE
,並中止查詢。此外,不少時候用EXISTS
代替IN
是一個好的選擇,例如:網絡
SELECT NUM FROM A WHERE NUM IN (SELECT NUM FROM B)
可使用SELECT NUM FROM A WHERE EXISTS (SELECT 1 FROM B WHERE NUM=A.NUM)
進行替代。併發
絕大多數狀況下,不要用 *
來代替查詢返回的字段列表,用 *
的好處是代碼量少,就算是表結構或視圖的列發生變化,編寫的查詢SQL語句也不用變,都返回全部的字段。但數據庫服務器在解析時,若是碰到 *
,則會先分析表的結構,而後把表的全部字段名再羅列出來,這就增長了分析的時間。另外一個問題是,SELECT *
可能包含了不須要的列,增長了網絡流量。若是在視圖建立中使用了SELECT *
,在後期若是有對視圖基表的表結構進行了更改,當查詢視圖時,可能會生成意外結果,除非重建視圖或利用SP_REFRESHVIEW
更新視圖的元數據。函數
DISTINCT
子句僅在特定功能的時候使用,即從記錄集中排除重複記錄的時候。這是由於DISTINCT
子句先獲取結果,進行排序集而後再去重,這樣增長了SQL SERVER資源的消耗。在實際的業務中,若是你已經預先知道SELECT
語句將從不返回重複記錄,那麼使用DISTINCT
語句是對SQL SERVER資源沒必要要的浪費。固然,若是是符合特定的業務場景,是能夠酌情使用的。工具
許多人沒徹底理解UNION
和UNION ALL
是怎樣工做的,所以,結果浪費了大量沒必要要的SQL Server資源。當使用UNION
時,它至關於在結果集上執行SELECT DISTINCT
。換句話說,UNION
將聯合兩個相相似的記錄集,而後搜索重複的記錄並排除。若是這是你的目的,那麼使用UNION
是正確的。但若是你使用UNION
聯合的兩個記錄集自己就沒有重複記錄,那麼使用UNION
會浪費資源,由於它要尋找重複記錄,即便你肯定它們不存在。總而言之,聯合無重複的結果集採用UNION ALL
,聯合存在重複記錄的採用UNION
。對於WITH TEMP TABLENAME AS
,其實並無創建臨時表,只是子查詢部分(SUBQUERY FACTORING
),定義一個SQL片段,該SQL片段會被整個SQL語句所用到。有的時候,是爲了讓SQL語句的可讀性更高些,也有多是在UNION ALL
的不一樣部分,做爲提供數據的部分。特別對於UNION ALL
比較有用。由於UNION ALL
的每一個部分可能相同,可是若是每一個部分都去執行一遍的話成本過高,因此可使用WITH AS
短語,則只要執行一遍便可。
缺省地,每次執行SQL語句時,一個消息會從服務端發給客戶端以顯示SQL語句影響的行數。這些信息對客戶端來講不多有用,甚至有些客戶端會把這些信息當成錯誤信息處理。經過關閉這個缺省值,你能減小在服務端和客戶端的網絡流量,幫助全面提高服務器和應用程序的性能。爲了關閉存儲過程級的這個特色,在每一個存儲過程的開頭包含SET NOCOUNT ON
語句。一樣,爲減小在服務端和客戶端的網絡流量,生產環境中應該去掉存儲過程當中那些在調試過程當中使用的SELECT
和PRINT
語句。
當在SQL語句中鏈接多個表時,能夠將表名或別名加到每一個COLUMN
前面,這樣能夠有效地減小解析的時間並減小那些由COLUMN
歧義引發的語法錯誤。例如:
SELECT COLUMN_A,COLUMN_B FROM TABLE1 T1 INNER JOIN TABLE2 T2 ON T1.ID = T2.UID
,其中COLUMN_A
是TABLE1
的數據列,COLUMN_B
是TABLE2
的數據列,這並不妨礙查詢的進行,可是改爲下列語句是否是更好呢?SELECT T1.COLUMN_A,T2.COLUMN_B FROM TABLE T1 INNER JOIN TABLE T2 ON T1.ID = T2.UID
關於索引,下圖展現出了索引的直觀結構:
索引按照索引的類型能夠分爲彙集索引和非彙集索引,一張數據表只能存在一個彙集索引,但能夠創建若干非彙集索引,彙集索引一般是創建在主鍵上,固然主鍵上不必定須要強制創建彙集索引。關於索引的實現原理能夠參考這篇數據庫索引的實現原理篇,以及創建索引的通常依據。對於彙集索引而言,表中存儲的數據按照索引的順序存儲,即邏輯順序決定了表中相應行的物理順序。對於非彙集索引,通常考慮在下列情形下使用非彙集索引:使用JOIN
的條件字段、使用GROUP BY
的字段、徹底匹配的WHERE
條件字段、外鍵字段等等。索引是有900字節大小限制的,所以不要在超長字段上創建索引,索引字段的總字節數不要超過900字節,不然插入的數據達到900字節時會報錯。另外,並非全部索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引,如一表中有字段Gender
,Male
、Female
幾乎各一半,那麼即便在Gender
上建了索引也對查詢效率起不了做用。索引並非越多越好,索引當然能夠提升查詢效率,但同時也下降了插入數據及更新數據的效率,由於插入或更新數據時有可能會重建索引,因此在創建索引時須要慎重考慮,視具體狀況而定。總之,要根據實際的業務情景合理地爲數據表創建索引。
存儲過程是數據庫中的一個重要對象。存儲過程其實是對一些SQL腳本的有邏輯地組合而造成的,是一組爲了完成特定功能的SQL 語句集。存儲在數據庫中,通過第一次編譯後再次調用不須要再次編譯,因此使用存儲過程可提升數據庫執行速度,用戶經過指定存儲過程的名字並給出參數(若是該存儲過程帶有參數)來執行它。存儲過程執行計劃可以重用,駐留在SQL SERVER內存的緩存裏,減小服務器開銷。當業務相對複雜的時候,能夠將該業務封裝成一個存儲過程存儲在數據庫服務器,能夠大大下降網絡流量的傳輸,提升性能。例如,經過網絡發送一個存儲過程調用,而不是發送500行的T-SQL,這樣速度會更快,資源佔用更少,有效地避免了每次執行SQL時,都會執行解析SQL語句、估算索引的利用率、綁定變量、讀取數據塊等工做。存儲過程可有效地下降數據庫鏈接次數,當對數據庫進行復雜操做時(如對多個表進行 Update,Insert,Query,Delete操做時),可將該複雜操做用存儲過程封裝起來與數據庫提供的事務處理結合一塊兒使用。這些操做,若是用程序來完成,就變成了一條條的 SQL 語句,可能要屢次鏈接數據庫。而採用成存儲過程,只須要鏈接一次數據庫就能夠了。
事務是數據庫應用中重要的工具,它具備原子性、一致性、隔離性、持久性這四個屬性,不少操做咱們都須要利用事務來保證數據的正確性。在使用事務中咱們須要作到儘可能避免死鎖、儘可能減小阻塞。開發過程當中,能夠經過如下幾種方式來避免問題的產生:事務操做過程要儘可能小,能拆分的事務要拆分開來,在更細的粒度上應用事務;事務操做過程當中不該該有交互,由於交互等待的時候,事務並未結束,可能鎖定了不少資源; 事務操做過程要按同一順序訪問對象,好比在某一事務中要按順序更新A、B兩表,那麼在其餘的事務中就不要按B、A的順序去更新這兩個表。我在實際工做中就遇到過這種問題(以下圖所示),因爲在事務中須要同時更新主表和子表,子表的數據更新後主表彙總數據,可是更新兩個表的時候,順序不一致,因爲事務的原子性,須要在同一事務中完成兩表的更新操做,這就造成了Transaction A須要的資源(子表B)被Transaction B佔據着,Transaction B須要的資源(主表A)被Transaction A佔據着,致使表被鎖住,形成了死鎖,後來對錶的更新順序進行了調整,解決了這個問題。儘可能不要指定鎖類型和索引,SQL SERVER容許咱們本身指定語句使用的鎖類型和索引,可是通常狀況下,SQL SERVER優化器選擇的鎖類型和索引是在當前數據量和查詢條件下是最優的,咱們指定的可能只是在目前狀況下更優,可是數據量和數據分佈在未來是會變化的。
下面是百度百科對SARG的解釋:
SARG (Searchable Arguments)操做,用於限制搜索的一個操做,它一般是指一個特定的匹配,一個值的範圍內的匹配或者兩個以上條件的AND鏈接。
SARG來源於Search Argument
(搜索參數)的首字母拼成的SARG,它是指WHERE
子句裏,列和常量的比較。若是WHERE
子句是SARGABLE(可SARG的),這意味着它能利用索引加速查詢的完成。若是WHERE
子句不是可SARG的,這意味着WHERE
子句不能利用索引(或至少部分不能利用),執行的是全表或索引掃描,這會引發查詢的性能降低。
在WHERE
子句中,能夠SARG的搜索條件包含如下如:包含如下操做符=
、>
、<
、>=
、<=
、BETWEEN
及部分狀況下的LIKE
(通配符在查詢關鍵字以後,如LIKE 'A%'
)
在WHERE
子句中,不可SARG的搜索條件如:IS NULL
, <>
, !=
, !>
, !<
, NOT
, NOT EXISTS
, NOT IN
, NOT LIKE
和LIKE '%500'
,一般(但不老是)會阻止查詢優化器使用索引執行搜索。另外在列上使用包括函數的表達式、兩邊都使用相同列的表達式、或和一個列(不是常量)比較的表達式,都是不可SARG的。並非每個不可SARG的WHERE
子句都註定要全表掃描。若是WHERE
子句包括兩個可SARG和一個不可SARG的子句,那麼至少可SARG的子句能使用索引(若是存在的話)幫助快速訪問數據。
大多數狀況下,若是表上有包括查詢裏全部SELECT
、JOIN
、WHERE
子句用到的列的覆蓋索引,那麼覆蓋索引可以代替全表掃描去返回查詢的數據,即便它有不可SARG的WHERE
子句。某些狀況下,能夠把不可SARG的WHERE
子句重寫成可SARG的子句。例如:
WHERE SUBSTRING(FirstName,1,1) = 'M'
能夠寫成:WHERE
這兩個FirstName
LIKE 'M%'WHERE
子句有相同的結果,但第一個是不可SARG的(由於使用了函數)將運行得慢些,而第二個是可SARG的,將運行得快些。若是你不知道特定的WHERE
子句是否是可SARG的,能夠在查詢分析器裏檢查查詢執行計劃。這樣作,你能很快地知道查詢是使用了索引仍是全表掃描來返回的數據。仔細分析,許多不可SARG的查詢能寫成可SARG的查詢,從而實現性能的優化和提高。
查詢條件中使用了不等於操做符(<>
, !=
)的SELECT
語句執行效率較低,由於不等於操做符會限制索引,引發全表掃描,即便被比較的字段上有索引,這時能夠經過把不等於操做符改爲OR,可使用索引,從而避免全表掃描。例如, 能夠把SELECT TOP 100 AGE FROM TABLE WHERE AGE <> 25
改寫爲SELECT TOP 1000 AGE FROM TABLE WHERE AGE > 25 OR AGE < 25
應當儘可能避免在WHERE
子句中對字段進行函數操做,這將致使引擎放棄使用索引而進行全表掃描。例如:SELECT ID FROM TABLE WHERE SUBSTRING(NAME, 1, 3) = 'ABC'
在複雜系統中,若是業務是以存儲過程的方式組織的,那麼中間必然會產生一些臨時查詢出的數據,此時臨時表和表變量很難避免,關於臨時表和表變量的用法,須要注意的是,若是語句很複雜,鏈接太多,能夠考慮用臨時表和表變量分步完成,將須要的結果集存儲在臨時表或表變量中,便於複用;一樣地,若是須要屢次用到一個大表的同一部分數據,考慮用臨時表和表變量暫存這部分數據; 若是須要綜合多個表的數據,造成一個結果集,能夠考慮用臨時表和表變量分步彙總出這多個表的數據;其餘狀況下,應該控制臨時表和表變量的使用。另外,在臨時表完成自身功能後,要顯式地刪除臨時表,先TRUNCATE TABLE
,而後DROP TABLE
,以免資源的佔用。關於臨時表和表變量的選擇,不少說法是表變量儲存在內存,速度快,應該首選表變量,可是在實際使用中發現,這個選擇主要是考慮須要放在臨時表中的數據量,在數據量較多的狀況下,臨時表的速度反而更快。關於臨時表的建立,使用SELECT INTO
和CREATE TABLE
+ INSERT INTO
的選擇,咱們作過測試,通常狀況下,SELECT INTO
會比CREATE TABLE
+ INSERT INTO
的方法快不少,可是SELECT INTO
會鎖定TEMPDB
的系統表SYSOBJECTS
、SYSINDEXES
、SYSCOLUMNS
,在多用戶併發環境下,容易阻塞其餘進程,因此建議在併發系統中,儘可能使用CREATE TABLE
+ INSERT INTO
,而大數據量的單個語句使用中,使用SELECT INTO
。
臨時表和表變量是有區別的,表變量是存儲在內存中的,當用戶在訪問表變量的時候,SQL SERVER是不產生日誌的,而在臨時表中是產生日誌的;在表變量中,是不容許有非彙集索引的;表變量是不容許有DEFAULT
默認值,也不容許有約束;臨時表上的統計信息是健全而可靠的,可是表變量上的統計信息是不可靠的;臨時表中是有鎖的機制,而表變量中就沒有鎖的機制。瞭解兩者的區別,能夠針對特定場景選擇最優方案,使用表變量主要須要考慮的就是應用程序對內存的壓力,若是代碼的運行實例不少,就要特別注意內存變量對內存的消耗。對於較小的數據或者是經過計算出來的數據推薦使用表變量。若是數據的結果比較大,在代碼中用於臨時計算,在選取的時候沒有什麼分組或聚合,也能夠考慮使用表變量。通常對於大的數據結果集,或者由於統計出來的數據爲了便於更好的優化,咱們就推薦使用臨時表,同時還能夠建立索引,因爲臨時表是存放在Tempdb中,通常默認分配的空間不多,須要對Tempdb進行調優,增大其存儲的空間。
本篇博文對經常使用的數據庫優化方法進行了簡單的梳理和總結,若是文中有不正確或者不恰當的地方,歡迎提出質疑,共同討論共同進步,若是您有什麼更好的優化方案,能夠在評論區討論,我會把這些優化方案補充進來。若是您以爲有幫助,點個贊喲~
做者:悠揚的牧笛
博客地址:http://www.cnblogs.com/xhb-bky-blog/p/9051380.html
聲明:本博客原創文字只表明本人工做中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關係。非商業,未受權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。若是您以爲文章對您有幫助,能夠【打賞】博主或點擊文章右下角【推薦】一下。您的鼓勵是博主堅持原創和持續寫做的最大動力!