SQL 優化SQL查詢:如何寫出高性能SQL語句

一、 首先要搞明白什麼叫執行計劃?html

執行計劃是數據庫根據SQL語句和相關表的統計信息做出的一個查詢方案,這個方案是由查詢優化器自動分析產生的,好比一條SQL語句若是用來從一個 10萬條記錄的表中查1條記錄,那查詢優化器會選擇「索引查找」方式,若是該表進行了歸檔,當前只剩下5000條記錄了,那查詢優化器就會改變方案,採用 「全表掃描」方式。程序員

可見,執行計劃並非固定的,它是「個性化的」。產生一個正確的「執行計劃」有兩點很重要:sql

(1)    SQL語句是否清晰地告訴查詢優化器它想幹什麼?數據庫

(2)    查詢優化器獲得的數據庫統計信息是不是最新的、正確的?併發

二、 統一SQL語句的寫法oracle

對於如下兩句SQL語句,程序員認爲是相同的,數據庫查詢優化器認爲是不一樣的。 高併發

  
select * from dual select * From dual

其實就是大小寫不一樣,查詢分析器就認爲是兩句不一樣的SQL語句,必須進行兩次解析。生成2個執行計劃。因此做爲程序員,應該保證相同的查詢語句在任何地方都一致,多一個空格都不行!oop

三、 不要把SQL語句寫得太複雜性能

我常常看到,從數據庫中捕捉到的一條SQL語句打印出來有2張A4紙這麼長。通常來講這麼複雜的語句一般都是有問題的。我拿着這2頁長的SQL語句去請教原做者,結果他說時間太長,他一時也看不懂了。可想而知,連原做者都有可能看糊塗的SQL語句,數據庫也同樣會看糊塗。優化

通常,將一個Select語句的結果做爲子集,而後從該子集中再進行查詢,這種一層嵌套語句仍是比較常見的,可是根據經驗,超過3層嵌套,查詢優化器就很容易給出錯誤的執行計劃。由於它被繞暈了。像這種相似人工智能的東西,終究比人的分辨力要差些,若是人都看暈了,我能夠保證數據庫也會暈的。

另外,執行計劃是能夠被重用的,越簡單的SQL語句被重用的可能性越高。而複雜的SQL語句只要有一個字符發生變化就必須從新解析,而後再把這一大堆垃圾塞在內存裏。可想而知,數據庫的效率會何等低下。

四、 使用「臨時表」暫存中間結果

簡化SQL語句的重要方法就是採用臨時表暫存中間結果,可是,臨時表的好處遠遠不止這些,將臨時結果暫存在臨時表,後面的查詢就在tempdb中了,這能夠避免程序中屢次掃描主表,也大大減小了程序執行中「共享鎖」阻塞「更新鎖」,減小了阻塞,提升了併發性能。

五、 OLTP系統SQL語句必須採用綁定變量 

  
select * from orderheader where changetime > ' 2010-10-20 00:00:01 ' select * from orderheader where changetime > ' 2010-09-22 00:00:01 '

 

以上兩句語句,查詢優化器認爲是不一樣的SQL語句,須要解析兩次。若是採用綁定變量

  
select * from orderheader where changetime > @chgtime

@chgtime變量能夠傳入任何值,這樣大量的相似查詢能夠重用該執行計劃了,這能夠大大下降數據庫解析SQL語句的負擔。一次解析,屢次重用,是提升數據庫效率的原則。

六、 綁定變量窺測

事物都存在兩面性,綁定變量對大多數OLTP處理是適用的,可是也有例外。好比在where條件中的字段是「傾斜字段」的時候。

「傾斜字段」指該列中的絕大多數的值都是相同的,好比一張人口調查表,其中「民族」這列,90%以上都是漢族。那麼若是一個SQL語句要查詢30歲的漢族人口有多少,那「民族」這列必然要被放在where條件中。這個時候若是採用綁定變量@nation會存在很大問題。

