前言:
Effective SQL
瀏覽了一遍EFFECTIVE系列書名,似乎缺少Effective SQL,所以有了一種莫名的沖動吧。
參考CSDN和博客堂的文檔,加上以自己的切身體會總結(jié)出的一些Effective,希望能夠給大家?guī)硪恍椭?
由于轉(zhuǎn)載,整理的文章比較多,所以不一一指出出處,請?jiān)淖髡叨喽嗾徑?
簽于作者水平有限,所以可能于實(shí)際中有較大出入,望見諒。
如有不正之處,請及時與作者本人聯(lián)系。謝謝!
正文:
一.名詞解釋:
0。SQL 結(jié)構(gòu)化查詢語言(Structured Query Language)
1。非關(guān)系型數(shù)據(jù)庫系統(tǒng)
做為第一代數(shù)據(jù)庫系統(tǒng)的總稱,其包括2種類型:“層次”數(shù)據(jù)庫與“網(wǎng)狀”數(shù)據(jù)庫
“層次”數(shù)據(jù)庫管理系統(tǒng) eg:IBM&IMS (Information Management System)
特點(diǎn):數(shù)據(jù)按層次模型組織
"網(wǎng)狀"數(shù)據(jù)庫
特點(diǎn):數(shù)據(jù)按網(wǎng)狀模型組織
2。關(guān)系型數(shù)據(jù)庫系統(tǒng)
關(guān)系性數(shù)據(jù)庫管理系統(tǒng) (RDBMS)
eg:SQL/DS , DB2, Oracle ,Informix ,Unity,dBASE等
特點(diǎn):數(shù)據(jù)按二維的表格組織。
3。數(shù)據(jù)庫(DataBase)
按一定結(jié)構(gòu)存儲在計(jì)算機(jī)中相互關(guān)聯(lián)的數(shù)據(jù)的集合。
4。數(shù)據(jù)庫管理系統(tǒng)DBMS(Database Management System)
一個通用的軟件系統(tǒng)。就是讓你怎么管理你的數(shù)據(jù)庫。其中包括存儲,安全,完整性管理等。
5。數(shù)據(jù)庫應(yīng)用系統(tǒng)DBAS (Database Application System)
數(shù)據(jù)庫應(yīng)用程序系統(tǒng),建立在DBMS基礎(chǔ)之上的。就是一個面向用戶的軟件系統(tǒng)。
6。ANSI標(biāo)準(zhǔn) (American National Standards Institute)美國國家標(biāo)準(zhǔn)委員會
因?yàn)?999年第2次更新SQL,所以SQL又稱為SQL99或SQL3(第3版,前2個版本分別為1986年的sql ,1992 年的sql2/sql92)。
7。SQL語句的3種類型
數(shù)據(jù)操作語句(Data Manipulation Language ) DML 關(guān)于數(shù)據(jù)操作命令的 eg:select,insert,update,delete
數(shù)據(jù)定義語句(Data Definition Language ) DDL 關(guān)于數(shù)據(jù)對象訪問的 eg:create, drop
數(shù)據(jù)控制語句(Data Control Language) DCL 關(guān)于權(quán)限的 eg:grant ,revoke
8。PL/SQL Procedural Language/sql
用于oracle的語言
9.T-SQL transact-sql
用于 microsoft sql server 和sybase adaptive server
10。E.F.Codd關(guān)于關(guān)系型數(shù)據(jù)庫12條檢驗(yàn)原則(MYSQL,不支持視圖和原子事物處理,所以排除)
內(nèi)容:暫略
11。數(shù)據(jù)庫設(shè)計(jì)之新奧爾良方法。
需求分析==》概念設(shè)計(jì)==》邏輯設(shè)計(jì)==》物理設(shè)計(jì).
4個步驟的具體中以需求分析最重要.
需求分析的內(nèi)容:暫略
概念設(shè)計(jì)的內(nèi)容:暫略
邏輯設(shè)計(jì)的內(nèi)容:暫略
物理設(shè)計(jì)的內(nèi)容:暫略
二.數(shù)據(jù)庫優(yōu)化方案
1.索引
一 概述
可以利用索引快速訪問數(shù)據(jù)庫表中的特定信息。索引是對數(shù)據(jù)庫表中一個或多個列的值進(jìn)行排序的結(jié)構(gòu)。
索引提供指針以指向存儲在表中指定列的數(shù)據(jù)值,然后根據(jù)指定的排序次序排列這些指針。
數(shù)據(jù)庫使用索引的方式與使用書的目錄很相似:通過搜索索引找到特定的值,
然后跟隨指針到達(dá)包含該值的行
索引是一個單獨(dú)的、物理的數(shù)據(jù)庫結(jié)構(gòu),它是某個表中一列或若干列值的集合和相應(yīng)的指向表中物理標(biāo)識這些值的數(shù)據(jù)頁的邏輯指針清單。
一個表的存儲是由兩部分組成的,一部分用來存放表的數(shù)據(jù)頁面,另一部分存放索引頁面。索引就存放在索引頁面上
二 索引的兩種類型:
聚集索引=簇集索引
聚集索引基于數(shù)據(jù)行的鍵值在表內(nèi)排序和存儲這些數(shù)據(jù)行。由于數(shù)據(jù)行按基于聚集索引鍵的排序次序存儲,
因此聚集索引對查找行很有效。每個表只能有一個聚集索引,因?yàn)閿?shù)據(jù)行本身只能按一個順序存儲。
數(shù)據(jù)行本身構(gòu)成聚集索引的最低級別。
只有當(dāng)表包含聚集索引時,表內(nèi)的數(shù)據(jù)行才按排序次序存儲。如果表沒有聚集索引,
則其數(shù)據(jù)行按堆集方式存儲。
聚集索引對于那些經(jīng)常要搜索范圍值的列特別有效。使用聚集索引找到包含第一個值的行后,
便可以確保包含后續(xù)索引值的行在物理相鄰。例如,如果應(yīng)用程序執(zhí)行的一個查詢經(jīng)常檢索某一日期范圍
內(nèi)的記錄,則使用聚集索引可以迅速找到包含開始日期的行,然后檢索表中所有相鄰的行,
直到到達(dá)結(jié)束日期。這樣有助于提高此類查詢的性能。同樣,如果對從表中檢索的數(shù)據(jù)進(jìn)行排序時
經(jīng)常要用到某一列,則可以將該表在該列上聚集(物理排序),避免每次查詢該列時都進(jìn)行排序,
從而節(jié)省成本
非聚集索引
非聚集索引具有完全獨(dú)立于數(shù)據(jù)行的結(jié)構(gòu)。非聚集索引的最低行包含非聚集索引的鍵值,
并且每個鍵值項(xiàng)都有指針指向包含該鍵值的數(shù)據(jù)行。數(shù)據(jù)行不按基于非聚集鍵的次序存儲。
在非聚集索引內(nèi),從索引行指向數(shù)據(jù)行的指針稱為行定位器。
行定位器的結(jié)構(gòu)取決于數(shù)據(jù)頁的存儲方式是堆集還是聚集。對于堆集,行定位器是指向行的指針。
對于有聚集索引的表,行定位器是聚集索引鍵。
只有在表上創(chuàng)建了聚集索引時,表內(nèi)的行才按特定的順序存儲。這些行就基于聚集索引鍵按順序存儲。
如果一個表只有非聚集索引,它的數(shù)據(jù)行將按無序的堆集方式存儲
非聚集索引可以建多個,兩者都能改善查詢性能
非聚集索引與聚集索引一樣有 B 樹結(jié)構(gòu),但是有兩個重大差別:
數(shù)據(jù)行不按非聚集索引鍵的順序排序和存儲。
非聚集索引的葉層不包含數(shù)據(jù)頁。
相反,葉節(jié)點(diǎn)包含索引行。每個索引行包含非聚集鍵值以及一個或多個行定位器,
這些行定位器指向有該鍵值的數(shù)據(jù)行(如果索引不唯一,則可能是多行)。
非聚集索引可以在有聚集索引的表、堆集或索引視圖上定義
聚集索引-->順序表結(jié)構(gòu).其物理數(shù)據(jù)和邏輯排序緊鄰.
非聚集索引-->單鏈表結(jié)構(gòu).起物理和邏輯排序不按順序排列.
打個比方.
一本字典,你現(xiàn)在查一個陳字.你有2種方法.首先,你在知道他念chen的情況下去按照拼音字母去查找.他是排在字母A,B
于是你很容易的就找到"陳"字.第2種方法則是按編旁查找,先找到耳朵旁,去找到一個臨時的編旁表在去找"東"這個字,然后按照給出的
頁數(shù)找到相應(yīng)的位置.
顯然,第一種方法就是聚集索引,按照物理位置根據(jù)排序來查找.
第2種方法則是非聚集索引,按照一個臨時索引來查找.
另外
唯一索引
唯一索引可以確保索引列不包含重復(fù)的值。在多列唯一索引的情況下,該索引可以確保索引列中每個值組
合都是唯一的。唯一索引既是索引也是約束。
復(fù)合索引
索引項(xiàng)是多個的就叫組合索引,也叫復(fù)合索引。復(fù)合索引使用時需要注意索引項(xiàng)的次序。
二 索引的創(chuàng)建
有兩種方法可以在 SQL Server 內(nèi)定義索引: CREATE INDEX 語句和CREATE TABLE 語句
CREATE TABLE支持在創(chuàng)建索引時使用下列約束:
PRIMARY KEY 創(chuàng)建唯一索引來強(qiáng)制執(zhí)行主鍵
UNIQUE 創(chuàng)建唯一索引
CLUSTERED 創(chuàng)建聚集索引
NONCLUSTERED 創(chuàng)建非聚集索引
注: 1 定義索引時,可以指定每列的數(shù)據(jù)是按升序還是降序存儲。如果不指定,則默認(rèn)為升序
2 支持在計(jì)算列上創(chuàng)建索引
3 為索引指定填充因子
可標(biāo)識填充因子來指定每個索引頁的填滿程度。索引頁上的空余空間量很重要,
因?yàn)楫?dāng)索引頁填滿時,系統(tǒng)必須花時間拆分它以便為新行騰出空間。
三 索引的維護(hù)語句
DBCC DBREINDEX 重建指定數(shù)據(jù)庫中表的一個或多個索引
DBCC INDEXFRAG 整理指定的表或視圖的聚集索引和輔助索引碎片
比較
速度 兼容性 日志影響 數(shù)據(jù)訪問影響 額外磁盤空間
DBCC 最快 最好 大,但能通過把 操作過程中數(shù)據(jù)不 需要大
DBREINDEX 可以重 故障還原模型設(shè) 能訪問,影響大
建所有 為簡單減少日志
有索引
DBCC 慢 但可 必須分 小 數(shù)據(jù)未被鎖定 需要小
INDEXDEFRAG 隨時終 別指定
止執(zhí)行
drop index 中等 必須分 大,但能通過把 僅在操作執(zhí)行時 中等,操作在
create index 別指定 故障還原模型設(shè) 鎖定數(shù)據(jù) tempdb中進(jìn)行
為簡單減少日志
四 查看索引的方法
sp_indexes 返回指定遠(yuǎn)程表的索引信息
INDEXKEY_PROPERTY 返回有關(guān)索引鍵的信息
sysindexes系統(tǒng)表 數(shù)據(jù)庫中的每個索引和表在表中各占一行,該表存儲在每個數(shù)據(jù)庫中
五 可以通過執(zhí)行計(jì)劃
查看sql語句執(zhí)行時是否建立在索引之上
比如
CREATE TABLE Test
(Field_1 int NOT NULL,
Field_2 int CONSTRAINT PK_Test
PRIMARY KEY CLUSTERED (Field_1))
CREATE index IX_Test ON Test (Field_2)
1 SELECT * FROM Test WHERE Field_2 =408
執(zhí)行計(jì)劃可以看出使用了IX_Test索引
2 SELECT * FROM Test WHERE Field_1 =1
執(zhí)行計(jì)劃可以看出使用了PK_Test
3 但如果是SELECT * FROM Test with (index(IX_Test)) WHERE Field_1 =1
則指定使用索引
六 索引的具體使用
1) 索引的設(shè)計(jì)
A:盡量避免表掃描
檢查你的查詢語句的where子句,因?yàn)檫@是優(yōu)化器重要關(guān)注的地方。包含在where里面的每一列(column)都是可能的侯選索引,為能達(dá)到最優(yōu)的性能,考慮在下面給出的例子:對于在where子句中給出了column1這個列。
下面的兩個條件可以提高索引的優(yōu)化查詢性能!
第一:在表中的column1列上有一個單索引
第二:在表中有多索引,但是column1是第一個索引的列
避免定義多索引而column1是第二個或后面的索引,這樣的索引不能優(yōu)化服務(wù)器性能
例如:下面的例子用了pubs數(shù)據(jù)庫。
SELECT au_id, au_lname, au_fname FROM authors
WHERE au_lname = ’White’
按下面幾個列上建立的索引將會是對優(yōu)化器有用的索引
?au_lname
?au_lname, au_fname
而在下面幾個列上建立的索引將不會對優(yōu)化器起到好的作用
?au_address
?au_fname, au_lname
考慮使用窄的索引在一個或兩個列上,窄索引比多索引和復(fù)合索引更能有效。用窄的索引,在每一頁上
將會有更多的行和更少的索引級別(相對與多索引和復(fù)合索引而言),這將推進(jìn)系統(tǒng)性能。
對于多列索引,SQL Server維持一個在所有列的索引上的密度統(tǒng)計(jì)(用于聯(lián)合)和在第一個索引上的
histogram(柱狀圖)統(tǒng)計(jì)。根據(jù)統(tǒng)計(jì)結(jié)果,如果在復(fù)合索引上的第一個索引很少被選擇使用,那么優(yōu)化器對很多查詢請求將不會使用索引。
有用的索引會提高select語句的性能,包括insert,uodate,delete。
但是,由于改變一個表的內(nèi)容,將會影響索引。每一個insert,update,delete語句將會使性能下降一些。實(shí)驗(yàn)表明,不要在一個單表上用大量的索引,不要在共享的列上(指在多表中用了參考約束)使用重疊的索引。
在某一列上檢查唯一的數(shù)據(jù)的個數(shù),比較它與表中數(shù)據(jù)的行數(shù)做一個比較。這就是數(shù)據(jù)的選擇性,這比較結(jié)果將會幫助你決定是否將某一列作為侯選的索引列,如果需要,建哪一種索引。你可以用下面的查詢語句返回某一列的不同值的數(shù)目。
select count(distinct cloumn_name) from table_name
假設(shè)column_name是一個10000行的表,則看column_name返回值來決定是否應(yīng)該使用,及應(yīng)該使用什么索引。
Unique values Index
5000 Nonclustered index
20 Clustered index
3 No index
2) 鏃索引和非鏃索引的選擇
<1:>鏃索引是行的物理順序和索引的順序是一致的。頁級,低層等索引的各個級別上都包含實(shí)際的數(shù)據(jù)頁。一個表只能是有一個鏃索引。由于update,delete語句要求相對多一些的讀操作,因此鏃索引常常能加速這樣的操作。在至少有一個索引的表中,你應(yīng)該有一個鏃索引。
在下面的幾個情況下,你可以考慮用鏃索引:
例如: 某列包括的不同值的個數(shù)是有限的(但是不是極少的)
顧客表的州名列有50個左右的不同州名的縮寫值,可以使用鏃索引。
例如: 對返回一定范圍內(nèi)值的列可以使用鏃索引,比如用between,>,>=,<,<=等等來對列進(jìn)行操作的列上。
select * from sales where ord_date between ’5/1/93’ and ’6/1/93’
例如: 對查詢時返回大量結(jié)果的列可以使用鏃索引。
SELECT * FROM phonebook WHERE last_name = ’Smith’
當(dāng)有大量的行正在被插入表中時,要避免在本表一個自然增長(例如,identity列)的列上建立鏃索引。如果你建立了鏃的索引,那么insert的性能就會大大降低。因?yàn)槊恳粋€插入的行必須到表的最后,表的最后一個數(shù)據(jù)頁。
當(dāng)一個數(shù)據(jù)正在被插入(這時這個數(shù)據(jù)頁是被鎖定的),所有的其他插入行必須等待直到當(dāng)前的插入已經(jīng)結(jié)束。
一個索引的葉級頁中包括實(shí)際的數(shù)據(jù)頁,并且在硬盤上的數(shù)據(jù)頁的次序是跟鏃索引的邏輯次序一樣的。
<2:>一個非鏃的索引就是行的物理次序與索引的次序是不同的。一個非鏃索引的葉級包含了指向行數(shù)據(jù)頁的指針。
在一個表中可以有多個非鏃索引,你可以在以下幾個情況下考慮使用非鏃索引。
在有很多不同值的列上可以考慮使用非鏃索引
例如:一個part_id列在一個part表中
select * from employee where emp_id = ’pcm9809f’
查詢語句中用order by 子句的列上可以考慮使用鏃索引
3) 一個表列如果設(shè)為主鍵(primary key),它會自動生成一個聚簇索引
這時不能直接使用Drop index Table1.Tableindex1語句
必須刪除主鍵約束,用語句:alter table table1 drop constraint 約束名(如pk_xxx)
七.全文索引
use pubs
go
--打開數(shù)據(jù)庫全文索引的支持
execute sp_fulltext_database 'enable'
go
--建立全文目錄ft_titles
execute sp_fulltext_catalog 'ft_titles', 'create'
go
--為titles表建立全文索引數(shù)據(jù)元,UPKCL_titleidind是主鍵所建立的唯一索引,可由sp_help titles得知
execute sp_fulltext_table 'titles','create', 'ft_titles', 'UPKCL_titleidind'
go
--設(shè)置全文索引列名
exec sp_fulltext_column 'titles', 'title', 'add'
go
exec sp_fulltext_column 'titles', 'notes', 'add'
go
--建立全文索引
exec sp_fulltext_table 'titles', 'activate'
go
--填充全文索引目錄
exec sp_fulltext_catalog 'ft_titles', 'start_full'
go
--使用contains和freetext
select title, notes from titles
where contains(title, '"computer Cooking"')
go
select title, notes from titles
where freetext(title, 'computer Cooking')
go
select title, notes from titles
where freetext(title, '"computer Cooking"')
go
select title, notes from titles
where contains(title, 'computer')
go
select title, notes from titles
where freetext (*, 'computer')
go
這里提一下google的搜索引擎的原理.
他把每個字詞都做為單元去查詢.
打個比方:我在字典里查詢,現(xiàn)在我要搜索"樹型"這個詞,他會把這個樹型這個詞全文掃描一遍,生成一個二叉樹.并記下他的頁數(shù).
然后當(dāng)我第2次查找的時候顯然這個"記憶"提示,然后"提取".如果你對某一個字段做了全文索引的話,他會全文掃描表一遍,然后紀(jì)錄下
相應(yīng)的紀(jì)錄,生成二叉樹.
如果我要查找"樹葉",同理也可以得出頁數(shù).但當(dāng)我們?nèi)ゲ檎乙幌?樹型結(jié)構(gòu)"他則會把"樹型"和"樹型結(jié)構(gòu)"都"紀(jì)錄"下來.
八.巧妙的使用索引.
SELECT SUM(quantity) AS quantity FROM test WHERE...
1.若WHERE 里用的是字段與常量比較,MSSQL會自動引用該字段上的索引;若用的是變量,MSSQL不會自動引用該字段上的索引而是根據(jù)聚集索引進(jìn)行掃描
2.加上with(index(索引名))指定索引,即:
SELECT SUM(quantity) AS quantity FROM with(index(索引名)) test WHERE...
指定索引后,WHERE 里不論是常量還是變量,MSSQL都根據(jù)指定的索引進(jìn)行掃描
3.DBCC DBREINDEX執(zhí)行并不一定能優(yōu)化MSSQL性能,慎用
4.如果在pub_id上建立索引的話
select * from titles where pub_id-500 >1000 ---------(a)
select * from titles where pub_id >1000+500 -----------(b)
請選用(b)語句,這樣的話,他會利用索引,而(a)的話由于對字段操作了,所以不會利用索引.
5.盡量避免用like語句,
如果去查找baa%,caa%的話
如果是like '%aa%','_aa%','[m-z]o%' 則根本不會用到索引.
替換方法.columns like 'baa%' or columns like 'caa %'
6什么情況下應(yīng)不建或少建索引
a.表記錄太少 .因?yàn)樗饕脑挘獙?shù)據(jù)庫往返2次操作,如果1個表只有幾行字段的話,數(shù)據(jù)庫會對他的紀(jì)錄一次性全部取出來,這樣的效率要遠(yuǎn)遠(yuǎn)高于索引.
b.經(jīng)常insert,delete,update的表 對一些經(jīng)常處理的業(yè)務(wù)表應(yīng)在查詢允許的情況下盡量減少索引
c.數(shù)據(jù)重復(fù)且分布平均的表字段,如:性別字段,各占50%的話,你即使建了,也起不到明顯的作用.
d.經(jīng)常和主字段一塊查詢但主字段索引值比較多的表字段
表經(jīng)常按收費(fèi)序號、戶標(biāo)識編號、抄表日期、電費(fèi)發(fā)生年月、操作標(biāo)志來具體查詢某一筆收款的情況,如果將所有的字段都建在一個索引里那將會增加數(shù)據(jù)的修改、插入、刪除時間,從實(shí)際上分析一筆收款如果按收費(fèi)序號索引就已經(jīng)將記錄減少到只有幾條,如果再按后面的幾個字段索引查詢將對性能不產(chǎn)生太大的影響。
e.如果一個表的記錄達(dá)到100萬以上的話,要對其中一個字段建索引可能要花很長的時間,甚至導(dǎo)致服務(wù)器數(shù)據(jù)庫死機(jī),因?yàn)樵诮ㄋ饕臅r候ORACLE要將索引字段所有的內(nèi)容取出并進(jìn)行全面排序,數(shù)據(jù)量大的話可能導(dǎo)致服務(wù)器排序內(nèi)存不足而引用磁盤交換空間進(jìn)行,這將嚴(yán)重影響服務(wù)器數(shù)據(jù)庫的工作。解決方法是增大數(shù)據(jù)庫啟動初始化中的排序內(nèi)存參數(shù),如果要進(jìn)行大量的索引修改可以設(shè)置10M以上的排序內(nèi)存(ORACLE缺省大小為64K),在索引建立完成后應(yīng)將參數(shù)修改回來,因?yàn)樵趯?shí)際OLTP數(shù)據(jù)庫應(yīng)用中一般不會用到這么大的排序內(nèi)存。
以下轉(zhuǎn)載
great_domino 的 Blog
探討如何在有著1000萬條數(shù)據(jù)的MS SQL SERVER數(shù)據(jù)庫中實(shí)現(xiàn)快速的數(shù)據(jù)提取和數(shù)據(jù)分頁。以下代碼說明了我們實(shí)例中數(shù)據(jù)庫的“紅頭文件”一表的部分?jǐn)?shù)據(jù)結(jié)構(gòu):
CREATE TABLE [dbo].[TGongwen] ( --TGongwen是紅頭文件表名
[Gid] [int] IDENTITY (1, 1) NOT NULL ,
--本表的id號,也是主鍵
[title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
--紅頭文件的標(biāo)題
[fariqi] [datetime] NULL ,
--發(fā)布日期
[neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,
--發(fā)布用戶
[reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,
--需要瀏覽的用戶。每個用戶中間用分隔符“,”分開
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
下面,我們來往數(shù)據(jù)庫中添加1000萬條數(shù)據(jù):
declare @i int
set @i=1
while @i<=250000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,辦公室,王局長,劉局長,張局長,admin,刑偵支隊(duì),特勤支隊(duì),交巡警支隊(duì),經(jīng)偵支隊(duì),戶政科,治安支隊(duì),外事科','這是最先的25萬條記錄')
set @i=@i+1
end
GO
declare @i int
set @i=1
while @i<=250000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','辦公室','辦公室,通信科,王局長,劉局長,張局長,admin,刑偵支隊(duì),特勤支隊(duì),交巡警支隊(duì),經(jīng)偵支隊(duì),戶政科,外事科','這是中間的25萬條記錄')
set @i=@i+1
end
GO
declare @h int
set @h=1
while @h<=100
begin
declare @i int
set @i=2002
while @i<=2003
begin
declare @j int
set @j=0
while @j<50
begin
declare @k int
set @k=0
while @k<50
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','辦公室,通信科,王局長,劉局長,張局長,admin,刑偵支隊(duì),特勤支隊(duì),交巡警支隊(duì),經(jīng)偵支隊(duì),戶政科,外事科','這是最后的50萬條記錄')
set @k=@k+1
end
set @j=@j+1
end
set @i=@i+1
end
set @h=@h+1
end
GO
declare @i int
set @i=1
while @i<=9000000
begin
insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,辦公室,王局長,劉局長,張局長,admin,刑偵支隊(duì),特勤支隊(duì),交巡警支隊(duì),經(jīng)偵支隊(duì),戶政科,治安支隊(duì),外事科','這是最后添加的900萬條記錄')
set @i=@i+1000000
end
GO
通過以上語句,我們創(chuàng)建了25萬條由于2004年2月5日發(fā)布的記錄,25萬條由辦公室于2004年9月6日發(fā)布的記錄,2002年和2003年各100個2500條相同日期、不同分秒的記錄(共50萬條),還有由通信科于2004年5月5日發(fā)布的900萬條記錄,合計(jì)1000萬條。
何時使用聚集索引或非聚集索引
下面的表總結(jié)了何時使用聚集索引或非聚集索引(很重要)。
動作描述
使用聚集索引
使用非聚集索引
列經(jīng)常被分組排序
應(yīng)
應(yīng)
返回某范圍內(nèi)的數(shù)據(jù)
應(yīng)
不應(yīng)
一個或極少不同值
不應(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)。比如您的某個表有一個時間列,恰好您把聚合索引建立在了該列,這時您查詢2004年1月1日至2004年10月1日之間的全部數(shù)據(jù)時,這個速度就將是很快的,因?yàn)槟倪@本字典正文是按日期進(jìn)行排序的,聚類索引只需要找到要檢索的所有數(shù)據(jù)中的開頭和結(jié)尾數(shù)據(jù)即可;而不像非聚集索引,必須先查到目錄中查到每一項(xiàng)數(shù)據(jù)對應(yīng)的頁碼,然后再根據(jù)頁碼查到具體內(nèi)容。
(三)結(jié)合實(shí)際,談索引使用的誤區(qū)
理論的目的是應(yīng)用。雖然我們剛才列出了何時應(yīng)使用聚集索引或非聚集索引,但在實(shí)踐中以上規(guī)則卻很容易被忽視或不能根據(jù)實(shí)際情況進(jìn)行綜合分析。下面我們將根據(jù)在實(shí)踐中遇到的實(shí)際問題來談一下索引使用的誤區(qū),以便于大家掌握索引建立的方法。
1、主鍵就是聚集索引
這種想法筆者認(rèn)為是極端錯誤的,是對聚集索引的一種浪費(fèi)。雖然SQL SERVER默認(rèn)是在主鍵上建立聚集索引的。
通常,我們會在每個表中都建立一個ID列,以區(qū)分每條數(shù)據(jù),并且這個ID列是自動增大的,步長一般為1。我們的這個辦公自動化的實(shí)例中的列Gid就是如此。此時,如果我們將這個列設(shè)為主鍵,SQL SERVER會將此列默認(rèn)為聚集索引。這樣做有好處,就是可以讓您的數(shù)據(jù)在數(shù)據(jù)庫中按照ID進(jìn)行物理排序,但筆者認(rèn)為這樣做意義不大。
顯而易見,聚集索引的優(yōu)勢是很明顯的,而每個表中只能有一個聚集索引的規(guī)則,這使得聚集索引變得更加珍貴。
從我們前面談到的聚集索引的定義我們可以看出,使用聚集索引的最大好處就是能夠根據(jù)查詢要求,迅速縮小查詢范圍,避免全表掃描。在實(shí)際應(yīng)用中,因?yàn)镮D號是自動生成的,我們并不知道每條記錄的ID號,所以我們很難在實(shí)踐中用ID號來進(jìn)行查詢。這就使讓ID號這個主鍵作為聚集索引成為一種資源浪費(fèi)。其次,讓每個ID號都不同的字段作為聚集索引也不符合“大數(shù)目的不同值情況下不應(yīng)建立聚合索引”規(guī)則;當(dāng)然,這種情況只是針對用戶經(jīng)常修改記錄內(nèi)容,特別是索引項(xiàng)的時候會負(fù)作用,但對于查詢速度并沒有影響。
在辦公自動化系統(tǒng)中,無論是系統(tǒng)首頁顯示的需要用戶簽收的文件、會議還是用戶進(jìn)行文件查詢等任何情況下進(jìn)行數(shù)據(jù)查詢都離不開字段的是“日期”還有用戶本身的“用戶名”。
通常,辦公自動化的首頁會顯示每個用戶尚未簽收的文件或會議。雖然我們的where語句可以僅僅限制當(dāng)前用戶尚未簽收的情況,但如果您的系統(tǒng)已建立了很長時間,并且數(shù)據(jù)量很大,那么,每次每個用戶打開首頁的時候都進(jìn)行一次全表掃描,這樣做意義是不大的,絕大多數(shù)的用戶1個月前的文件都已經(jīng)瀏覽過了,這樣做只能徒增數(shù)據(jù)庫的開銷而已。事實(shí)上,我們完全可以讓用戶打開系統(tǒng)首頁時,數(shù)據(jù)庫僅僅查詢這個用戶近3個月來未閱覽的文件,通過“日期”這個字段來限制表掃描,提高查詢速度。如果您的辦公自動化系統(tǒng)已經(jīng)建立的2年,那么您的首頁顯示速度理論上將是原來速度8倍,甚至更快。
在這里之所以提到“理論上”三字,是因?yàn)槿绻木奂饕€是盲目地建在ID這個主鍵上時,您的查詢速度是沒有這么高的,即使您在“日期”這個字段上建立的索引(非聚合索引)。下面我們就來看一下在1000萬條數(shù)據(jù)量的情況下各種查詢的速度表現(xiàn)(3個月內(nèi)的數(shù)據(jù)為25萬條):
(1)僅在主鍵上建立聚集索引,并且不劃分時間段:
Select gid,fariqi,neibuyonghu,title from tgongwen
用時:128470毫秒(即:128秒)
(2)在主鍵上建立聚集索引,在fariq上建立非聚集索引:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用時:53763毫秒(54秒)
(3)將聚合索引建立在日期列(fariqi)上:
select gid,fariqi,neibuyonghu,title from Tgongwen
where fariqi> dateadd(day,-90,getdate())
用時:2423毫秒(2秒)
雖然每條語句提取出來的都是25萬條數(shù)據(jù),各種情況的差異卻是巨大的,特別是將聚集索引建立在日期列時的差異。事實(shí)上,如果您的數(shù)據(jù)庫真的有1000萬容量的話,把主鍵建立在ID列上,就像以上的第1、2種情況,在網(wǎng)頁上的表現(xiàn)就是超時,根本就無法顯示。這也是我摒棄ID列作為聚集索引的一個最重要的因素。
得出以上速度的方法是:在各個select語句前加:declare @d datetime
set @d=getdate()
并在select語句后加:
select [語句執(zhí)行花費(fèi)時間(毫秒)]=datediff(ms,@d,getdate())
2、只要建立索引就能顯著提高查詢速度
事實(shí)上,我們可以發(fā)現(xiàn)上面的例子中,第2、3條語句完全相同,且建立索引的字段也相同;不同的僅是前者在fariqi字段上建立的是非聚合索引,后者在此字段上建立的是聚合索引,但查詢速度卻有著天壤之別。所以,并非是在任何字段上簡單地建立索引就能提高查詢速度。
從建表的語句中,我們可以看到這個有著1000萬數(shù)據(jù)的表中fariqi字段有5003個不同記錄。在此字段上建立聚合索引是再合適不過了。在現(xiàn)實(shí)中,我們每天都會發(fā)幾個文件,這幾個文件的發(fā)文日期就相同,這完全符合建立聚集索引要求的:“既不能絕大多數(shù)都相同,又不能只有極少數(shù)相同”的規(guī)則。由此看來,我們建立“適當(dāng)”的聚合索引對于我們提高查詢速度是非常重要的。
3、把所有需要提高查詢速度的字段都加進(jìn)聚集索引,以提高查詢速度
上面已經(jīng)談到:在進(jìn)行數(shù)據(jù)查詢時都離不開字段的是“日期”還有用戶本身的“用戶名”。既然這兩個字段都是如此的重要,我們可以把他們合并起來,建立一個復(fù)合索引(compound index)。
很多人認(rèn)為只要把任何字段加進(jìn)聚集索引,就能提高查詢速度,也有人感到迷惑:如果把復(fù)合的聚集索引字段分開查詢,那么查詢速度會減慢嗎?帶著這個問題,我們來看一下以下的查詢速度(結(jié)果集都是25萬條數(shù)據(jù)):(日期列fariqi首先排在復(fù)合聚集索引的起始列,用戶名neibuyonghu排在后列)
(1)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'
查詢速度:2513毫秒
(2)select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='辦公室'
查詢速度:2516毫秒
(3)select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='辦公室'
查詢速度:60280毫秒
從以上試驗(yàn)中,我們可以看到如果僅用聚集索引的起始列作為查詢條件和同時用到復(fù)合聚集索引的全部列的查詢速度是幾乎一樣的,甚至比用上全部的復(fù)合索引列還要略快(在查詢結(jié)果集數(shù)目一樣的情況下);而如果僅用復(fù)合聚集索引的非起始列作為查詢條件的話,這個索引是不起任何作用的。當(dāng)然,語句1、2的查詢速度一樣是因?yàn)椴樵兊臈l目數(shù)一樣,如果復(fù)合索引的所有列都用上,而且查詢結(jié)果少的話,這樣就會形成“索引覆蓋”,因而性能可以達(dá)到最優(yōu)。同時,請記住:無論您是否經(jīng)常使用聚合索引的其他列,但其前導(dǎo)列一定要是使用最頻繁的列。
(四)其他書上沒有的索引使用經(jīng)驗(yàn)總結(jié)
1、用聚合索引比用不是聚合索引的主鍵速度快
下面是實(shí)例語句:(都是提取25萬條數(shù)據(jù))
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
使用時間:3326毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000
使用時間:4470毫秒
這里,用聚合索引比用不是聚合索引的主鍵速度快了近1/4。
2、用聚合索引比用一般的主鍵作order by時速度快,特別是在小數(shù)據(jù)量情況下
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi
用時:12936
select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid
用時:18843
這里,用聚合索引比用一般的主鍵作order by時,速度快了3/10。事實(shí)上,如果數(shù)據(jù)量很小的話,用聚集索引作為排序列要比使用非聚集索引速度快得明顯的多;而數(shù)據(jù)量如果很大的話,如10萬以上,則二者的速度差別不明顯。
3、使用聚合索引內(nèi)的時間段,搜索時間會按數(shù)據(jù)占整個數(shù)據(jù)表的百分比成比例減少,而無論聚合索引使用了多少個
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'
用時:6343毫秒(提取100萬條)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'
用時:3170毫秒(提取50萬條)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'
用時:3326毫秒(和上句的結(jié)果一模一樣。如果采集的數(shù)量一樣,那么用大于號和等于號是一樣的)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6'
用時:3280毫秒
4 、日期列不會因?yàn)橛蟹置氲妮斎攵鴾p慢查詢速度
下面的例子中,共有100萬條數(shù)據(jù),2004年1月1日以后的數(shù)據(jù)有50萬條,但只有兩個不同的日期,日期精確到日;之前有數(shù)據(jù)50萬條,有5000個不同的日期,日期精確到秒。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi
用時:6390毫秒
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi
用時:6453毫秒
(五)其他注意事項(xiàng)
“水可載舟,亦可覆舟”,索引也一樣。索引有助于提高檢索性能,但過多或不當(dāng)?shù)乃饕矔?dǎo)致系統(tǒng)低效。因?yàn)橛脩粼诒碇忻考舆M(jìn)一個索引,數(shù)據(jù)庫就要做更多的工作。過多的索引甚至?xí)?dǎo)致索引碎片。
所以說,我們要建立一個“適當(dāng)”的索引體系,特別是對聚合索引的創(chuàng)建,更應(yīng)精益求精,以使您的數(shù)據(jù)庫能得到高性能的發(fā)揮。
當(dāng)然,在實(shí)踐中,作為一個盡職的數(shù)據(jù)庫管理員,您還要多測試一些方案,找出哪種方案效率最高、最為有效。
二、改善SQL語句
很多人不知道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句先后上看,這兩個語句的確是不一樣,如果tID是一個聚合索引,那么后一句僅僅從表的10000條以后的記錄中查找就行了;而前一句則要先從全表中查找看有幾個name='zhangsan'的,而后再根據(jù)限制條件條件tID>10000來提出查詢結(jié)果。
事實(shí)上,這樣的擔(dān)心是不必要的。SQL SERVER中有一個“查詢分析優(yōu)化器”,它可以計(jì)算出where子句中的搜索條件并確定哪個索引能縮小表掃描的搜索空間,也就是說,它能實(shí)現(xiàn)自動優(yōu)化。
雖然查詢優(yōu)化器可以根據(jù)where子句自動的進(jìn)行查詢優(yōu)化,但大家仍然有必要了解一下“查詢優(yōu)化器”的工作原理,如非這樣,有時查詢優(yōu)化器就會不按照您的本意進(jìn)行快速查詢。
在查詢分析階段,查詢優(yōu)化器查看查詢的每個階段并決定限制需要掃描的數(shù)據(jù)量是否有用。如果一個階段可以被用作一個掃描參數(shù)(SARG),那么就稱之為可優(yōu)化的,并且可以利用索引快速獲得所需數(shù)據(jù)。
SARG的定義:用于限制搜索的一個操作,因?yàn)樗ǔJ侵敢粋€特定的匹配,一個值得范圍內(nèi)的匹配或者兩個以上條件的AND連接。形式如下:
列名 操作符 <常數(shù) 或 變量>
或
<常數(shù) 或 變量> 操作符列名
列名可以出現(xiàn)在操作符的一邊,而常數(shù)或變量出現(xiàn)在操作符的另一邊。如:
Name=’張三’
價格>5000
5000<價格
Name=’張三’ and 價格>5000
如果一個表達(dá)式不能滿足SARG的形式,那它就無法限制搜索的范圍了,也就是SQL SERVER必須對每一行都判斷它是否滿足WHERE子句中的所有條件。所以一個索引對于不滿足SARG形式的表達(dá)式來說是無用的。
介紹完SARG后,我們來總結(jié)一下使用SARG以及在實(shí)踐中遇到的和某些資料上結(jié)論不同的經(jīng)驗(yàn):
1、Like語句是否屬于SARG取決于所使用的通配符的類型
如:name like ‘張%’ ,這就屬于SARG
而:name like ‘%張’ ,就不屬于SARG。
原因是通配符%在字符串的開通使得索引無法使用。
2、or 會引起全表掃描
Name=’張三’ and 價格>5000 符號SARG,而:Name=’張三’ or 價格>5000 則不符合SARG。使用or會引起全表掃描。
3、非操作符、函數(shù)引起的不滿足SARG形式的語句
不滿足SARG形式的語句最典型的情況就是包括非操作符的語句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外還有函數(shù)。下面就是幾個不滿足SARG形式的例子:
ABS(價格)<5000
Name like ‘%三’
有些表達(dá)式,如:
WHERE 價格*2>5000
SQL SERVER也會認(rèn)為是SARG,SQL SERVER會將此式轉(zhuǎn)化為:
WHERE 價格>2500/2
但我們不推薦這樣使用,因?yàn)橛袝rSQL SERVER不能保證這種轉(zhuǎn)化與原始表達(dá)式是完全等價的。
4、IN 的作用相當(dāng)與OR
語句:
Select * from table1 where tid in (2,3)
和
Select * from table1 where tid=2 or tid=3
是一樣的,都會引起全表掃描,如果tid上有索引,其索引也會失效。
5、盡量少用NOT
6、exists 和 in 的執(zhí)行效率是一樣的
很多資料上都顯示說,exists要比in的執(zhí)行效率要高,同時應(yīng)盡可能的用not exists來代替not in。但事實(shí)上,我試驗(yàn)了一下,發(fā)現(xiàn)二者無論是前面帶不帶not,二者之間的執(zhí)行效率都是一樣的。因?yàn)樯婕白硬樵儯覀冊囼?yàn)這次用SQL SERVER自帶的pubs數(shù)據(jù)庫。運(yùn)行前我們可以把SQL SERVER的statistics I/O狀態(tài)打開。
(1)select title,price from titles where title_id in (select title_id from sales where qty>30)
該句的執(zhí)行結(jié)果為:
表 'sales'。掃描計(jì)數(shù) 18,邏輯讀 56 次,物理讀 0 次,預(yù)讀 0 次。
表 'titles'。掃描計(jì)數(shù) 1,邏輯讀 2 次,物理讀 0 次,預(yù)讀 0 次。
(2)select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)
第二句的執(zhí)行結(jié)果為:
表 'sales'。掃描計(jì)數(shù) 18,邏輯讀 56 次,物理讀 0 次,預(yù)讀 0 次。
表 'titles'。掃描計(jì)數(shù) 1,邏輯讀 2 次,物理讀 0 次,預(yù)讀 0 次。
我們從此可以看到用exists和用in的執(zhí)行效率是一樣的。
7、用函數(shù)charindex()和前面加通配符%的LIKE執(zhí)行效率一樣
前面,我們談到,如果在LIKE前面加上通配符%,那么將會引起全表掃描,所以其執(zhí)行效率是低下的。但有的資料介紹說,用函數(shù)charindex()來代替LIKE速度會有大的提升,經(jīng)我試驗(yàn),發(fā)現(xiàn)這種說明也是錯誤的:
select gid,title,fariqi,reader from tgongwen where charindex('刑偵支隊(duì)',reader)>0 and fariqi>'2004-5-5'
用時:7秒,另外:掃描計(jì)數(shù) 4,邏輯讀 7155 次,物理讀 0 次,預(yù)讀 0 次。
select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑偵支隊(duì)' + '%' and fariqi>'2004-5-5'
用時:7秒,另外:掃描計(jì)數(shù) 4,邏輯讀 7155 次,物理讀 0 次,預(yù)讀 0 次。
8、union并不絕對比or的執(zhí)行效率高
我們前面已經(jīng)談到了在where子句中使用or會引起全表掃描,一般的,我所見過的資料都是推薦這里用union來代替or。事實(shí)證明,這種說法對于大部分都是適用的。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000
用時:68秒。掃描計(jì)數(shù) 1,邏輯讀 404008 次,物理讀 283 次,預(yù)讀 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秒。掃描計(jì)數(shù) 8,邏輯讀 67489 次,物理讀 216 次,預(yù)讀 7499 次。
看來,用union在通常情況下比用or的效率要高的多。
但經(jīng)過試驗(yàn),筆者發(fā)現(xiàn)如果or兩邊的查詢列是一樣的話,那么用union則反倒和用or的執(zhí)行速度差很多,雖然這里union掃描的是索引,而or掃描的是全表。
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'
用時:6423毫秒。掃描計(jì)數(shù) 2,邏輯讀 14726 次,物理讀 1 次,預(yù)讀 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毫秒。掃描計(jì)數(shù) 8,邏輯讀 14806 次,物理讀 108 次,預(yù)讀 1144 次。
9、字段提取要按照“需多少、提多少”的原則,避免“select *”
我們來做一個試驗(yàn):
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毫秒
由此看來,我們每少提取一個字段,數(shù)據(jù)的提取速度就會有相應(yīng)的提升。提升的速度還要看您舍棄的字段的大小來判斷。
10、count(*)不比count(字段)慢
某些資料上說:用*會統(tǒng)計(jì)所有列,顯然要比一個世界的列名效率低。這種說法其實(shí)是沒有根據(jù)的。我們來看:
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(主鍵)的速度是相當(dāng)?shù)模鴆ount(*)卻比其他任何除主鍵以外的字段匯總速度要快,而且字段越長,匯總的速度就越慢。我想,如果用count(*), SQL SERVER可能會自動查找最小字段來匯總的。當(dāng)然,如果您直接寫count(主鍵)將會來的更直接些。
11、order by按聚集索引列排序效率最高
我們來看:(gid是主鍵,fariqi是聚合索引列)
select top 10000 gid,fariqi,reader,title from tgongwen
用時:196 毫秒。 掃描計(jì)數(shù) 1,邏輯讀 289 次,物理讀 1 次,預(yù)讀 1527 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc
用時:4720毫秒。 掃描計(jì)數(shù) 1,邏輯讀 41956 次,物理讀 0 次,預(yù)讀 1287 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用時:4736毫秒。 掃描計(jì)數(shù) 1,邏輯讀 55350 次,物理讀 10 次,預(yù)讀 775 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc
用時:173毫秒。 掃描計(jì)數(shù) 1,邏輯讀 290 次,物理讀 0 次,預(yù)讀 0 次。
select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc
用時:156毫秒。 掃描計(jì)數(shù) 1,邏輯讀 289 次,物理讀 0 次,預(yù)讀 0 次。
從以上我們可以看出,不排序的速度以及邏輯讀次數(shù)都是和“order by 聚集索引列” 的速度是相當(dāng)?shù)模@些都比“order by 非聚集索引列”的查詢速度是快得多的。
同時,按照某個字段進(jìn)行排序的時候,無論是正序還是倒序,速度是基本相當(dāng)?shù)摹?/P>
12、高效的TOP
事實(shí)上,在查詢和提取超大容量的數(shù)據(jù)集時,影響數(shù)據(jù)庫響應(yīng)時間的最大因素不是數(shù)據(jù)查找,而是物理的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
這條語句,從理論上講,整條語句的執(zhí)行時間應(yīng)該比子句的執(zhí)行時間長,但事實(shí)相反。因?yàn)椋泳鋱?zhí)行后返回的是10000條記錄,而整條語句僅返回10條語句,所以影響數(shù)據(jù)庫響應(yīng)時間最大的因素是物理I/O操作。而限制物理I/O操作此處的最有效方法之一就是使用TOP關(guān)鍵詞了。TOP關(guān)鍵詞是SQL SERVER中經(jīng)過系統(tǒng)優(yōu)化過的一個用來提取前幾條或前幾個百分比數(shù)據(jù)的詞。經(jīng)筆者在實(shí)踐中的應(yīng)用,發(fā)現(xiàn)TOP確實(shí)很好用,效率也很高。但這個詞在另外一個大型數(shù)據(jù)庫ORACLE中卻沒有,這不能說不是一個遺憾,雖然在ORACLE中可以用其他方法(如:rownumber)來解決。在以后的關(guān)于“實(shí)現(xiàn)千萬級數(shù)據(jù)的分頁顯示存儲過程”的討論中,我們就將用到TOP這個關(guān)鍵詞。
到此為止,我們上面討論了如何實(shí)現(xiàn)從大容量的數(shù)據(jù)庫中快速地查詢出您所需要的數(shù)據(jù)方法。當(dāng)然,我們介紹的這些方法都是“軟”方法,在實(shí)踐中,我們還要考慮各種“硬”因素,如:網(wǎng)絡(luò)性能、服務(wù)器的性能、操作系統(tǒng)的性能,甚至網(wǎng)卡、交換機(jī)等。
三、實(shí)現(xiàn)小數(shù)據(jù)量和海量數(shù)據(jù)的通用分頁顯示存儲過程
建立一個web 應(yīng)用,分頁瀏覽功能必不可少。這個問題是數(shù)據(jù)庫處理中十分常見的問題。經(jīng)典的數(shù)據(jù)分頁方法是:ADO 紀(jì)錄集分頁法,也就是利用ADO自帶的分頁功能(利用游標(biāo))來實(shí)現(xiàn)分頁。但這種分頁方法僅適用于較小數(shù)據(jù)量的情形,因?yàn)橛螛?biāo)本身有缺點(diǎn):游標(biāo)是存放在內(nèi)存中,很費(fèi)內(nèi)存。游標(biāo)一建立,就將相關(guān)的記錄鎖住,直到取消游標(biāo)。游標(biāo)提供了對特定集合中逐行掃描的手段,一般使用游標(biāo)來逐行遍歷數(shù)據(jù),根據(jù)取出數(shù)據(jù)條件的不同進(jìn)行不同的操作。而對于多表和大表中定義的游標(biāo)(大的數(shù)據(jù)集合)循環(huán)很容易使程序進(jìn)入一個漫長的等待甚至死機(jī)。
更重要的是,對于非常大的數(shù)據(jù)模型而言,分頁檢索時,如果按照傳統(tǒng)的每次都加載整個數(shù)據(jù)源的方法是非常浪費(fèi)資源的。現(xiàn)在流行的分頁方法一般是檢索頁面大小的塊區(qū)的數(shù)據(jù),而非檢索所有的數(shù)據(jù),然后單步執(zhí)行當(dāng)前行。
最早較好地實(shí)現(xiàn)這種根據(jù)頁面大小和頁碼來提取數(shù)據(jù)的方法大概就是“俄羅斯存儲過程”。這個存儲過程用了游標(biāo),由于游標(biāo)的局限性,所以這個方法并沒有得到大家的普遍認(rèn)可。
后來,網(wǎng)上有人改造了此存儲過程,下面的存儲過程就是結(jié)合我們的辦公自動化實(shí)例寫的分頁存儲過程:
CREATE procedure pagination1
(@pagesize int, --頁面大小,如每頁存儲20條記錄
@pageindex int --當(dāng)前頁碼
)
as
set nocount on
begin
declare @indextable table(id int identity(1,1),nid int) --定義表變量
declare @PageLowerBound int --定義此頁的底碼
declare @PageUpperBound int --定義此頁的頂碼
set @PageLowerBound=(@pageindex-1)*@pagesize
set @PageUpperBound=@PageLowerBound+@pagesize
set rowcount @PageUpperBound
insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc
select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid
and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id
end
set nocount off
以上存儲過程運(yùn)用了SQL SERVER的最新技術(shù)――表變量。應(yīng)該說這個存儲過程也是一個非常優(yōu)秀的分頁存儲過程。當(dāng)然,在這個過程中,您也可以把其中的表變量寫成臨時表:CREATE TABLE #Temp。但很明顯,在SQL SERVER中,用臨時表是沒有用表變量快的。所以筆者剛開始使用這個存儲過程時,感覺非常的不錯,速度也比原來的ADO的好。但后來,我又發(fā)現(xiàn)了比此方法更好的方法。
筆者曾在網(wǎng)上看到了一篇小短文《從數(shù)據(jù)表中取出第n條到第m條的記錄的方法》,全文如下:
從publish 表中取出第 n 條到第 m 條的記錄:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
(SELECT TOP n-1 id
FROM publish))
id 為publish 表的關(guān)鍵字
我當(dāng)時看到這篇文章的時候,真的是精神為之一振,覺得思路非常得好。等到后來,我在作辦公自動化系統(tǒng)(ASP.NET+ C#+SQL SERVER)的時候,忽然想起了這篇文章,我想如果把這個語句改造一下,這就可能是一個非常好的分頁存儲過程。于是我就滿網(wǎng)上找這篇文章,沒想到,文章還沒找到,卻找到了一篇根據(jù)此語句寫的一個分頁存儲過程,這個存儲過程也是目前較為流行的一種分頁存儲過程,我很后悔沒有爭先把這段文字改造成存儲過程:
CREATE PROCEDURE pagination2
(
@SQL nVARCHAR(4000), --不帶排序語句的SQL語句
@Page int, --頁碼
@RecsPerPage int, --每頁容納的記錄數(shù)
@ID VARCHAR(255), --需要排序的不重復(fù)的ID號
@Sort VARCHAR(255) --排序字段及規(guī)則
)
AS
DECLARE @Str nVARCHAR(4000)
SET @Str='SELECT TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+') T WHERE T.'+@ID+'NOT IN
(SELECT TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM ('+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort
PRINT @Str
EXEC sp_ExecuteSql @Str
GO
其實(shí),以上語句可以簡化為:
SELECT TOP 頁大小 *
FROM Table1
WHERE (ID NOT IN
(SELECT TOP 頁大小*頁數(shù) id
FROM 表
ORDER BY id))
ORDER BY ID
但這個存儲過程有一個致命的缺點(diǎn),就是它含有NOT IN字樣。雖然我可以把它改造為:
SELECT TOP 頁大小 *
FROM Table1
WHERE not exists
(select * from (select top (頁大小*頁數(shù)) * from table1 order by id) b where b.id=a.id )
order by id
即,用not exists來代替not in,但我們前面已經(jīng)談過了,二者的執(zhí)行效率實(shí)際上是沒有區(qū)別的。
既便如此,用TOP 結(jié)合NOT IN的這個方法還是比用游標(biāo)要來得快一些。
雖然用not exists并不能挽救上個存儲過程的效率,但使用SQL SERVER中的TOP關(guān)鍵字卻是一個非常明智的選擇。因?yàn)榉猪搩?yōu)化的最終目的就是避免產(chǎn)生過大的記錄集,而我們在前面也已經(jīng)提到了TOP的優(yōu)勢,通過TOP 即可實(shí)現(xiàn)對數(shù)據(jù)量的控制。
在分頁算法中,影響我們查詢速度的關(guān)鍵因素有兩點(diǎn):TOP和NOT IN。TOP可以提高我們的查詢速度,而NOT IN會減慢我們的查詢速度,所以要提高我們整個分頁算法的速度,就要徹底改造NOT IN,同其他方法來替代它。
我們知道,幾乎任何字段,我們都可以通過max(字段)或min(字段)來提取某個字段中的最大或最小值,所以如果這個字段不重復(fù),那么就可以利用這些不重復(fù)的字段的max或min作為分水嶺,使其成為分頁算法中分開每頁的參照物。在這里,我們可以用操作符“>”或“<”號來完成這個使命,使查詢語句符合SARG形式。如:
Select top 10 * from table1 where id>200
于是就有了如下分頁方案:
select top 頁大小 *
from table1
where id>
(select max (id) from
(select top ((頁碼-1)*頁大小) id from table1 order by id) as T
)
order by id
在選擇即不重復(fù)值,又容易分辨大小的列時,我們通常會選擇主鍵。下表列出了筆者用有著1000萬數(shù)據(jù)的辦公自動化系統(tǒng)中的表,在以GID(GID是主鍵,但并不是聚集索引。)為排序列、提取gid,fariqi,title字段,分別以第1、10、100、500、1000、1萬、10萬、25萬、50萬頁為例,測試以上三種分頁方案的執(zhí)行速度:(單位:毫秒)
頁 碼
方案1
方案2
方案3
1
60
30
76
10
46
16
63
100
1076
720
130
500
540
12943
83
1000
17110
470
250
1萬
24796
4500
140
10萬
38326
42283
1553
25萬
28140
128720
2330
50萬
121686
127846
7168
從上表中,我們可以看出,三種存儲過程在執(zhí)行100頁以下的分頁命令時,都是可以信任的,速度都很好。但第一種方案在執(zhí)行分頁1000頁以上后,速度就降了下來。第二種方案大約是在執(zhí)行分頁1萬頁以上后速度開始降了下來。而第三種方案卻始終沒有大的降勢,后勁仍然很足。
在確定了第三種分頁方案后,我們可以據(jù)此寫一個存儲過程。大家知道SQL SERVER的存儲過程是事先編譯好的SQL語句,它的執(zhí)行效率要比通過WEB頁面?zhèn)鱽淼腟QL語句的執(zhí)行效率要高。下面的存儲過程不僅含有分頁方案,還會根據(jù)頁面?zhèn)鱽淼膮?shù)來確定是否進(jìn)行數(shù)據(jù)總數(shù)統(tǒng)計(jì)。
-- 獲取指定頁的數(shù)據(jù)
CREATE PROCEDURE pagination3
@tblName varchar(255), -- 表名
@strGetFields varchar(1000) = '*', -- 需要返回的列
@fldName varchar(255)='', -- 排序的字段名
@PageSize int = 10, -- 頁尺寸
@PageIndex int = 1, -- 頁碼
@doCount bit = 0, -- 返回記錄總數(shù), 非 0 值則返回
@OrderType bit = 0, -- 設(shè)置排序類型, 非 0 值則降序
@strWhere varchar(1500) = '' -- 查詢條件 (注意: 不要加 where)
AS
declare @strSQL varchar(5000) -- 主語句
declare @strTmp varchar(110) -- 臨時變量
declare @strOrder varchar(400) -- 排序類型
if @doCount != 0
begin
if @strWhere !=''
set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere
else
set @strSQL = "select count(*) as Total from [" + @tblName + "]"
end
--以上代碼的意思是如果@doCount傳遞過來的不是0,就執(zhí)行總數(shù)統(tǒng)計(jì)。以下的所有代碼都是@doCount為0的情況
else
begin
if @OrderType != 0
begin
set @strTmp = "<(select min"
set @strOrder = " order by [" + @fldName +"] desc"
--如果@OrderType不是0,就執(zhí)行降序,這句很重要!
end
else
begin
set @strTmp = ">(select max"
set @strOrder = " order by [" + @fldName +"] asc"
end
if @PageIndex = 1
begin
if @strWhere != ''
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from [" + @tblName + "] where " + @strWhere + " " + @strOrder
else
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["+ @tblName + "] "+ @strOrder
--如果是第一頁就執(zhí)行以上代碼,這樣會加快執(zhí)行速度
end
else
begin
--以下代碼賦予了@strSQL以真正執(zhí)行的SQL代碼
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["
+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder
if @strWhere != ''
set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ " from ["
+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["
+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["
+ @fldName + "] from [" + @tblName + "] where " + @strWhere + " "
+ @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder
end
end
exec (@strSQL)
GO
上面的這個存儲過程是一個通用的存儲過程,其注釋已寫在其中了。
在大數(shù)據(jù)量的情況下,特別是在查詢最后幾頁的時候,查詢時間一般不會超過9秒;而用其他存儲過程,在實(shí)踐中就會導(dǎo)致超時,所以這個存儲過程非常適用于大容量數(shù)據(jù)庫的查詢。
筆者希望能夠通過對以上存儲過程的解析,能給大家?guī)硪欢ǖ膯⑹荆⒔o工作帶來一定的效率提升,同時希望同行提出更優(yōu)秀的實(shí)時數(shù)據(jù)分頁算法。
四、聚集索引的重要性和如何選擇聚集索引
在上一節(jié)的標(biāo)題中,筆者寫的是:實(shí)現(xiàn)小數(shù)據(jù)量和海量數(shù)據(jù)的通用分頁顯示存儲過程。這是因?yàn)樵趯⒈敬鎯^程應(yīng)用于“辦公自動化”系統(tǒng)的實(shí)踐中時,筆者發(fā)現(xiàn)這第三種存儲過程在小數(shù)據(jù)量的情況下,有如下現(xiàn)象:
1、分頁速度一般維持在1秒和3秒之間。
2、在查詢最后一頁時,速度一般為5秒至8秒,哪怕分頁總數(shù)只有3頁或30萬頁。
雖然在超大容量情況下,這個分頁的實(shí)現(xiàn)過程是很快的,但在分前幾頁時,這個1-3秒的速度比起第一種甚至沒有經(jīng)過優(yōu)化的分頁方法速度還要慢,借用戶的話說就是“還沒有ACCESS數(shù)據(jù)庫速度快”,這個認(rèn)識足以導(dǎo)致用戶放棄使用您開發(fā)的系統(tǒng)。
筆者就此分析了一下,原來產(chǎn)生這種現(xiàn)象的癥結(jié)是如此的簡單,但又如此的重要:排序的字段不是聚集索引!
本篇文章的題目是:“查詢優(yōu)化及分頁算法方案”。筆者只所以把“查詢優(yōu)化”和“分頁算法”這兩個聯(lián)系不是很大的論題放在一起,就是因?yàn)槎叨夹枰粋€非常重要的東西――聚集索引。
在前面的討論中我們已經(jīng)提到了,聚集索引有兩個最大的優(yōu)勢:
1、以最快的速度縮小查詢范圍。
2、以最快的速度進(jìn)行字段排序。
第1條多用在查詢優(yōu)化時,而第2條多用在進(jìn)行分頁時的數(shù)據(jù)排序。
而聚集索引在每個表內(nèi)又只能建立一個,這使得聚集索引顯得更加的重要。聚集索引的挑選可以說是實(shí)現(xiàn)“查詢優(yōu)化”和“高效分頁”的最關(guān)鍵因素。
但要既使聚集索引列既符合查詢列的需要,又符合排序列的需要,這通常是一個矛盾。
筆者前面“索引”的討論中,將fariqi,即用戶發(fā)文日期作為了聚集索引的起始列,日期的精確度為“日”。這種作法的優(yōu)點(diǎn),前面已經(jīng)提到了,在進(jìn)行劃時間段的快速查詢中,比用ID主鍵列有很大的優(yōu)勢。
但在分頁時,由于這個聚集索引列存在著重復(fù)記錄,所以無法使用max或min來最為分頁的參照物,進(jìn)而無法實(shí)現(xiàn)更為高效的排序。而如果將ID主鍵列作為聚集索引,那么聚集索引除了用以排序之外,沒有任何用處,實(shí)際上是浪費(fèi)了聚集索引這個寶貴的資源。
為解決這個矛盾,筆者后來又添加了一個日期列,其默認(rèn)值為getdate()。用戶在寫入記錄時,這個列自動寫入當(dāng)時的時間,時間精確到毫秒。即使這樣,為了避免可能性很小的重合,還要在此列上創(chuàng)建UNIQUE約束。將此日期列作為聚集索引列。
有了這個時間型聚集索引列之后,用戶就既可以用這個列查找用戶在插入數(shù)據(jù)時的某個時間段的查詢,又可以作為唯一列來實(shí)現(xiàn)max或min,成為分頁算法的參照物。
經(jīng)過這樣的優(yōu)化,筆者發(fā)現(xiàn),無論是大數(shù)據(jù)量的情況下還是小數(shù)據(jù)量的情況下,分頁速度一般都是幾十毫秒,甚至0毫秒。而用日期段縮小范圍的查詢速度比原來也沒有任何遲鈍。
聚集索引是如此的重要和珍貴,所以筆者總結(jié)了一下,一定要將聚集索引建立在:
1、您最頻繁使用的、用以縮小查詢范圍的字段上;
2、您最頻繁使用的、需要排序的字段上。
結(jié)束語:
希望這篇文章不僅能夠給大家的工作帶來一定的幫助,也希望能讓大家能夠體會到分析問題的方法;最重要的是,希望這篇文章能夠拋磚引玉,掀起大家的學(xué)習(xí)和討論的興趣,以共同促進(jìn)。
最后需要說明的是,在試驗(yàn)中,發(fā)現(xiàn)用戶在進(jìn)行大數(shù)據(jù)量查詢的時候,對數(shù)據(jù)庫速度影響最大的不是內(nèi)存大小,而是CPU。在我的P4 2.4機(jī)器上試驗(yàn)的時候,查看“資源管理器”,CPU經(jīng)常出現(xiàn)持續(xù)到100%的現(xiàn)象,而內(nèi)存用量卻并沒有改變或者說沒有大的改變。即使在我們的HP ML 350 G3服務(wù)器上試驗(yàn)時,CPU峰值也能達(dá)到90%,一般持續(xù)在70%左右。
本文的試驗(yàn)數(shù)據(jù)都是來自我們的HP ML 350服務(wù)器。服務(wù)器配置:雙Inter Xeon 超線程 CPU 2.4G,內(nèi)存1G,操作系統(tǒng)Windows Server 2003 Enterprise Edition,數(shù)據(jù)庫SQL Server 2000 SP3。
轉(zhuǎn)載完畢.
作者補(bǔ)充:
1.columns in('aa','bb')
他等于columns = 'aa' or columns ='bb' 他先去查詢columns ='aa'放在一個臨時的空間里,然后等columns ='bb'查詢完后,做個or查詢得出結(jié)果.
至于效率的話,在columns建立索引的話, columns ='aa' or columns ='bb'要來的效率高
語法分析器會將columns in('aa','bb')轉(zhuǎn)化
為columns ='aa' or columns ='bb'來執(zhí)行。我們期望它會根據(jù)每個or子句分別查找,再將結(jié)果
相加,這樣可以利用columns 上的索引;但實(shí)際上(根據(jù)showplan),它卻采用了"OR策略"
,即先取出滿足每個or子句的行,存入臨時數(shù)據(jù)庫的工作表中,再建立唯一索引以去掉
重復(fù)行,最后從這個臨時表中計(jì)算結(jié)果。因此,實(shí)際過程沒有利用columns 上索引,并且完
成時間還要受tempdb數(shù)據(jù)庫性能的影響。
2.效率從高到低 count(1)>count(*)>count([id])
3.select max(cols) from table1 的效率>= select top 1 cols from table1 order by cols desc
4.在where 做并列條件句時,where cols1='aa' and cols2='bb'
如果cols1 ='aa' 占95% cols2占5%的話,把cols2='bb'放在前面 ,因?yàn)樗跈z索cols ='bb'的時候他只需查那5%,然后條件成立的話,去在這5%的紀(jì)錄里
去查找cols1 ='aa'
5.避免用if條件句,可以用or來替代.
declare @vsql varchar(200)
set @vsql ='Renaski'
select * from titles where @vsql ='Renaski' or price = 11.9500
如果@vsql為Renaski則把所有的紀(jì)錄都選出來,如果不是的話,則只查詢price = 11.9500 的紀(jì)錄.
6.任何對列的操作都將導(dǎo)致表掃描,它包括數(shù)據(jù)庫函數(shù)、計(jì)算表達(dá)式等等,查詢時
要盡可能將操作移至等號右邊。
7.盡量避免使用游標(biāo).
如果使用了游標(biāo),就要盡量避免在游標(biāo)循環(huán)中再進(jìn)行表連接的操作
8.取一個表的紀(jì)錄數(shù)
Select rows from sysindexes where id=object_id(N'titles') and indid<2
效率比
select count(1) from titles來的高.
9.取的一個表的數(shù)據(jù)信息.
SELECT
表名=case when a.colorder=1 then d.name else '' end,
表說明=case when a.colorder=1 then isnull(f.value,'') else '' end,
字段序號=a.colorder,
字段名=a.name,
標(biāo)識=case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else '' end,
主鍵=case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
SELECT name FROM sysindexes WHERE indid in(
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
))) then '√' else '' end,
類型=b.name,
占用字節(jié)數(shù)=a.length,
長度=COLUMNPROPERTY(a.id,a.name,'PRECISION'),
小數(shù)位數(shù)=isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0),
允許空=case when a.isnullable=1 then '√'else '' end,
默認(rèn)值=isnull(e.text,''),
字段說明=isnull(g.[value],''),
索引名稱=isnull(h.索引名稱,''),
索引順序=isnull(h.排序,'')
FROM syscolumns a
left join systypes b on a.xtype=b.xusertype
inner join sysobjects d on a.id=d.id and d.xtype='U' and d.status>=0
left join syscomments e on a.cdefault=e.id
left join sysproperties g on a.id=g.id and a.colid=g.smallid
left join sysproperties f on d.id=f.id and f.smallid=0
left join(--這部分是索引信息,如果要顯示索引與表及字段的對應(yīng)關(guān)系,可以只要此部分
select 索引名稱=a.name,c.id,d.colid
,排序=case indexkey_property(c.id,b.indid,b.keyno,'isdescending')
when 1 then '降序' when 0 then '升序' end
from sysindexes a
join sysindexkeys b on a.id=b.id and a.indid=b.indid
join (--這里的作用是有多個索引時,取索引號最小的那個
select id,colid,indid=min(indid) from sysindexkeys
group by id,colid) b1 on b.id=b1.id and b.colid=b1.colid and b.indid=b1.indid
join sysobjects c on b.id=c.id and c.xtype='U' and c.status>=0
join syscolumns d on b.id=d.id and b.colid=d.colid
where a.indid not in(0,255)
) h on a.id=h.id and a.colid=h.colid
--where d.name='要查詢的表' --如果只查詢指定表,加上此條件
order by a.id,a.colorder
10.創(chuàng)建一個表結(jié)構(gòu).
select * into #b from authors where 1=2;
注意:
#table1
##table1
@table1
局部臨時表
以一個井號(#)開頭的那些表名。只有在創(chuàng)建本地臨時表的連接上才能看到這些表。
全局臨時表
以兩個井號(##)開頭的那些表名。在所有連接上都能看到全局臨時表。如果在創(chuàng)建全局臨時表的連接斷開前沒有顯式地除去這些表,那么只要所有其它任務(wù)停止引用它們,這些表即被除去。當(dāng)創(chuàng)建全局臨時表的連接斷開后,新的任務(wù)不能再引用它們。當(dāng)前的語句一執(zhí)行完,任務(wù)與表之間的關(guān)聯(lián)即被除去;因此通常情況下,只要創(chuàng)建全局臨時表的連接斷開,全局臨時表即被除去。
@和#有和不同:@@在內(nèi)存,#在硬盤。我的體會是只要方便且數(shù)據(jù)量不大,使用@@。
11.視圖
他只是記住要連接,關(guān)聯(lián)列的信息,他不存放任何物理數(shù)據(jù).
在調(diào)用的時候他還是去取各個表中的數(shù)據(jù).
12.盡量不要用text屬性
系統(tǒng)為他專門開辟一個空間來存放.
用t-sql/varchar替代
pl/sql varchar2 替代.
13
GO語句是個命令識別并通過osql和isql和SQL 查詢分析器非T-SQL語句進(jìn)行識別。
如果你使用查詢分析器作為你的主開發(fā)工具,其他語句和庫文件將不會識別GO語句作為一個T-SQL命令
14.
用exec 效率來的高.
declare @sql nvarchar(300)
set @sql='select * from titles'
execute sp_executesql @sql
15,注意你的tempdb,使他自動增長.
16 使用no_log
select * from titles no_logs
17去不重復(fù)紀(jì)錄時,盡量用dictinct
18.盡量避免反復(fù)訪問同一張或幾張表,尤其是數(shù)據(jù)量較大的表,可以考慮先根據(jù)條件提取數(shù)據(jù)到臨時表中,然后再做連接。
19 盡量使用“>=”,不要使用“>”。 他會找到某個確定的數(shù)字進(jìn)行篩選,而>則沒有.
20注意表之間連接的數(shù)據(jù)類型,避免不同類型數(shù)據(jù)之間的連接。
21.可用ASE調(diào)優(yōu)命令:set statistics io on, set statistics time on , set showplan on 等,進(jìn)行優(yōu)化
22.truncate table 刪除數(shù)據(jù)
而不是delete from table
三.死鎖
像SQL server一樣的關(guān)系數(shù)據(jù)庫使用鎖來防止用戶“互相踩到對方的腳趾頭”。也就是說,鎖可以防止用戶造成修改數(shù)據(jù)時的碰撞。當(dāng)一個用戶鎖住一段代碼時候,其它的用戶都不能修改這段數(shù)據(jù)。另外,一個鎖阻止了用戶觀看未被授權(quán)的數(shù)據(jù)修改。用戶必須等待到數(shù)據(jù)修改并保存之后才能夠查看它。數(shù)據(jù)必須使用不同的方法來加鎖。SQL Server 2000使用鎖來實(shí)現(xiàn)多用戶同時修改數(shù)據(jù)庫同一數(shù)據(jù)時的同步控制
如果數(shù)據(jù)量超過200個數(shù)據(jù)頁面(400k),那么系統(tǒng)將會進(jìn)行鎖升級,頁級鎖會升級成表級鎖。
死鎖
一個數(shù)據(jù)庫的死鎖是發(fā)生在兩個或多于兩個訪問一些資源的數(shù)據(jù)庫會話中的,并且這些會話相互之間有依賴關(guān)系。死鎖是可以在任意一個多線程的系統(tǒng)成出現(xiàn)的一個情況,不僅僅局限于關(guān)系數(shù)據(jù)庫管理系統(tǒng)。一個多線程系統(tǒng)中的線程可能需要一個或多個資源(例如,鎖)。如果申請的資源正在被另外一個線程所使用,那么第一個線程就需要等待持有該資源的線程的釋放它所需要的資源。假設(shè)等待線程持有一個那個正擁有線程所依賴的資源。下面的這一段代碼就可以造成死鎖異常現(xiàn)象的發(fā)生:
System.Data.SqlClient.SqlException: Transaction (Process ID 12) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
當(dāng)一個SQL Server的調(diào)用和另外一個資源發(fā)生沖突時就會拋出異常,這個資源持有一個必要的資源。結(jié)果是,一個進(jìn)程就被終止了。當(dāng)進(jìn)程的ID號成為系統(tǒng)的唯一標(biāo)識的時候,這會是一個很平常死鎖的消息錯誤。
鎖的類型
一個數(shù)據(jù)庫系統(tǒng)在許多情況下都有可能鎖數(shù)據(jù)項(xiàng)。其可能性包括:
Rows—數(shù)據(jù)庫表中的一整行
Pages—行的集合(通常為幾kb)
Extents—通常是幾個頁的集合
Table—整個數(shù)據(jù)庫表
Database—被鎖的整個數(shù)據(jù)庫表
除非有其它的說明,數(shù)據(jù)庫根據(jù)情況自己選擇最好的鎖方式。不過值得感謝的是,SQL Server提供了一種避免默認(rèn)行為的方法。這是由鎖提示來完成的。
提示
或許你有過許多如下的經(jīng)歷:需要重設(shè)SQL Server的鎖計(jì)劃,并且加強(qiáng)數(shù)據(jù)庫表中鎖范圍。Tansact-SQL提供了一系列不同級別的鎖提示,你可以在SELECT,INSERT,UPDATE和DELETE中使用它們來告訴SQL Server你需要如何通過重設(shè)任何的系統(tǒng)或事務(wù)級別來鎖表格。可以實(shí)現(xiàn)的提示包括:
FASTFIRSTROW—選取結(jié)果集中的第一行,并將其優(yōu)化
HOLDLOCK—持有一個共享鎖直至事務(wù)完成
NOLOCK—不允許使用共享鎖或獨(dú)享鎖。這可能會造成數(shù)據(jù)重寫或者沒有被確認(rèn)就返回的情況;因此,就有可能使用到臟數(shù)據(jù)。這個提示只能在SELECT中使用。
PAGLOCK—鎖表格
READCOMMITTED—只讀取被事務(wù)確認(rèn)的數(shù)據(jù)。這就是SQL Server的默認(rèn)行為。
READPAST—跳過被其它進(jìn)程鎖住的行,所以返回的數(shù)據(jù)可能會忽略行的內(nèi)容。這也只能在SELECT中使用。
READUNCOMMITTED—等價于NOLOCK.
REPEATABLEREAD—在查詢語句中,對所有數(shù)據(jù)使用鎖。這可以防止其它的用戶更新數(shù)據(jù),但是新的行可能被其它的用戶插入到數(shù)據(jù)中,并且被最新訪問該數(shù)據(jù)的用戶讀取。
ROWLOCK—按照行的級別來對數(shù)據(jù)上鎖。SQL Server通常鎖到頁或者表級別來修改行,所以當(dāng)開發(fā)者使用單行的時候,通常要重設(shè)這個設(shè)置。
SERIALIZABLE—等價于HOLDLOCK.
TABLOCK—按照表級別上鎖。在運(yùn)行多個有關(guān)表級別數(shù)據(jù)操作的時候,你可能需要使用到這個提示。
UPDLOCK—當(dāng)讀取一個表的時候,使用更新鎖來代替共享鎖,并且保持一直擁有這個鎖直至事務(wù)結(jié)束。它的好處是,可以允許你在閱讀數(shù)據(jù)的時候可以不需要鎖,并且以最快的速度更新數(shù)據(jù)。
XLOCK—給所有的資源都上獨(dú)享鎖,直至事務(wù)結(jié)束。
對于數(shù)據(jù)庫死鎖,通常可以通過TRACE FLAG 1204、1205、1206,檢查ERRORLOG里面的輸出,和分析SQLTRACE的執(zhí)行上下文判斷死鎖問題的來由。
TRACEON函數(shù)的第三個參數(shù)設(shè)置為-1,表示不單單針對當(dāng)前connection,而是針對所有包括未來建立的connection。這樣,才夠完全,否則只是監(jiān)視當(dāng)前已經(jīng)建立的數(shù)據(jù)庫連接了。
執(zhí)行下面的話可以把死鎖記錄到Errorlog中:
dbcc traceon (1204, 3605, -1)
go
dbcc tracestatus(-1)
go
得到的輸出為:
DBCC 執(zhí)行完畢。如果 DBCC 輸出了錯誤信息,請與系統(tǒng)管理員聯(lián)系。
TraceFlag Status
--------- ------
1204 1
1205 1
3605 1
(所影響的行數(shù)為 3 行)
DBCC 執(zhí)行完畢。如果 DBCC 輸出了錯誤信息,請與系統(tǒng)管理員聯(lián)系。
此后,你可以查看數(shù)據(jù)庫的例行日志,每隔一段時間,數(shù)據(jù)庫都會檢查死鎖
2004-01-16 18:34:38.50 spid4 ----------------------------------
2004-01-16 18:34:38.50 spid4 Starting deadlock search 1976
2004-01-16 18:34:38.50 spid4 Target Resource Owner:
2004-01-16 18:34:38.50 spid4 ResType:LockOwner Stype:'OR' Mode: U SPID:55 ECID:0 Ec:(0xAA577570) Value:0x4c25cba0
2004-01-16 18:34:38.50 spid4 Node:1 ResType:LockOwner Stype:'OR' Mode: U SPID:55 ECID:0 Ec:(0xAA577570) Value:0x4c25cba0
2004-01-16 18:34:38.50 spid4 Node:2 ResType:LockOwner Stype:'OR' Mode: U SPID:71 ECID:0 Ec:(0xABF07570) Value:0x9bd0ba00
2004-01-16 18:34:38.50 spid4
2004-01-16 18:34:38.50 spid4 -- next branch --
2004-01-16 18:34:38.50 spid4 Node:2 ResType:LockOwner Stype:'OR' Mode: U SPID:71 ECID:0 Ec:(0xABF07570) Value:0x9bd0ba00
2004-01-16 18:34:38.50 spid4
2004-01-16 18:34:38.50 spid4
2004-01-16 18:34:38.50 spid4 End deadlock search 1976 ... a deadlock was not found.
2004-01-16 18:34:38.50 spid4 ----------------------------------
DBCC TRACEON打開(啟用)指定的跟蹤標(biāo)記。
注釋跟蹤標(biāo)記用于自定義某些控制 Microsoft? SQL Server? 操作方式的特性。跟蹤標(biāo)記在服務(wù)器中一直保持啟用狀態(tài),直到通過執(zhí)行 DBCC TRACEOFF 語句對其禁用為止。在發(fā)出 DBCC TRACEON 語句之前,連入到服務(wù)器的新連接看不到任何跟蹤標(biāo)記。一旦發(fā)出該語句,該連接就能看到服務(wù)器中當(dāng)前啟用的所有跟蹤標(biāo)記(即使這些標(biāo)記是由其它連接啟用)。
跟蹤標(biāo)記跟蹤標(biāo)記用于臨時設(shè)置服務(wù)器的特定特征或關(guān)閉特定行為。如果啟動 Microsoft? SQL Server 時設(shè)置了跟蹤標(biāo)記 3205,將禁用磁帶驅(qū)動程序的硬件壓縮。跟蹤標(biāo)記經(jīng)常用于診斷性能問題,或調(diào)試存儲過程或復(fù)雜的計(jì)算機(jī)系統(tǒng)。
下列跟蹤標(biāo)記在 SQL Server 中可用。跟蹤標(biāo)記 描述 1204 返回參與死鎖的鎖的類型以及當(dāng)前受影響的命令。
實(shí)際上可以在“錯誤 1000 -1999”中找到他們:
1204 19 SQL Server 此時無法獲取 LOCK 資源。請?jiān)诨顒佑脩魯?shù)較少時重新運(yùn)行您的語句,或者請求系統(tǒng)管理員檢查 SQL Server 鎖和內(nèi)存配置。
1205 13 事務(wù)(進(jìn)程 ID %1!)與另一個進(jìn)程已被死鎖在資源 {%2!} 上,且該事務(wù)已被選作死鎖犧牲品。請重新運(yùn)行該事務(wù)。
1206 18 事務(wù)管理器已取消了分布式事務(wù)。
需要指出的是對鎖的升級,完全是由系統(tǒng)自行判斷的,而非人為.如果要避免死鎖的話,其根本還在與數(shù)據(jù)庫的設(shè)計(jì)上.