SQLSERVER2008R2 索引建立的幾點建議

1、不要把聚集索引浪費在主鍵上,除非你只按主鍵查詢

       雖然SQL SERVER默認是在主鍵上建立聚集索引的,但實際應用中,這樣做比較浪費。通常,我們會在每個表中都建立一個ID列,以區分每條數據,並且這個ID列是自動增大的,步長一般爲1。此時,如果我們將這個列設爲主鍵,SQL SERVER會將此列默認爲聚集索引。這樣做有好處,就是可以讓您的數據在數據庫中按照ID進行物理排序,但這樣做實用價值不大。

       從我們前面談到的聚集索引的定義我們可以看出,使用聚集索引的最大好處就是能夠根據查詢要求,迅速縮小查詢範圍,避免全表掃描。在實際應用中,因爲ID號是自動生成的,我們並不知道每條記錄的ID 號,所以我們很難在實踐中用ID號來進行查詢。這就使讓ID號這個主鍵作爲聚集索引成爲一種資源浪費。聚集索引相對與非聚集索引的優勢是很明顯的,而每個表中只能有一個聚集索引的規則,這使得聚集索引變得更加寶貴,應該用在其他查詢頻率高的字段上。其次,讓每個ID號都不同的字段作爲聚集索引也不符合“大數目的不同值情況下不應建立聚合索引”規則;當然,這種情況只是針對用戶經常修改記錄內容,特別是索引項的時候會負作用,但對於查詢速度並沒有影響。

2、索引的建立要根據實際應用需求來進行

       並非是在任何字段上簡單地建立索引就能提高查詢速度。聚集索引建立的規則大致是“既不能絕大多數都相同,又不能只有極少數相同”。舉個例子,在公文表的收發日期字段上建立聚合索引是比較合適的。在政務系統中,我們每天都會收一些文件,這些文件的發文日期將會相同,在發文日期上建立聚合索引對性能的提升應該是相當大的。在羣集索引下,數據物理上按順序存於數據頁上,重複值也排列在一起,因而在範圍查找時,可以先找到這個範圍的起末點,且只在這個範圍內掃描數據頁,避免了大範圍掃描,提高了查詢速度。

另一個相反的例子:比如在僱員表的“性別”列上只有“男”與“女”兩個不同值,因此就完全沒必要建立索引。

3、在聚集索引中加入所有需要提高查詢速度的字段,形成複合索引

       根據一些實驗的結果,我們可以得出一些可供參考的結論:

       僅用複合聚集索引的起始列作爲查詢條件和同時用到複合聚集索引的全部列的查詢,速度是幾乎一樣的,甚至比後者還要快(在查詢結果集數目一樣的情況下);

       僅用複合聚集索引的非起始列作爲查詢條件的話,

       這個索引是不起任何作用的。

       複合聚集索引的所有列都用上,而且因爲查詢條件嚴格,查詢結果少的話,會形成“索引覆蓋”,性能可以達到最優。

       最重要的一點:無論是否經常使用複合聚合索引的其他列,其起始列一定要是使用最頻繁的列。

4.根據實踐得出的一些其他經驗,特定情況下有效

       用聚合索引比用不是聚合索引的主鍵速度快;

       用聚合索引比用一般的主鍵作order by速度快,特別是在小數據量情況;

      使用聚合索引內的時間段,搜索時間會按數據佔整個數據表的百分比成比例減少,而無論聚合索引使用了多少個;

       日期列不會因爲有分秒的輸入而減慢查詢速度;

       由於改變一個表的內容,將會引起索引的變化。頻繁的insert,update,delete語句將導致系統花費較大的代價進行索引更新,引起整體性能的下降。一般來講,在對查詢性能的要求高於對數據維護性能要求時,應該儘量使用索引,否則,就要慎重考慮一下付出的代價。在某些極端情況下,可先刪除索引,再對數據庫表更新大量數據,最後再重建索引,新建立的索引總是比較好用。

 
 

SQLSERVER2008R2正確使用索引