試想若是@nation傳入的第一個值是「漢族」,那整個執行計劃必然會選擇表掃描。而後,第二個值傳入的是「布依族」,按理說「布依族」佔的比例可能只有萬分之一,應該採用索引查找。可是,因爲重用了第一次解析的「漢族」的那個執行計劃,那麼第二次也將採用表掃描方式。這個問題就是著名的「綁定變量窺測」,建議對於「傾斜字段」不要採用綁定變量。

七、 只在必要的狀況下才使用begin tran

SQL Server中一句SQL語句默認就是一個事務,在該語句執行完成後也是默認commit的。其實,這就是begin tran的一個最小化的形式,比如在每句語句開頭隱含了一個begin tran,結束時隱含了一個commit。

有些狀況下,咱們須要顯式聲明begin tran,好比作「插、刪、改」操做須要同時修改幾個表,要求要麼幾個表都修改爲功,要麼都不成功。begin tran 能夠起到這樣的做用,它能夠把若干SQL語句套在一塊兒執行,最後再一塊兒commit。好處是保證了數據的一致性,但任何事情都不是天衣無縫的。Begin tran付出的代價是在提交以前,全部SQL語句鎖住的資源都不能釋放,直到commit掉。

可見,若是Begin tran套住的SQL語句太多,那數據庫的性能就糟糕了。在該大事務提交以前,必然會阻塞別的語句,形成block不少。

Begin tran使用的原則是,在保證數據一致性的前提下,begin tran 套住的SQL語句越少越好!有些狀況下能夠採用觸發器同步數據,不必定要用begin tran。

八、 一些SQL查詢語句應加上nolock

在SQL語句中加nolock是提升SQL Server併發性能的重要手段,在oracle中並不須要這樣作,由於oracle的結構更爲合理,有undo表空間保存「數據前影」,該數據若是在修改中還未commit,那麼你讀到的是它修改以前的副本,該副本放在undo表空間中。這樣,oracle的讀、寫能夠作到互不影響,這也是oracle 廣受稱讚的地方。SQL Server 的讀、寫是會相互阻塞的,爲了提升併發性能,對於一些查詢,能夠加上nolock,這樣讀的時候能夠容許寫,但缺點是可能讀到未提交的髒數據。使用 nolock有3條原則。

(1)    查詢的結果用於「插、刪、改」的不能加nolock !

(2)    查詢的表屬於頻繁發生頁分裂的,慎用nolock !

(3)    使用臨時表同樣能夠保存「數據前影」,起到相似oracle的undo表空間的功能,

能採用臨時表提升併發性能的,不要用nolock 。

九、 彙集索引沒有建在表的順序字段上,該表容易發生頁分裂

好比訂單表,有訂單編號orderid,也有客戶編號contactid,那麼彙集索引應該加在哪一個字段上呢?對於該表,訂單編號是順序添加的,若是在orderid上加彙集索引,新增的行都是添加在末尾,這樣不容易常常產生頁分裂。然而,因爲大多數查詢都是根據客戶編號來查的,所以,將彙集索引加在contactid上纔有意義。而contactid對於訂單表而言,並不是順序字段。

好比「張三」的「contactid」是001,那麼「張三」的訂單信息必須都放在這張表的第一個數據頁上,若是今天「張三」新下了一個訂單,那該訂單信息不能放在表的最後一頁,而是第一頁!若是第一頁放滿了呢?很抱歉,該表全部數據都要日後移動爲這條記錄騰地方。

SQL Server的索引和Oracle的索引是不一樣的,SQL Server的彙集索引其實是對錶按照彙集索引字段的順序進行了排序,至關於oracle的索引組織表。SQL Server的彙集索引就是表自己的一種組織形式,因此它的效率是很是高的。也正由於此,插入一條記錄,它的位置不是隨便放的,而是要按照順序放在該放的數據頁,若是那個數據頁沒有空間了,就引發了頁分裂。因此很顯然,彙集索引沒有建在表的順序字段上,該表容易發生頁分裂。

