SQL Server中的SQL語句優化與效率

不少人不知道SQL語句在SQL SERVER中是如何執行的,他們擔憂本身所寫的SQL語句會被SQL SERVER誤解。好比:數據庫

select * from table1 where name='zhangsan' and tID > 10000

和執行:服務器

select * from table1 where tID > 10000 and name='zhangsan'

  一些人不知道以上兩條語句的執行效率是否同樣,由於若是簡單的從語句前後上看,這兩個語句的確是不同,若是tID是一個聚合索引,那麼後一句僅僅從表的10000條之後的記錄中查找就好了;而前一句則要先從全表中查找看有幾個name='zhangsan'的,然後再根據限制條件條件tID>10000來提出查詢結果。
  事實上,這樣的擔憂是沒必要要的。SQL SERVER中有一個「查詢分析優化器」,它能夠計算出where子句中的搜索條件並肯定哪一個索引能縮小表掃描的搜索空間,也就是說,它能實現自動優化。
  雖然查詢優化器能夠根據where子句自動的進行查詢優化,但你們仍然有必要了解一下「查詢優化器」的工做原理,如非這樣,有時查詢優化器就會不按照您的本意進行快速查詢。
  在查詢分析階段,查詢優化器查看查詢的每一個階段並決定限制須要掃描的數據量是否有用。若是一個階段能夠被用做一個掃描參數(SARG),那麼就稱之爲可優化的,而且能夠利用索引快速得到所需數據。
  SARG的定義:用於限制搜索的一個操做,由於它一般是指一個特定的匹配,一個值得範圍內的匹配或者兩個以上條件的AND鏈接。形式以下:網絡

列名 操做符 <常數 或 變量> 或 <常數 或 變量> 操做符列名

列名能夠出如今操做符的一邊,而常數或變量出如今操做符的另外一邊。如:函數

Name='張三' 價格>5000 5000<價格 Name='張三' and 價格>5000

  若是一個表達式不能知足SARG的形式,那它就沒法限制搜索的範圍了,也就是SQL SERVER必須對每一行都判斷它是否知足WHERE子句中的全部條件。因此一個索引對於不知足SARG形式的表達式來講是無用的。
  介紹完SARG後,咱們來總結一下使用SARG以及在實踐中遇到的和某些資料上結論不一樣的經驗:性能

一、Like語句是否屬於SARG取決於所使用的通配符的類型優化

--如: name like '張%' --,這就屬於SARG --而: name like '%張' --,就不屬於SARG。

緣由是通配符%在字符串的開通使得索引沒法使用。spa

二、or 會引發全表掃描
  Name='張三' and 價格>5000 符號SARG,而:Name='張三' or 價格>5000 則不符合SARG。使用or會引發全表掃描。操作系統

三、非操做符、函數引發的不知足SARG形式的語句
  不知足SARG形式的語句最典型的狀況就是包括非操做符的語句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外還有函數。下面就是幾個不知足SARG形式的例子:code

ABS(價格)<5000 Name like '%三' --有些表達式,如: WHERE 價格*2>5000 --SQL SERVER也會認爲是SARG,SQL SERVER會將此式轉化爲: WHERE 價格>2500/2

但咱們不推薦這樣使用,由於有時SQL SERVER不能保證這種轉化與原始表達式是徹底等價的。排序

四、IN 的做用至關與OR

語句:

Select * from table1 where tid in (2,3) --和 Select * from table1 where tid=2 or tid=3

是同樣的,都會引發全表掃描,若是tid上有索引,其索引也會失效。

五、儘可能少用NOT

六、exists 和 in 的執行效率是同樣的
  不少資料上都顯示說,exists要比in的執行效率要高,同時應儘量的用not exists來代替not in。但事實上,我試驗了一下,發現兩者不管是前面帶不帶not,兩者之間的執行效率都是同樣的。由於涉及子查詢,咱們試驗此次用SQL SERVER自帶的pubs數據庫。運行前咱們能夠把SQL SERVER的statistics I/O狀態打開:

(1)

select title,price from titles where title_id in (select title_id from sales where qty>30)

該句的執行結果爲:

表 'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。

(2)

select title,price from titles   where exists (select * from sales   where sales.title_id=titles.title_id and qty>30)

第二句的執行結果爲:

表 'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。

咱們今後能夠看到用exists和用in的執行效率是同樣的。

七、用函數charindex()和前面加通配符%的LIKE執行效率同樣
  前面,咱們談到,若是在LIKE前面加上通配符%,那麼將會引發全表掃描,因此其執行效率是低下的。但有的資料介紹說,用函數charindex()來代替LIKE速度會有大的提高,經我試驗,發現這種說明也是錯誤的:

select gid,title,fariqi,reader from tgongwen    where charindex('刑偵支隊',reader)>0 and fariqi>'2004-5-5'

用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。

select gid,title,fariqi,reader from tgongwen    where reader like '%' + '刑偵支隊' + '%' and fariqi>'2004-5-5'

用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。