T1表 10000000萬條數據,(插入時間36分鐘,count(*)查詢19秒,空間佔用670M左右)

 

1.真正充分的利用索引
比如like '張%' 就是符合SARG(符合掃描參數)標準
而like '%張' 就不符合該標準

通配符%在字符串首字符的使用會導致索引無法使用,雖然實際應用中很難避免這樣用,但還是應該對這種現象有所瞭解,至少知道此種用法性能是很低下的。

 

**********************************************


2.“非”操作符不滿足SARG形式,使得索引無法使用
不滿足SARG形式的語句最典型的情況就是包括非操作符的語句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等。
如果使用not 或者 <>,最好轉換成別的方法,比如例子如下:

T1表 10000000萬條數據,構建如下:(插入時間36分鐘,count(*)查詢19秒,空間佔用670M左右)

DECLARE @i INT
SET @i = 1
WHILE @i<1000000
BEGIN
 INSERT INTO t1 VALUES ('zhang'+CONVERT(char(50), @i),'3.2',77);
 SET @i + 1;
END

三種查詢方式:

SELECT * FROM t1 WHERE id <>300000
SELECT * FROM t1 WHERE id NOT IN (300000)
SELECT * FROM t1 WHERE id >299999 AND id < 300001

在執行計劃中可以明顯看出,使用最後一種方式而不是前面兩種方式進行查詢。
網上是這麼說的,但自己做的試驗100W條數據,開銷計劃是一樣的。

 

*********************************************

 

3. 函數運算不滿足SARG形式,使得索引無法使用
例:下列SQL條件語句中的列都建有恰當的索引,但執行速度卻非常慢:

select * from record where substring(card_no,1,4)=′5378′(13秒)
select * from record where amount/30< 1000(11秒)
select * from record where convert(char(10),date,112)=′19991201′(10秒)

分析:

where子句中對列的任何操作結果都是在SQL運行時逐列計算得到的,因此它不得不進行全表掃描,而沒有使用該列上面的索引;如果這些結果在查詢編譯時就能得到,那麼就可以被SQL優化器優化,使用索引,避免表搜索,因此將SQL重寫成下面這樣:

select * from record where card_no like ′5378%′(< 1秒)
select * from record where amount < 1000*30(< 1秒)
select * from record where date= ′1999/12/01′ (< 1秒)

你會發現SQL明顯快很多

待測試.......

 

**********************************************

 

4.儘量不要對建立了索引的字段,作任何的直接處理

select * from employs where first_name + last_name ='beill cliton';

無法使用索引,改爲:

select * from employee where
first_name = substr('beill cliton',1,instr('beill cliton',' ')-1)
and
last_name = substr('beill cliton',instr('beill cliton',' ')+1)

則可以使用索引


***********************************************


5.不同類型的索引效能是不一樣的,應儘可能先使用效能高的
比如:數字類型的索引查找效率高於字符串類型,定長字符串char,nchar的索引效率高於變長字符串varchar,nvarchar的索引。
應該將
where username='張三' and age>20
改進爲
where age>20 and username='張三'
注意:此處,SQL的查詢分析優化功能可以做到自動重排條件順序,但還是建議預先手工排列好。


**************************************************


6.某些情況下IN 的作用與OR 相當 ,且都不能充分利用索引
例:表stuff有200000行,id_no上有非羣集索引,請看下面這個SQL:
select count(*) from stuff where id_no in(′0′,′1′) (23秒)
我們期望它會根據每個or子句分別查找,再將結果相加,這樣可以利用id_no上的索引;但實際上,它卻採用了"OR策略",即先取出滿足每個or子句的行,存入臨時數據庫的工作表中,再建立唯一索引以去掉重複行,最後從這個臨時表中計算結果。因此,實際過程沒有利用id_no 上索引,並且完成時間還要受tempdb數據庫性能的影響。
實踐證明,表的行數越多,工作表的性能就越差,當stuff有620000行時,執行時間會非常長!如果確定不同的條件不會產生大量重複值,還不如將or子句分開:

select count(*) from stuff where id_no=′0′
select count(*) from stuff where id_no=′1′

得到兩個結果,再用union作一次加法合算。因爲每句都使用了索引,執行時間會比較短,

select count(*) from stuff where id_no=′0′
union
select count(*) from stuff where id_no=′1′

從實踐效果來看,使用union在通常情況下比用or的效率要高的多,而exist關鍵字和in關鍵字在用法上類似,性能上也類似,都會產生全表掃描,效率比較低下,根據未經驗證的說法,exist可能比in要快些。


***************************************************


7.使用變通的方法提高查詢效率

  like關鍵字支持通配符匹配,但這種匹配特別耗時。例如:select * from customer where zipcode like “21_ _ _”,即使在zipcode字段上已建立了索引,在這種情況下也可能還是採用全表掃描方式。如果把語句改爲:select * from customer where zipcode >“21000”,在執行查詢時就會利用索引,大大提高速度。但這種變通是有限制的,不應引起業務意義上的損失,對於郵政編碼而言,zipcode like “21_ _ _” 和 zipcode >“21000” 意義是完全一致的。


*********************************************************人各有志,但富貴在天,人生允許彷徨,但不允許蹉跎.

 

8.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 次。

同時,按照某個字段進行排序的時候,無論是正序還是倒序,速度是基本相當的。

 

********************************************************


9.關於節省數據查詢系統開銷方面的措施
 (1)使用TOP儘量減少取出的數據量
 (2)字段提取要按照“需多少、提多少”的原則,避免“select *”
字段大小越大,數目越多,select所耗費的資源就越多,比如取int類型的字段就會比取char的快很多。我們每少提取一個字段,數據的提取速度就會有相應的提升。提升的幅度根據捨棄的字段的大小來判斷
 (3)count(*) 與 count(字段) 方法比較
 用count(*)和用 count(主鍵)的速度是相當的,而count(*)卻比其他任何除主鍵以外的字段彙總速度要快,而且字段越長,彙總速度就越慢。如果用 count(*), SQL SERVER會自動查找最小字段來彙總。當然,如果您直接寫count(主鍵)將會來的更直接些
 (4)有嵌套查詢時,儘可能在內層過濾掉數據
 如果一個列同時在主查詢和where子句中出現,很可能當主查詢中的列值改變之後,子查詢必須重新查詢一次。而且查詢嵌套層次越多,效率越低,因此應當儘量避免子查詢。如果子查詢不可避免,那麼要在子查詢中過濾掉儘可能多的行
 (5)多表關聯查詢時,需注意表順序,並儘可能早的過濾掉數據
 在使用Join進行多表關聯查詢時候,應該使用系統開銷最小的方案。連接條件要充份考慮帶有索引的表、行數多的表,並注意優化表順序;說的簡單一點,就是儘可能早的將之後要做關聯的數據量降下來。

 一般情況下,sqlserver 會對錶的連接作出自動優化。例如:
 select name,no from A
 join B on A. id=B.id
 join C on C.id=A.id
 where name='wang'
        儘管A表在From中先列出,然後纔是B,最後纔是C。但sql server可能會首先使用c表。它的選擇原則是相對於該查詢限制爲單行或少數幾行,就可以減少在其他表中查找的總數據量。絕大多數情況下,sql server 會作出最優的選擇,但如果你發覺某個複雜的聯結查詢速度比預計的要慢,就可以使用SET FORCEPLAN語句強制sql server按照表出現順序使用表。如上例加上:SET FORCEPLAN ON…….SET FORCEPLAN OFF 表的執行順序將會按照你所寫的順序執行。在查詢分析器中查看2種執行效率,從而選擇表的連接順序。SET FORCEPLAN的缺點是只能在存儲過程中使用

 
 
PS:數據庫的查詢優化技術 
 