曾經碰到過一個狀況,一位哥們的某張表重建索引後,插入的效率大幅降低了。估計狀況大概是這樣的。該表的彙集索引可能沒有建在表的順序字段上,該表常常被歸檔,因此該表的數據是以一種稀疏狀態存在的。好比張三下過20張訂單,而最近3個月的訂單隻有5張,歸檔策略是保留3個月數據,那麼張三過去的 15張訂單已經被歸檔,留下15個空位,能夠在insert發生時從新被利用。在這種狀況下因爲有空位能夠利用,就不會發生頁分裂。可是查詢性能會比較低,由於查詢時必須掃描那些沒有數據的空位。

重建彙集索引後狀況改變了,由於重建彙集索引就是把表中的數據從新排列一遍,原來的空位沒有了,而頁的填充率又很高,插入數據常常要發生頁分裂,因此性能大幅降低。

對於彙集索引沒有建在順序字段上的表,是否要給與比較低的頁填充率?是否要避免重建彙集索引?是一個值得考慮的問題!

十、加nolock後查詢常常發生頁分裂的表,容易產生跳讀或重複讀

加nolock後能夠在「插、刪、改」的同時進行查詢,可是因爲同時發生「插、刪、改」,在某些狀況下,一旦該數據頁滿了,那麼頁分裂不可避免,而此時nolock的查詢正在發生,好比在第100頁已經讀過的記錄,可能會由於頁分裂而分到第101頁,這有可能使得nolock查詢在讀101頁時重複讀到該條數據,產生「重複讀」。同理,若是在100頁上的數據還沒被讀到就分到99頁去了,那nolock查詢有可能會漏過該記錄,產生「跳讀」。

上面提到的哥們,在加了nolock後一些操做出現報錯,估計有可能由於nolock查詢產生了重複讀,2條相同的記錄去插入別的表,固然會發生主鍵衝突。

十一、使用like進行模糊查詢時應注意

有的時候會須要進行一些模糊查詢好比

  
select * from contact where username like % yue %

關鍵詞%yue%,因爲yue前面用到了「%」,所以該查詢必然走全表掃描,除非必要,不然不要在關鍵詞前加%,

十二、數據類型的隱式轉換對查詢效率的影響

sql server2000的數據庫,咱們的程序在提交sql語句的時候,沒有使用強類型提交這個字段的值,由sql server 2000自動轉換數據類型,會致使傳入的參數與主鍵字段類型不一致,這個時候sql server 2000可能就會使用全表掃描。Sql2005上沒有發現這種問題,可是仍是應該注意一下。

1三、SQL Server 錶鏈接的三種方式

(1) Merge Join

(2) Nested Loop Join

(3) Hash Join

SQL Server 2000只有一種join方式——Nested Loop Join,若是A結果集較小,那就默認做爲外表,A中每條記錄都要去B中掃描一遍,實際掃過的行數至關於A結果集行數x B結果集行數。因此若是兩個結果集都很大,那Join的結果很糟糕。

SQL Server 2005新增了Merge Join,若是A表和B表的鏈接字段正好是彙集索引所在字段,那麼表的順序已經排好,只要兩邊拼上去就好了,這種join的開銷至關於A表的結果集行數加上B表的結果集行數,一個是加,一個是乘,可見merge join 的效果要比Nested Loop Join好多了。

若是鏈接的字段上沒有索引,那SQL2000的效率是至關低的,而SQL2005提供了Hash join,至關於臨時給A,B表的結果集加上索引,所以SQL2005的效率比SQL2000有很大提升,我認爲,這是一個重要的緣由。

總結一下,在錶鏈接時要注意如下幾點:

(1)    鏈接字段儘可能選擇彙集索引所在的字段

(2)    仔細考慮where條件,儘可能減少A、B表的結果集

(3)    若是不少join的鏈接字段都缺乏索引,而你還在用SQL Server 2000,趕忙升級吧。

本文連接:http://www.cnblogs.com/ATree/archive/2011/02/13/sql_optimize_1.html

相關文章
相關標籤/搜索