一垄懂、數(shù)據(jù)庫結(jié)構(gòu)的設(shè)計(jì)
?如果不能設(shè)計(jì)一個(gè)合理的數(shù)據(jù)庫模型,不僅會增加客戶端和服務(wù)器段程序的編程和維護(hù)的難度墙杯,而且將會影響系統(tǒng)實(shí)際運(yùn)行的性能仲墨。所以,在一個(gè)系統(tǒng)開始實(shí)施之前疏遏,完備的數(shù)據(jù)庫模型的設(shè)計(jì)是必須的脉课。
?在一個(gè)系統(tǒng)分析救军、設(shè)計(jì)階段,因?yàn)閿?shù)據(jù)量較小倘零,負(fù)荷較低唱遭。我們往往只注意到功能的實(shí)現(xiàn),而很難注意到性能的薄弱之處视事,等到系統(tǒng)投入實(shí)際運(yùn)行一段時(shí)間后胆萧,才發(fā)現(xiàn)系統(tǒng)的性能在降低,這時(shí)再來考慮提高系統(tǒng)性能則要花費(fèi)更多的人力物力俐东,而整個(gè)系統(tǒng)也不可避免的形成了一個(gè)打補(bǔ)丁工程跌穗。
?所以在考慮整個(gè)系統(tǒng)的流程的時(shí)候,我們必須要考慮虏辫,在高并發(fā)大數(shù)據(jù)量的訪問情況下蚌吸,我們的系統(tǒng)會不會出現(xiàn)極端的情況。(例如:對外統(tǒng)計(jì)系統(tǒng)在7月16日出現(xiàn)的數(shù)據(jù)異常的情況砌庄,并發(fā)大數(shù)據(jù)量的的訪問造成羹唠,數(shù)據(jù)庫的響應(yīng)時(shí)間不能跟上數(shù)據(jù)刷新的速度造成。具體情況是:在日期臨界時(shí)(00:00:00)娄昆,判斷數(shù)據(jù)庫中是否有當(dāng)前日期的記錄佩微,沒有則插入一條當(dāng)前日期的記錄。在低并發(fā)訪問的情況下萌焰,不會發(fā)生問題哺眯,但是當(dāng)日期臨界時(shí)的訪問量相當(dāng)大的時(shí)候,在做這一判斷的時(shí)候扒俯,會出現(xiàn)多次條件成立奶卓,則數(shù)據(jù)庫里會被插入多條當(dāng)前日期的記錄,從而造成數(shù)據(jù)錯(cuò)誤撼玄。)夺姑,數(shù)據(jù)庫的模型確定下來之后,我們有必要做一個(gè)系統(tǒng)內(nèi)數(shù)據(jù)流向圖掌猛,分析可能出現(xiàn)的瓶頸盏浙。
為了保證數(shù)據(jù)庫的一致性和完整性,在邏輯設(shè)計(jì)的時(shí)候往往會設(shè)計(jì)過多的表間關(guān)聯(lián)荔茬,盡可能的降低數(shù)據(jù)的冗余只盹。(例如用戶表的地區(qū),我們可以把地區(qū)另外存放到一個(gè)地區(qū)表中)如果數(shù)據(jù)冗余低兔院,數(shù)據(jù)的完整性容易得到保證,提高了數(shù)據(jù)吞吐速度站削,保證了數(shù)據(jù)的完整性坊萝,清楚地表達(dá)數(shù)據(jù)元素之間的關(guān)系。而對于多表之間的關(guān)聯(lián)查詢(尤其是大數(shù)據(jù)表)時(shí),其性能將會降低十偶,同時(shí)也提高了客戶端程序的編程難度菩鲜,因此,物理設(shè)計(jì)需折衷考慮惦积,根據(jù)業(yè)務(wù)規(guī)則接校,確定對關(guān)聯(lián)表的數(shù)據(jù)量大小、數(shù)據(jù)項(xiàng)的訪問頻度狮崩,對此類數(shù)據(jù)表頻繁的關(guān)聯(lián)查詢應(yīng)適當(dāng)提高數(shù)據(jù)冗余設(shè)計(jì)但增加了表間連接查詢的操作蛛勉,也使得程序的變得復(fù)雜,為了提高系統(tǒng)的響應(yīng)時(shí)間睦柴,合理的數(shù)據(jù)冗余也是必要的诽凌。設(shè)計(jì)人員在設(shè)計(jì)階段應(yīng)根據(jù)系統(tǒng)操作的類型、頻度加以均衡考慮坦敌。
?另外侣诵,最好不要用自增屬性字段作為主鍵與子表關(guān)聯(lián)。不便于系統(tǒng)的遷移和數(shù)據(jù)恢復(fù)狱窘。對外統(tǒng)計(jì)系統(tǒng)映射關(guān)系丟失(******************)杜顺。
?原來的表格必須可以通過由它分離出去的表格重新構(gòu)建。使用這個(gè)規(guī)定的好處是蘸炸,你可以確保不會在分離的表格中引入多余的列躬络,所有你創(chuàng)建的表格結(jié)構(gòu)都與它們的實(shí)際需要一樣大。應(yīng)用這條規(guī)定是一個(gè)好習(xí)慣幻馁,不過除非你要處理一個(gè)非常大型的數(shù)據(jù)洗鸵,否則你將不需要用到它。(例如一個(gè)通行證系統(tǒng)仗嗦,我可以將USERID膘滨,USERNAME,USERPASSWORD稀拐,單獨(dú)出來作個(gè)表火邓,再把USERID作為其他表的外鍵)
表的設(shè)計(jì)具體注意的問題:
1、數(shù)據(jù)行的長度不要超過8020字節(jié)德撬,如果超過這個(gè)長度的話在物理頁中這條數(shù)據(jù)會占用兩行從而造成存儲碎片铲咨,降低查詢效率。
?2蜓洪、能夠用數(shù)字類型的字段盡量選擇數(shù)字類型而不用字符串類型的(電話號碼)纤勒,這會降低查詢和連接的性能,并會增加存儲開銷隆檀。這是因?yàn)橐嬖谔幚聿樵兒瓦B接回逐個(gè)比較字符串中每一個(gè)字符摇天,而對于數(shù)字型而言只需要比較一次就夠了粹湃。
?3、對于不可變字符類型char和可變字符類型varchar 都是8000字節(jié),char查詢快泉坐,但是耗存儲空間为鳄,varchar查詢相對慢一些但是節(jié)省存儲空間。在設(shè)計(jì)字段的時(shí)候可以靈活選擇腕让,例如用戶名孤钦、密碼等長度變化不大的字段可以選擇CHAR,對于評論等長度變化大的字段可以選擇VARCHAR纯丸。
?4偏形、字段的長度在最大限度的滿足可能的需要的前提下,應(yīng)該盡可能的設(shè)得短一些液南,這樣可以提高查詢的效率壳猜,而且在建立索引的時(shí)候也可以減少資源的消耗。
二滑凉、查詢的優(yōu)化
保證在實(shí)現(xiàn)功能的基礎(chǔ)上统扳,盡量減少對數(shù)據(jù)庫的訪問次數(shù);通過搜索參數(shù)畅姊,盡量減少對表的訪問行數(shù),最小化結(jié)果集咒钟,從而減輕網(wǎng)絡(luò)負(fù)擔(dān);能夠分開的操作盡量分開處理若未,提高每次的響應(yīng)速度朱嘴;在數(shù)據(jù)窗口使用SQL時(shí),盡量把使用的索引放在選擇的首列粗合;算法的結(jié)構(gòu)盡量簡單萍嬉;在查詢時(shí),不要過多地使用通配符如SELECT * FROM T1語句隙疚,要用到幾列就選擇幾列如:SELECT COL1,COL2 FROM T1壤追;在可能的情況下盡量限制盡量結(jié)果集行數(shù)如:SELECT TOP 300 COL1,COL2,COL3 FROM T1,因?yàn)槟承┣闆r下用戶是不需要那么多的數(shù)據(jù)的。
在沒有建索引的情況下供屉,數(shù)據(jù)庫查找某一條數(shù)據(jù)行冰,就必須進(jìn)行全表掃描了,對所有數(shù)據(jù)進(jìn)行一次遍歷伶丐,查找出符合條件的記錄悼做。在數(shù)據(jù)量比較小的情況下,也許看不出明顯的差別哗魂,但是當(dāng)數(shù)據(jù)量大的情況下肛走,這種情況就是極為糟糕的了。
SQL語句在SQL SERVER中是如何執(zhí)行的录别,他們擔(dān)心自己所寫的SQL語句會被SQL SERVER誤解羹与。比如:
select * from table1 where name='zhangsan' and tID > 10000
和執(zhí)行:
select * from table1 where tID > 10000 and name='zhangsan'
一些人不知道以上兩條語句的執(zhí)行效率是否一樣故硅,因?yàn)槿绻唵蔚膹恼Z句先后上看,這兩個(gè)語句的確是不一樣纵搁,如果tID是一個(gè)聚合索引,那么后一句僅僅從表的10000條以后的記錄中查找就行了往踢;而前一句則要先從全表中查找看有幾個(gè)name='zhangsan'的腾誉,而后再根據(jù)限制條件條件tID>10000來提出查詢結(jié)果。
事實(shí)上峻呕,這樣的擔(dān)心是不必要的利职。SQL SERVER中有一個(gè)“查詢分析優(yōu)化器”,它可以計(jì)算出where子句中的搜索條件并確定哪個(gè)索引能縮小表掃描的搜索空間瘦癌,也就是說猪贪,它能實(shí)現(xiàn)自動優(yōu)化。雖然查詢優(yōu)化器可以根據(jù)where子句自動的進(jìn)行查詢優(yōu)化讯私,但有時(shí)查詢優(yōu)化器就會不按照您的本意進(jìn)行快速查詢热押。
在查詢分析階段,查詢優(yōu)化器查看查詢的每個(gè)階段并決定限制需要掃描的數(shù)據(jù)量是否有用斤寇。如果一個(gè)階段可以被用作一個(gè)掃描參數(shù)(SARG)桶癣,那么就稱之為可優(yōu)化的,并且可以利用索引快速獲得所需數(shù)據(jù)娘锁。
SARG的定義:用于限制搜索的一個(gè)操作牙寞,因?yàn)樗ǔJ侵敢粋€(gè)特定的匹配,一個(gè)值的范圍內(nèi)的匹配或者兩個(gè)以上條件的AND連接莫秆。形式如下:
列名 操作符 <常數(shù) 或 變量> 或 <常數(shù) 或 變量> 操作符 列名
列名可以出現(xiàn)在操作符的一邊间雀,而常數(shù)或變量出現(xiàn)在操作符的另一邊。如:
Name=’張三’
價(jià)格>5000
5000<價(jià)格
Name=’張三’ and 價(jià)格>5000
如果一個(gè)表達(dá)式不能滿足SARG的形式镊屎,那它就無法限制搜索的范圍了惹挟,也就是SQL SERVER必須對每一行都判斷它是否滿足WHERE子句中的所有條件。所以一個(gè)索引對于不滿足SARG形式的表達(dá)式來說是無用的杯道。
?所以匪煌,優(yōu)化查詢最重要的就是,盡量使語句符合查詢優(yōu)化器的規(guī)則避免全表掃描而使用索引查詢党巾。
具體要注意的:
1.應(yīng)盡量避免在 where 子句中對字段進(jìn)行 null 值判斷萎庭,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num is null
可以在num上設(shè)置默認(rèn)值0齿拂,確保表中num列沒有null值驳规,然后這樣查詢:
select id from t where num=0
2.應(yīng)盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描署海。優(yōu)化器將無法通過索引來確定將要命中的行數(shù),因此需要搜索該表的所有行吗购。
3.應(yīng)盡量避免在 where 子句中使用 or 來連接條件医男,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10
union all
select id from t where num=20
4.in 和 not in 也要慎用捻勉,因?yàn)镮N會使系統(tǒng)無法使用索引,而只能直接搜索表中的數(shù)據(jù)镀梭。如:
select id from t where num in(1,2,3)
對于連續(xù)的數(shù)值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
5.盡量避免在索引過的字符數(shù)據(jù)中踱启,使用非打頭字母搜索报账。這也使得引擎無法利用索引。
見如下例子:
SELECT * FROM T1 WHERE NAME LIKE ‘%L%’
SELECT * FROM T1 WHERE SUBSTING(NAME,2,1)=’L’
SELECT * FROM T1 WHERE NAME LIKE ‘L%’
即使NAME字段建有索引埠偿,前兩個(gè)查詢依然無法利用索引完成加快操作透罢,引擎不得不對全表所有數(shù)據(jù)逐條操作來完成任務(wù)。而第三個(gè)查詢能夠使用索引來加快操作冠蒋。
6.必要時(shí)強(qiáng)制查詢優(yōu)化器使用某個(gè)索引羽圃,如在 where 子句中使用參數(shù),也會導(dǎo)致全表掃描抖剿。因?yàn)镾QL只有在運(yùn)行時(shí)才會解析局部變量朽寞,但優(yōu)化程序不能將訪問計(jì)劃的選擇推遲到運(yùn)行時(shí);它必須在編譯時(shí)進(jìn)行選擇牙躺。然而愁憔,如果在編譯時(shí)建立訪問計(jì)劃,變量的值還是未知的孽拷,因而無法作為索引選擇的輸入項(xiàng)吨掌。如下面語句將進(jìn)行全表掃描:
select id from t wherenum=@num
可以改為強(qiáng)制查詢使用索引:
select id from t with(index(索引名)) wherenum=@num
7.應(yīng)盡量避免在 where 子句中對字段進(jìn)行表達(dá)式操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描脓恕。如:
SELECT * FROM T1 WHERE F1/2=100
應(yīng)改為:
SELECT * FROM T1 WHERE F1=100*2
SELECT * FROM RECORD WHERE SUBSTRING(CARD_NO,1,4)=’5378’
應(yīng)改為:
SELECT * FROM RECORD WHERE CARD_NO LIKE ‘5378%’
SELECT member_number, first_name, last_name FROM members
WHERE DATEDIFF(yy,datofbirth,GETDATE()) > 21
應(yīng)改為:
SELECT member_number, first_name, last_name FROM members
WHERE dateofbirth < DATEADD(yy,-21,GETDATE())
即:任何對列的操作都將導(dǎo)致表掃描膜宋,它包括數(shù)據(jù)庫函數(shù)、計(jì)算表達(dá)式等等炼幔,查詢時(shí)要盡可能將操作移至等號右邊秋茫。
8.應(yīng)盡量避免在where子句中對字段進(jìn)行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描乃秀。如:
select id from t where substring(name,1,3)='abc'--name以abc開頭的id
select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id
應(yīng)改為:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
9.不要在 where 子句中的“=”左邊進(jìn)行函數(shù)肛著、算術(shù)運(yùn)算或其他表達(dá)式運(yùn)算,否則系統(tǒng)將可能無法正確使用索引跺讯。
10.在使用索引字段作為條件時(shí)枢贿,如果該索引是復(fù)合索引,那么必須使用到該索引中的第一個(gè)字段作為條件時(shí)才能保證系統(tǒng)使用該索引刀脏,否則該索引將不會被使用局荚,并且應(yīng)盡可能的讓字段順序與索引順序相一致。
11.很多時(shí)候用 exists是一個(gè)好的選擇:
elect num from a where num in(select num from b)
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num)
SELECT SUM(T1.C1)FROM T1 WHERE(
(SELECT COUNT(*)FROM T2 WHERE T2.C2=T1.C2>0)
SELECT SUM(T1.C1) FROM T1WHERE EXISTS(
SELECT * FROM T2 WHERE T2.C2=T1.C2)
兩者產(chǎn)生相同的結(jié)果,但是后者的效率顯然要高于前者耀态。因?yàn)楹笳卟粫a(chǎn)生大量鎖定的表掃描或是索引掃描轮傍。
如果你想校驗(yàn)表里是否存在某條紀(jì)錄,不要用count(*)那樣效率很低首装,而且浪費(fèi)服務(wù)器資源创夜。可以用EXISTS代替仙逻。如:
IF (SELECT COUNT(*) FROM table_name WHERE column_name = 'xxx')
可以寫成:
IF EXISTS (SELECT * FROM table_name WHERE column_name = 'xxx')
經(jīng)常需要寫一個(gè)T_SQL語句比較一個(gè)父結(jié)果集和子結(jié)果集挥下,從而找到是否存在在父結(jié)果集中有而在子結(jié)果集中沒有的記錄,如:
SELECT a.hdr_key FROM hdr_tbl a---- tbl a 表示tbl用別名a代替
WHERE NOT EXISTS (SELECT * FROM dtl_tbl b WHERE a.hdr_key = b.hdr_key)
SELECT a.hdr_key FROM hdr_tbl a
LEFT JOIN dtl_tbl b ON a.hdr_key = b.hdr_key WHERE b.hdr_key IS NULL
SELECT hdr_key FROM hdr_tbl
WHERE hdr_key NOT IN (SELECT hdr_key FROM dtl_tbl)
三種寫法都可以得到同樣正確的結(jié)果桨醋,但是效率依次降低。
12.盡量使用表變量來代替臨時(shí)表现斋。如果表變量包含大量數(shù)據(jù)喜最,請注意索引非常有限(只有主鍵索引)。
13.避免頻繁創(chuàng)建和刪除臨時(shí)表庄蹋,以減少系統(tǒng)表資源的消耗瞬内。
14.臨時(shí)表并不是不可使用,適當(dāng)?shù)厥褂盟鼈兛梢允鼓承├谈行奘椋绯娴?dāng)需要重復(fù)引用大型表或常用表中的某個(gè)數(shù)據(jù)集時(shí)。但是倦西,對于一次性事件能真,最好使用導(dǎo)出表。
15.在新建臨時(shí)表時(shí)扰柠,如果一次性插入數(shù)據(jù)量很大粉铐,那么可以使用 select into 代替 create table,避免造成大量 log 卤档,以提高速度蝙泼;如果數(shù)據(jù)量不大,為了緩和系統(tǒng)表的資源劝枣,應(yīng)先create table汤踏,然后insert。
16.如果使用到了臨時(shí)表舔腾,在存儲過程的最后務(wù)必將所有的臨時(shí)表顯式刪除溪胶,先 truncate table ,然后 drop table 琢唾,這樣可以避免系統(tǒng)表的較長時(shí)間鎖定载荔。
17.在所有的存儲過程和觸發(fā)器的開始處設(shè)置 SET NOCOUNT ON ,在結(jié)束時(shí)設(shè)置 SET NOCOUNT OFF 采桃。無需在執(zhí)行存儲過程和觸發(fā)器的每個(gè)語句后向客戶端發(fā)送 DONE_IN_PROC 消息懒熙。
18.盡量避免大事務(wù)操作丘损,提高系統(tǒng)并發(fā)能力。
19.盡量避免向客戶端返回大數(shù)據(jù)量工扎,若數(shù)據(jù)量過大徘钥,應(yīng)該考慮相應(yīng)需求是否合理。
20. 避免使用不兼容的數(shù)據(jù)類型肢娘。例如float和int呈础、char和varchar、binary和varbinary是不兼容的橱健。數(shù)據(jù)類型的不兼容可能使優(yōu)化器無法執(zhí)行一些本來可以進(jìn)行的優(yōu)化操作而钞。例如:
SELECT name FROM employee WHERE salary > 60000
在這條語句中,如salary字段是money型的,則優(yōu)化器很難對其進(jìn)行優(yōu)化,因?yàn)?0000是個(gè)整型數(shù)。我們應(yīng)當(dāng)在編程時(shí)將整型轉(zhuǎn)化成為錢幣型,而不要等到運(yùn)行時(shí)轉(zhuǎn)化拘荡。
21.充分利用連接條件臼节,在某種情況下,兩個(gè)表之間可能不只一個(gè)的連接條件珊皿,這時(shí)在 WHERE 子句中將連接條件完整的寫上网缝,有可能大大提高查詢速度。
例:
SELECT SUM(A.AMOUNT) FROM ACCOUNT A,CARD B WHERE A.CARD_NO = B.CARD_NO
SELECT SUM(A.AMOUNT) FROM ACCOUNT A,CARD B WHERE A.CARD_NO = B.CARD_NO AND A.ACCOUNT_NO=B.ACCOUNT_NO
第二句將比第一句執(zhí)行快得多蟋定。
22粉臊、使用視圖加速查詢
把表的一個(gè)子集進(jìn)行排序并創(chuàng)建視圖,有時(shí)能加速查詢驶兜。它有助于避免多重排序 操作扼仲,而且在其他方面還能簡化優(yōu)化器的工作。例如:
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
如果這個(gè)查詢要被執(zhí)行多次而不止一次蝇狼,可以把所有未付款的客戶找出來放在一個(gè)視圖中阅畴,并按客戶的名字進(jìn)行排序:
CREATE VIEW DBO.V_CUST_RCVLBES
AS
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
然后以下面的方式在視圖中查詢:
SELECT * FROM V_CUST_RCVLBES
WHERE postcode>“98000”
視圖中的行要比主表中的行少,而且物理順序就是所要求的順序颤专,減少了磁盤I/O纽哥,所以查詢工作量可以得到大幅減少。
23栖秕、能用DISTINCT的就不用GROUP BY
SELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID
可改為:
SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 10
24.能用UNION ALL就不要用UNION
UNION ALL不執(zhí)行SELECT DISTINCT函數(shù)春塌,這樣就會減少很多不必要的資源
35.盡量不要用SELECT INTO語句。
SELECT INOT 語句會導(dǎo)致表鎖定,阻止其他用戶訪問該表只壳。
上面我們提到的是一些基本的提高查詢速度的注意事項(xiàng),但是在更多的情況下,往往需要反復(fù)試驗(yàn)比較不同的語句以得到最佳方案俏拱。最好的方法當(dāng)然是測試,看實(shí)現(xiàn)相同功能的SQL語句哪個(gè)執(zhí)行時(shí)間最少吼句,但是數(shù)據(jù)庫中如果數(shù)據(jù)量很少锅必,是比較不出來的,這時(shí)可以用查看執(zhí)行計(jì)劃惕艳,即:把實(shí)現(xiàn)相同功能的多條SQL語句考到查詢分析器搞隐,按CTRL+L看查所利用的索引,表掃描次數(shù)(這兩個(gè)對性能影響最大)远搪,總體上看詢成本百分比即可劣纲。
三、算法的優(yōu)化
盡量避免使用游標(biāo)谁鳍,因?yàn)橛螛?biāo)的效率較差味廊,如果游標(biāo)操作的數(shù)據(jù)超過1萬行,那么就應(yīng)該考慮改寫棠耕。.使用基于游標(biāo)的方法或臨時(shí)表方法之前,應(yīng)先尋找基于集的解決方案來解決問題柠新,基于集的方法通常更有效窍荧。與臨時(shí)表一樣,游標(biāo)并不是不可使用恨憎。對小型數(shù)據(jù)集使用 FAST_FORWARD 游標(biāo)通常要優(yōu)于其他逐行處理方法蕊退,尤其是在必須引用幾個(gè)表才能獲得所需的數(shù)據(jù)時(shí)。在結(jié)果集中包括“合計(jì)”的例程通常要比使用游標(biāo)執(zhí)行的速度快憔恳。如果開發(fā)時(shí)間允許瓤荔,基于游標(biāo)的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好钥组。
游標(biāo)提供了對特定集合中逐行掃描的手段输硝,一般使用游標(biāo)逐行遍歷數(shù)據(jù),根據(jù)取出的數(shù)據(jù)不同條件進(jìn)行不同的操作程梦。尤其對多表和大表定義的游標(biāo)(大的數(shù)據(jù)集合)循環(huán)很容易使程序進(jìn)入一個(gè)漫長的等特甚至死機(jī)点把。
在有些場合,有時(shí)也非得使用游標(biāo)屿附,此時(shí)也可考慮將符合條件的數(shù)據(jù)行轉(zhuǎn)入臨時(shí)表中郎逃,再對臨時(shí)表定義游標(biāo)進(jìn)行操作,可時(shí)性能得到明顯提高挺份。
(例如:對內(nèi)統(tǒng)計(jì)第一版)
封裝存儲過程
四褒翰、建立高效的索引
創(chuàng)建索引一般有以下兩個(gè)目的:維護(hù)被索引列的唯一性和提供快速訪問表中數(shù)據(jù)的策略。大型數(shù)據(jù)庫有兩種索引即簇索引和非簇索引,一個(gè)沒有簇索引的表是按堆結(jié)構(gòu)存儲數(shù)據(jù)优训,所有的數(shù)據(jù)均添加在表的尾部朵你,而建立了簇索引的表,其數(shù)據(jù)在物理上會按照簇索引鍵的順序存儲型宙,一個(gè)表只允許有一個(gè)簇索引撬呢,因此,根據(jù)B樹結(jié)構(gòu)妆兑,可以理解添加任何一種索引均能提高按索引列查詢的速度魂拦,但會降低插入、更新搁嗓、刪除操作的性能芯勘,尤其是當(dāng)填充因子(Fill Factor)較大時(shí)。所以對索引較多的表進(jìn)行頻繁的插入腺逛、更新荷愕、刪除操作,建表和索引時(shí)因設(shè)置較小的填充因子棍矛,以便在各數(shù)據(jù)頁中留下較多的自由空間安疗,減少頁分割及重新組織的工作。
索引是從數(shù)據(jù)庫中獲取數(shù)據(jù)的最高效方式之一够委。95% 的數(shù)據(jù)庫性能問題都可以采用索引技術(shù)得到解決荐类。作為一條規(guī)則,我通常對邏輯主鍵使用唯一的成組索引茁帽,對系統(tǒng)鍵(作為存儲過程)采用唯一的非成組索引玉罐,對任何外鍵列[字段]采用非成組索引。不過潘拨,索引就象是鹽吊输,太多了菜就咸了。你得考慮數(shù)據(jù)庫的空間有多大铁追,表如何進(jìn)行訪問季蚂,還有這些訪問是否主要用作讀寫。
實(shí)際上琅束,您可以把索引理解為一種特殊的目錄癣蟋。微軟的SQL SERVER提供了兩種索引:聚集索引(clustered index,也稱聚類索引狰闪、簇集索引)和非聚集索引(nonclustered index疯搅,也稱非聚類索引、非簇集索引)埋泵。下面幔欧,我們舉例來說明一下聚集索引和非聚集索引的區(qū)別:
其實(shí)罪治,我們的漢語字典的正文本身就是一個(gè)聚集索引。比如礁蔗,我們要查“安”字觉义,就會很自然地翻開字典的前幾頁,因?yàn)椤鞍病钡钠匆羰恰癮n”浴井,而按照拼音排序漢字的字典是以英文字母“a”開頭并以“z”結(jié)尾的晒骇,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”開頭的部分仍然找不到這個(gè)字磺浙,那么就說明您的字典中沒有這個(gè)字洪囤;同樣的,如果查“張”字撕氧,那您也會將您的字典翻到最后部分瘤缩,因?yàn)椤皬垺钡钠匆羰恰皕hang”。也就是說伦泥,字典的正文部分本身就是一個(gè)目錄剥啤,您不需要再去查其他目錄來找到您需要找的內(nèi)容。
我們把這種正文內(nèi)容本身就是一種按照一定規(guī)則排列的目錄稱為“聚集索引”不脯。
如果您認(rèn)識某個(gè)字府怯,您可以快速地從自動中查到這個(gè)字。但您也可能會遇到您不認(rèn)識的字防楷,不知道它的發(fā)音富腊,這時(shí)候,您就不能按照剛才的方法找到您要查的字域帐,而需要去根據(jù)“偏旁部首”查到您要找的字,然后根據(jù)這個(gè)字后的頁碼直接翻到某頁來找到您要找的字是整。但您結(jié)合“部首目錄”和“檢字表”而查到的字的排序并不是真正的正文的排序方法肖揣,比如您查“張”字,我們可以看到在查部首之后的檢字表中“張”的頁碼是672頁浮入,檢字表中“張”的上面是“馳”字龙优,但頁碼卻是63頁,“張”的下面是“弩”字事秀,頁面是390頁彤断。很顯然,這些字并不是真正的分別位于“張”字的上下方易迹,現(xiàn)在您看到的連續(xù)的“馳宰衙、張、弩”三字實(shí)際上就是他們在非聚集索引中的排序睹欲,是字典正文中的字在非聚集索引中的映射供炼。我們可以通過這種方式來找到您所需要的字一屋,但它需要兩個(gè)過程,先找到目錄中的結(jié)果袋哼,然后再翻到您所需要的頁碼冀墨。
我們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱為“非聚集索引”涛贯。
進(jìn)一步引申一下诽嘉,我們可以很容易的理解:每個(gè)表只能有一個(gè)聚集索引,因?yàn)槟夸浿荒馨凑找环N方法進(jìn)行排序弟翘。
(一)何時(shí)使用聚集索引或非聚集索引
下面的表總結(jié)了何時(shí)使用聚集索引或非聚集索引(很重要)虫腋。
動作描述 使用聚集索引 使用非聚集索引
列經(jīng)常被分組排序 應(yīng) 應(yīng)
返回某范圍內(nèi)的數(shù)據(jù) 應(yīng) 不應(yīng)
一個(gè)或極少不同值 不應(yīng) 不應(yīng)
小數(shù)目的不同值 應(yīng) 不應(yīng)
大數(shù)目的不同值 不應(yīng) 應(yīng)
頻繁更新的列 不應(yīng) 應(yīng)
外鍵列 應(yīng) 應(yīng)
主鍵列 應(yīng) 應(yīng)
頻繁修改索引列 不應(yīng) 應(yīng)
事實(shí)上,我們可以通過前面聚集索引和非聚集索引的定義的例子來理解上表衅胀。如:返回某范圍內(nèi)的數(shù)據(jù)一項(xiàng)岔乔。比如您的某個(gè)表有一個(gè)時(shí)間列,恰好您把聚合索引建立在了該列滚躯,這時(shí)您查詢2004年1月1日至2004年10月1日之間的全部數(shù)據(jù)時(shí)雏门,這個(gè)速度就將是很快的,因?yàn)槟倪@本字典正文是按日期進(jìn)行排序的掸掏,聚類索引只需要找到要檢索的所有數(shù)據(jù)中的開頭和結(jié)尾數(shù)據(jù)即可茁影;而不像非聚集索引,必須先查到目錄中查到每一項(xiàng)數(shù)據(jù)對應(yīng)的頁碼丧凤,然后再根據(jù)頁碼查到具體內(nèi)容募闲。
(二)結(jié)合實(shí)際,談索引使用的誤區(qū)
理論的目的是應(yīng)用愿待。雖然我們剛才列出了何時(shí)應(yīng)使用聚集索引或非聚集索引浩螺,但在實(shí)踐中以上規(guī)則卻很容易被忽視或不能根據(jù)實(shí)際情況進(jìn)行綜合分析。下面我們將根據(jù)在實(shí)踐中遇到的實(shí)際問題來談一下索引使用的誤區(qū)仍侥,以便于大家掌握索引建立的方法要出。
1、主鍵就是聚集索引
這種想法筆者認(rèn)為是極端錯(cuò)誤的农渊,是對聚集索引的一種浪費(fèi)患蹂。雖然SQL SERVER默認(rèn)是在主鍵上建立聚集索引的。
通常砸紊,我們會在每個(gè)表中都建立一個(gè)ID列传于,以區(qū)分每條數(shù)據(jù),并且這個(gè)ID列是自動增大的醉顽,步長一般為1沼溜。我們的這個(gè)辦公自動化的實(shí)例中的列Gid就是如此。此時(shí)游添,如果我們將這個(gè)列設(shè)為主鍵盛末,SQL SERVER會將此列默認(rèn)為聚集索引弹惦。這樣做有好處,就是可以讓您的數(shù)據(jù)在數(shù)據(jù)庫中按照ID進(jìn)行物理排序悄但,但筆者認(rèn)為這樣做意義不大棠隐。
顯而易見,聚集索引的優(yōu)勢是很明顯的檐嚣,而每個(gè)表中只能有一個(gè)聚集索引的規(guī)則助泽,這使得聚集索引變得更加珍貴。
從我們前面談到的聚集索引的定義我們可以看出嚎京,使用聚集索引的最大好處就是能夠根據(jù)查詢要求嗡贺,迅速縮小查詢范圍,避免全表掃描鞍帝。在實(shí)際應(yīng)用中诫睬,因?yàn)镮D號是自動生成的,我們并不知道每條記錄的ID號帕涌,所以我們很難在實(shí)踐中用ID號來進(jìn)行查詢摄凡。這就使讓ID號這個(gè)主鍵作為聚集索引成為一種資源浪費(fèi)。其次蚓曼,讓每個(gè)ID號都不同的字段作為聚集索引也不符合“大數(shù)目的不同值情況下不應(yīng)建立聚合索引”規(guī)則亲澡;當(dāng)然,這種情況只是針對用戶經(jīng)常修改記錄內(nèi)容纫版,特別是索引項(xiàng)的時(shí)候會負(fù)作用床绪,但對于查詢速度并沒有影響。
在辦公自動化系統(tǒng)中其弊,無論是系統(tǒng)首頁顯示的需要用戶簽收的文件癞己、會議還是用戶進(jìn)行文件查詢等任何情況下進(jìn)行數(shù)據(jù)查詢都離不開字段的是“日期”還有用戶本身的“用戶名”。
通常梭伐,辦公自動化的首頁會顯示每個(gè)用戶尚未簽收的文件或會議痹雅。雖然我們的where語句可以僅僅限制當(dāng)前用戶尚未簽收的情況,但如果您的系統(tǒng)已建立了很長時(shí)間籽御,并且數(shù)據(jù)量很大,那么惰匙,每次每個(gè)用戶打開首頁的時(shí)候都進(jìn)行一次全表掃描技掏,這樣做意義是不大的,絕大多數(shù)的用戶1個(gè)月前的文件都已經(jīng)瀏覽過了项鬼,這樣做只能徒增數(shù)據(jù)庫的開銷而已哑梳。事實(shí)上,我們完全可以讓用戶打開系統(tǒng)首頁時(shí)绘盟,數(shù)據(jù)庫僅僅查詢這個(gè)用戶近3個(gè)月來未閱覽的文件鸠真,通過“日期”這個(gè)字段來限制表掃描悯仙,提高查詢速度。如果您的辦公自動化系統(tǒng)已經(jīng)建立的2年吠卷,那么您的首頁顯示速度理論上將是原來速度8倍锡垄,甚至更快。
2祭隔、只要建立索引就能顯著提高查詢速度
事實(shí)上货岭,我們可以發(fā)現(xiàn)上面的例子中,第2疾渴、3條語句完全相同千贯,且建立索引的字段也相同;不同的僅是前者在fariqi字段上建立的是非聚合索引搞坝,后者在此字段上建立的是聚合索引搔谴,但查詢速度卻有著天壤之別。所以桩撮,并非是在任何字段上簡單地建立索引就能提高查詢速度敦第。
從建表的語句中,我們可以看到這個(gè)有著1000萬數(shù)據(jù)的表中fariqi字段有5003個(gè)不同記錄距境。在此字段上建立聚合索引是再合適不過了申尼。在現(xiàn)實(shí)中,我們每天都會發(fā)幾個(gè)文件垫桂,這幾個(gè)文件的發(fā)文日期就相同师幕,這完全符合建立聚集索引要求的:“既不能絕大多數(shù)都相同,又不能只有極少數(shù)相同”的規(guī)則诬滩。由此看來霹粥,我們建立“適當(dāng)”的聚合索引對于我們提高查詢速度是非常重要的。
3疼鸟、把所有需要提高查詢速度的字段都加進(jìn)聚集索引后控,以提高查詢速度
上面已經(jīng)談到:在進(jìn)行數(shù)據(jù)查詢時(shí)都離不開字段的是“日期”還有用戶本身的“用戶名”。既然這兩個(gè)字段都是如此的重要空镜,我們可以把他們合并起來浩淘,建立一個(gè)復(fù)合索引(compound index)。
很多人認(rèn)為只要把任何字段加進(jìn)聚集索引吴攒,就能提高查詢速度张抄,也有人感到迷惑:如果把復(fù)合的聚集索引字段分開查詢,那么查詢速度會減慢嗎洼怔?帶著這個(gè)問題署惯,我們來看一下以下的查詢速度(結(jié)果集都是25萬條數(shù)據(jù)):(日期列fariqi首先排在復(fù)合聚集索引的起始列,用戶名neibuyonghu排在后列)
我們可以看到如果僅用聚集索引的起始列作為查詢條件和同時(shí)用到復(fù)合聚集索引的全部列的查詢速度是幾乎一樣的镣隶,甚至比用上全部的復(fù)合索引列還要略快(在查詢結(jié)果集數(shù)目一樣的情況下)极谊;而如果僅用復(fù)合聚集索引的非起始列作為查詢條件的話诡右,這個(gè)索引是不起任何作用的。當(dāng)然轻猖,語句1帆吻、2的查詢速度一樣是因?yàn)椴樵兊臈l目數(shù)一樣,如果復(fù)合索引的所有列都用上蜕依,而且查詢結(jié)果少的話桅锄,這樣就會形成“索引覆蓋”,因而性能可以達(dá)到最優(yōu)样眠。同時(shí)友瘤,請記住:無論您是否經(jīng)常使用聚合索引的其他列檐束,但其前導(dǎo)列一定要是使用最頻繁的列辫秧。
(三)其他注意事項(xiàng)
“水可載舟,亦可覆舟”被丧,索引也一樣盟戏。索引有助于提高檢索性能,但過多或不當(dāng)?shù)乃饕矔?dǎo)致系統(tǒng)低效甥桂。因?yàn)橛脩粼诒碇忻考舆M(jìn)一個(gè)索引柿究,數(shù)據(jù)庫就要做更多的工作低斋。過多的索引甚至?xí)?dǎo)致索引碎片妙蔗。
所以說,我們要建立一個(gè)“適當(dāng)”的索引體系捌袜,特別是對聚合索引的創(chuàng)建办陷,更應(yīng)精益求精貌夕,以使您的數(shù)據(jù)庫能得到高性能的發(fā)揮