數據庫系統是管理信息系統的核心,基於數據庫的聯機事務處理(OLTP)以及聯機分析處理(OLAP)是銀行、企業、政府等部門最爲重要的計算機應用之一。從大多數系統的應用實例來看,查詢操作在各種數據庫操作中所佔據的比重最大,而查詢操作所基於的SELECT語句在SQL語句中又是代價最大的語句。舉例來說,如果數據的量積累到一定的程度,比如一個銀行的賬戶數據庫表信息積累到上百萬甚至上千萬條記錄,全表掃描一次往往需要數十分鐘,甚至數小時。如果採用比全表掃描更好的查詢策略,往往可以使查詢時間降爲幾分鐘,由此可見查詢優化技術的重要性。 
筆者在應用項目的實施中發現,許多程序員在利用一些前端數據庫開發工具(如PowerBuilder、Delphi等)開發數據庫應用程序時,只注重用戶界面的華麗,並不重視查詢語句的效率問題,導致所開發出來的應用系統效率低下,資源浪費嚴重。因此,如何設計高效合理的查詢語句就顯得非常重要。本文以應用實例爲基礎,結合數據庫理論,介紹查詢優化技術在現實系統中的運用。 

分析問題 

許多程序員認爲查詢優化是DBMS(數據庫管理系統)的任務,與程序員所編寫的SQL語句關係不大,這是錯誤的。一個好的查詢計劃往往可以使程序性能提高數十倍。查詢計劃是用戶所提交的SQL語句的集合,查詢規劃是經過優化處理之後所產生的語句集合。DBMS處理查詢計劃的過程是這樣的:在做完查詢語句的詞法、語法檢查之後,將語句提交給DBMS的查詢優化器,優化器做完代數優化和存取路徑的優化之後,由預編譯模塊對語句進行處理並生成查詢規劃,然後在合適的時間提交給系統處理執行,最後將執行結果返回給用戶。在實際的數據庫產品(如Oracle、Sybase等)的高版本中都是採用基於代價的優化方法,這種優化能根據從系統字典表所得到的信息來估計不同的查詢規劃的代價,然後選擇一個較優的規劃。雖然現在的數據庫產品在查詢優化方面已經做得越來越好,但由用戶提交的SQL語句是系統優化的基礎,很難設想一個原本糟糕的查詢計劃經過系統的優化之後會變得高效,因此用戶所寫語句的優劣至關重要。系統所做查詢優化我們暫不討論,下面重點說明改善用戶查詢計劃的解決方案。  
解決問題 
下面以關係數據庫系統Informix爲例,介紹改善用戶查詢計劃的方法。 

1.合理使用索引 
索引是數據庫中重要的數據結構,它的根本目的就是爲了提高查詢效率。現在大多數的數據庫產品都採用IBM最先提出的ISAM索引結構。索引的使用要恰到好處,其使用原則如下: 
●在經常進行連接,但是沒有指定爲外鍵的列上建立索引,而不經常連接的字段則由優化器自動生成索引。 
●在頻繁進行排序或分組(即進行group by或order by操作)的列上建立索引。 
●在條件表達式中經常用到的不同值較多的列上建立檢索,在不同值少的列上不要建立索引。比如在僱員表的“性別”列上只有“男”與“女”兩個不同值,因此就無必要建立索引。如果建立索引不但不會提高查詢效率,反而會嚴重降低更新速度。 
●如果待排序的列有多個,可以在這些列上建立複合索引(compound index)。 
●使用系統工具。如Informix數據庫有一個tbcheck工具,可以在可疑的索引上進行檢查。在一些數據庫服務器上,索引可能失效或者因爲頻繁操作而使得讀取效率降低,如果一個使用索引的查詢不明不白地慢下來,可以試着用tbcheck工具檢查索引的完整性,必要時進行修復。另外,當數據庫表更新大量數據後,刪除並重建索引可以提高查詢速度。 

2.避免或簡化排序 
應當簡化或避免對大型表進行重複的排序。當能夠利用索引自動以適當的次序產生輸出時,優化器就避免了排序的步驟。以下是一些影響因素: 
●索引中不包括一個或幾個待排序的列; 
●group by或order by子句中列的次序與索引的次序不一樣; 
●排序的列來自不同的表。 
爲了避免不必要的排序,就要正確地增建索引,合理地合併數據庫表(儘管有時可能影響表的規範化,但相對於效率的提高是值得的)。如果排序不可避免,那麼應當試圖簡化它,如縮小排序的列的範圍等。 