八、union並不絕對比or的執行效率高
  咱們前面已經談到了在where子句中使用or會引發全表掃描,通常的,我所見過的資料都是推薦這裏用union來代替or。事實證實,這種說法對於大部分都是適用的。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen    where fariqi='2004-9-16' or gid>9990000

用時:68秒。掃描計數 1,邏輯讀 404008 次,物理讀 283 次,預讀 392163 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' union select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000

用時:9秒。掃描計數 8,邏輯讀 67489 次,物理讀 216 次,預讀 7499 次。

看來,用union在一般狀況下比用or的效率要高的多。

但通過試驗,筆者發現若是or兩邊的查詢列是同樣的話,那麼用union則反倒和用or的執行速度差不少,雖然這裏union掃描的是索引,而or掃描的是全表。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen    where fariqi='2004-9-16' or fariqi='2004-2-5'

用時:6423毫秒。掃描計數 2,邏輯讀 14726 次,物理讀 1 次,預讀 7176 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' union select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-2-5'

用時:11640毫秒。掃描計數 8,邏輯讀 14806 次,物理讀 108 次,預讀 1144 次。

九、字段提取要按照「需多少、提多少」的原則,避免「select *」
  咱們來作一個試驗:

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用時:4673毫秒

select top 10000 gid,fariqi,title from tgongwen order by gid desc

用時:1376毫秒

select top 10000 gid,fariqi from tgongwen order by gid desc

用時:80毫秒

由此看來,咱們每少提取一個字段,數據的提取速度就會有相應的提高。提高的速度還要看您捨棄的字段的大小來判斷。

十、count(*)不比count(字段)慢
  某些資料上說:用*會統計全部列,顯然要比一個世界的列名效率低。這種說法實際上是沒有根據的。咱們來看:

select count(*) from Tgongwen

用時:1500毫秒

select count(gid) from Tgongwen 

用時:1483毫秒

select count(fariqi) from Tgongwen

用時:3140毫秒

select count(title) from Tgongwen

用時:52050毫秒

從以上能夠看出,若是用count(*)和用count(主鍵)的速度是至關的,而count(*)卻比其餘任何除主鍵之外的字段彙總速度要快,並且字段越長,彙總的速度就越慢。我想,若是用count(*), SQL SERVER可能會自動查找最小字段來彙總的。固然,若是您直接寫count(主鍵)將會來的更直接些。

十一、order by按彙集索引列排序效率最高
  咱們來看:(gid是主鍵,fariqi是聚合索引列):

select top 10000 gid,fariqi,reader,title from tgongwen

用時:196 毫秒。 掃描計數 1,邏輯讀 289 次,物理讀 1 次,預讀 1527 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc

用時:4720毫秒。 掃描計數 1,邏輯讀 41956 次,物理讀 0 次,預讀 1287 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用時:4736毫秒。 掃描計數 1,邏輯讀 55350 次,物理讀 10 次,預讀 775 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc

用時:173毫秒。 掃描計數 1,邏輯讀 290 次,物理讀 0 次,預讀 0 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc

用時:156毫秒。 掃描計數 1,邏輯讀 289 次,物理讀 0 次,預讀 0 次。

從以上咱們能夠看出,不排序的速度以及邏輯讀次數都是和「order by 彙集索引列」 的速度是至關的,但這些都比「order by 非彙集索引列」的查詢速度是快得多的。
  同時,按照某個字段進行排序的時候,不管是正序仍是倒序,速度是基本至關的。

十二、高效的TOP
  事實上,在查詢和提取超大容量的數據集時,影響數據庫響應時間的最大因素不是數據查找,而是物理的I/0操做。如:

select top 10 * from ( select top 10000 gid,fariqi,title from tgongwen where neibuyonghu='辦公室' order by gid desc) as a order by gid asc

這條語句,從理論上講,整條語句的執行時間應該比子句的執行時間長,但事實相反。由於,子句執行後返回的是10000條記錄,而整條語句僅返回10條語句,因此影響數據庫響應時間最大的因素是物理I/O操做。而限制物理I/O操做此處的最有效方法之一就是使用TOP關鍵詞了。TOP關鍵詞是SQL SERVER中通過系統優化過的一個用來提取前幾條或前幾個百分比數據的詞。經筆者在實踐中的應用,發現TOP確實很好用,效率也很高。但這個詞在另一個大型數據庫ORACLE中卻沒有,這不能說不是一個遺憾,雖然在ORACLE中能夠用其餘方法(如:rownumber)來解決。在之後的關於「實現千萬級數據的分頁顯示存儲過程」的討論中,咱們就將用到TOP這個關鍵詞。  到此爲止,咱們上面討論瞭如何實現從大容量的數據庫中快速地查詢出您所須要的數據方法。固然,咱們介紹的這些方法都是「軟」方法,在實踐中,咱們還要考慮各類「硬」因素,如:網絡性能、服務器的性能、操做系統的性能,甚至網卡、交換機等。

相關文章
相關標籤/搜索