3.消除對大型錶行數據的順序存取 
在嵌套查詢中,對錶的順序存取對查詢效率可能產生致命的影響。比如採用順序存取策略,一個嵌套3層的查詢,如果每層都查詢1000行,那麼這個查詢就要查詢10億行數據。避免這種情況的主要方法就是對連接的列進行索引。例如,兩個表:學生表(學號、姓名、年齡……)和選課表(學號、課程號、成績)。如果兩個表要做連接,就要在“學號”這個連接字段上建立索引。 
還可以使用並集來避免順序存取。儘管在所有的檢查列上都有索引,但某些形式的where子句強迫優化器使用順序存取。下面的查詢將強迫對orders表執行順序操作: 
SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008 
雖然在customer_num和order_num上建有索引,但是在上面的語句中優化器還是使用順序存取路徑掃描整個表。因爲這個語句要檢索的是分離的行的集合,所以應該改爲如下語句: 
SELECT * FROM orders WHERE customer_num=104 AND order_num>1001 
UNION 
SELECT * FROM orders WHERE order_num=1008 
這樣就能利用索引路徑處理查詢。 

4.避免相關子查詢 
一個列的標籤同時在主查詢和where子句中的查詢中出現,那麼很可能當主查詢中的列值改變之後,子查詢必須重新查詢一次。查詢嵌套層次越多,效率越低,因此應當儘量避免子查詢。如果子查詢不可避免,那麼要在子查詢中過濾掉儘可能多的行。 

5.避免困難的正規表達式 
MATCHES和LIKE關鍵字支持通配符匹配,技術上叫正規表達式。但這種匹配特別耗費時間。例如:SELECT * FROM customer WHERE zipcode LIKE “98_ _ _” 
即使在zipcode字段上建立了索引,在這種情況下也還是採用順序掃描的方式。如果把語句改爲SELECT * FROM customer WHERE zipcode >“98000”,在執行查詢時就會利用索引來查詢,顯然會大大提高速度。 
另外,還要避免非開始的子串。例如語句:SELECT * FROM customer WHERE zipcode[2,3] >“80”,在where子句中採用了非開始子串,因而這個語句也不會使用索引。
6.使用臨時表加速查詢 
把表的一個子集進行排序並創建臨時表,有時能加速查詢。它有助於避免多重排序操作,而且在其他方面還能簡化優化器的工作。例如: 
SELECT cust.name,rcvbles.balance,……other columns 
FROM cust,rcvbles 
WHERE cust.customer_id = rcvlbes.customer_id 
AND rcvblls.balance>0 
AND cust.postcode>“98000” 
ORDER BY cust.name 
如果這個查詢要被執行多次而不止一次,可以把所有未付款的客戶找出來放在一個臨時文件中,並按客戶的名字進行排序: 
SELECT cust.name,rcvbles.balance,……other columns 
FROM cust,rcvbles 
WHERE cust.customer_id = rcvlbes.customer_id 
AND rcvblls.balance>0 
ORDER BY cust.name 
INTO TEMP cust_with_balance 
然後以下面的方式在臨時表中查詢: 
SELECT * FROM cust_with_balance 
WHERE postcode>“98000” 
臨時表中的行要比主表中的行少,而且物理順序就是所要求的順序,減少了磁盤I/O,所以查詢工作量可以得到大幅減少。 
注意:臨時表創建後不會反映主表的修改。在主表中數據頻繁修改的情況下,注意不要丟失數據。 

7.用排序來取代非順序存取 
非順序磁盤存取是最慢的操作,表現在磁盤存取臂的來回移動。SQL語句隱藏了這一情況,使得我們在寫應用程序時很容易寫出要求存取大量非順序頁的查詢。 
有些時候,用數據庫的排序能力來替代非順序的存取能改進查詢。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章