筆者在工作實踐中發現
不良的SQL往往來自於不恰當的索引設計
不充份的連接條件和不可優化的where子句
以下就對數據庫優化問題進行了介紹
需要的朋友可以參考下
人們在使用SQL時往往會陷入一個誤區即太關注於所得的結果是否正確而忽略了不同的實現方法之間可能存在的性能差異這種性能差異在大型的或是復雜的數據庫環境中(如聯機事務處理OLTP或決策支持系統DSS)中表現得尤為明顯
筆者在工作實踐中發現不良的SQL往往來自於不恰當的索引設計不充份的連接條件和不可優化的where子句
在對它們進行適當的優化後
其運行速度有了明顯地提高!
下面將從這三個方面分別進行總結
為了更直觀地說明問題
所有實例中的SQL運行時間均經過測試
不超過1秒的均表示為(<
秒)
測試環境: 主機
HP LH II
主頻
MHZ
內存
兆
操作系統
Operserver
數據庫
Sybase
一不合理的索引設計
例
表record有
行
試看在不同的索引下
下面幾個 SQL的運行情況
在date上建有一非個群集索引
select count(*) from record where date >
and date <
and amount >
(
秒)
select date
sum(amount) from record group by date(
秒)
select count(*) from record where date >
and place in (
BJ
SH
) (
秒)
分析
date上有大量的重復值
在非群集索引下
數據在物理上隨機存放在數據頁上
在范圍查找時
必須執行一次表掃描才能找到這一范圍內的全部行
在date上的一個群集索引
select count(*) from record where date >
and date <
and amount >
(
秒)
select date
sum(amount) from record group by date(
秒)
select count(*) from record where date >
and place in (
BJ
SH
)(
秒)
分析
在群集索引下
數據在物理上按順序在數據頁上
重復值也排列在一起
因而在范圍查找時
可以先找到這個范圍的起末點
且只在這個范圍內掃描數據頁
避免了大范圍掃描
提高了查詢速度
在place
date
amount上的組合索引
select count(*) from record where date >
and date <
and amount >
(
秒)
select date
sum(amount) from record group by date(
秒)
select count(*) from record where date >
and place in (
BJ
SH
)(<
秒)
分析
這是一個不很合理的組合索引
因為它的前導列是place
第一和第二條SQL沒有引用place
因此也沒有利用上索引
第三個SQL使用了place
且引用的所有列都包含在組合索引中
形成了索引覆蓋
所以它的速度是非常快的
在date
place
amount上的組合索引
select count(*) from record where date >
and date <
and amount >
(<
秒)
select date
sum(amount) from record group by date(
秒)
select count(*) from record where date >
and place in (
BJ
SH
)(<
秒)
分析
這是一個合理的組合索引
它將date作為前導列
使每個SQL都可以利用索引
並且在第一和第三個SQL中形成了索引覆蓋
因而性能達到了最優
總結
缺省情況下建立的索引是非群集索引
但有時它並不是最佳的
合理的索引設計要建立在對各種查詢的分析和預測上
一般來說
①
有大量重復值
且經常有范圍查詢(between
>
<
>=
< =)和order by
group by發生的列
可考慮建立群集索引
②
經常同時存取多列
且每列都含有重復值可考慮建立組合索引
③
組合索引要盡量使關鍵查詢形成索引覆蓋
其前導列一定是使用最頻繁的列
二不充份的連接條件
例
表card有
行
在card_no上有一個非聚集索引
表account有
行
在account_no上有一個非聚集索引
試看在不同的表連接條件下
兩個SQL的執行情況
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(<
秒)
分析
在第一個連接條件下
最佳查詢方案是將account作外層表
card作內層表
利用card上的索引
其I/O次數可由以下公式估算為
外層表account上的
頁+(外層表account的
行*內層表card上對應外層表第一行所要查找的
頁)=
次I/O
在第二個連接條件下
最佳查詢方案是將card作外層表
account作內層表
利用account上的索引
其I/O次數可由以下公式估算為
外層表card上的
頁+(外層表card的
行*內層表account上對應外層表每一行所要查找的
頁)=
次I/O
可見
只有充份的連接條件
真正的最佳方案才會被執行
總結
多表操作在被實際執行前
查詢優化器會根據連接條件
列出幾組可能的連接方案並從中找出系統開銷最小的最佳方案
連接條件要充份考慮帶有索引的表
行數多的表
內外表的選擇可由公式
外層表中的匹配行數*內層表中每一次查找的次數確定
乘積最小為最佳方案
查看執行方案的方法
用set showplanon
打開showplan選項
就可以看到連接順序
使用何種索引的信息
想看更詳細的信息
需用sa角色執行dbcc(
)
三不可優化的where子句
例
下列SQL條件語句中的列都建有恰當的索引
但執行速度卻非常慢
select * from record wheresubstring(card_no
)=
(
秒)
select * from record whereamount/
<
(
秒)
select * from record whereconvert(char(
)
date
)=
(
秒)
分析
where子句中對列的任何操作結果都是在SQL運行時逐列計算得到的
因此它不得不進行表搜索
而沒有使用該列上面的索引
如果這些結果在查詢編譯時就能得到
那麼就可以被SQL優化器優化
使用索引
避免表搜索
因此將SQL重寫成下面這樣
select * from record where card_no like
%
(<
秒)
select * from record where amount<
*
(<
秒)
select * from record where date=
/
/
(<
秒)
你會發現SQL明顯快起來!
例
表stuff有
行
id_no上有非群集索引
請看下面這個SQL
select count(*) from stuff where id_no in(
)(
秒)
分析
where條件中的
in
在邏輯上相當於
or
所以語法分析器會將in (
)轉化為id_no =
or id_no=
來執行
我們期望它會根據每個or子句分別查找
再將結果相加
這樣可以利用id_no上的索引
但實際上(根據showplan)
它卻采用了"OR策略"
即先取出滿足每個or子句的行
存入臨時數據庫的工作表中
再建立唯一索引以去掉重復行
最後從這個臨時表中計算結果
因此
實際過程沒有利用id_no上索引
並且完成時間還要受tempdb數據庫性能的影響
實踐證明
表的行數越多
工作表的性能就越差
當stuff有
行時
執行時間竟達到
秒!還不如將or子句分開
select count(*) from stuff where id_no=
select count(*) from stuff where id_no=
得到兩個結果
再作一次加法合算
因為每句都使用了索引
執行時間只有
秒
在
行下
時間也只有
秒
或者
用更好的方法
寫一個簡單的存儲過程
create proc count_stuff asdeclare @a intdeclare @b intdeclare @c intdeclare @d char(
)beginselect @a=count(*) from stuff where id_no=
select @b=count(*) from stuff where id_no=
endselect @c=@a+@bselect @d=convert(char(
)
@c)print @d
直接算出結果
執行時間同上面一樣快!
總結
可見
所謂優化即where子句利用了索引
不可優化即發生了表掃描或額外開銷
任何對列的操作都將導致表掃描
它包括數據庫函數
計算表達式等等
查詢時要盡可能將操作移至等號右邊
in
or子句常會使用工作表
使索引失效
如果不產生大量重復值
可以考慮把子句拆開
拆開的子句中應該包含索引
要善於使用存儲過程
它使SQL變得更加靈活和高效
從以上這些例子可以看出
SQL優化的實質就是在結果正確的前提下
用優化器可以識別的語句
充份利用索引
減少表掃描的I/O次數
盡量避免表搜索的發生
其實SQL的性能優化是一個復雜的過程
上述這些只是在應用層次的一種體現
深入研究還會涉及數據庫層的資源配置
網絡層的流量控制以及操作系統層的總體設計
開發人員如果用到其他庫的Table或View
務必在當前庫中建立View來實現跨庫操作
最好不要直接使用“databse
dbo
table_name”
因為sp_depends不能顯示出該SP所使用的跨庫table或view
不方便校驗
開發人員在提交SP前
必須已經使用set showplan on分析過查詢計劃
做過自身的查詢優化檢查
高程序運行效率
優化應用程序
在SP編寫過程中應該注意以下幾點
a) SQL的使用規范
i
盡量避免大事務操作
慎用holdlock子句
提高系統並發能力
ii
盡量避免反復訪問同一張或幾張表
尤其是數據量較大的表
可以考慮先根據條件提取數據到臨時表中
然後再做連接
iii
盡量避免使用游標
因為游標的效率較差
如果游標操作的數據超過
萬行
那麼就應該改寫
如果使用了游標
就要盡量避免在游標循環中再進行表連接的操作
iv
注意where字句寫法
必須考慮語句順序
應該根據索引順序
范圍大小來確定條件子句的前後順序
盡可能的讓字段順序與索引順序相一致
范圍從大到小
v
不要在where子句中的“=”左邊進行函數
算術運算或其他表達式運算
否則系統將可能無法正確使用索引
vi
盡量使用exists代替select count(
)來判斷是否存在記錄
count函數只有在統計表中所有行數時使用
而且count(
)比count(*)更有效率
vii
盡量使用“>=”
不要使用“>”
viii
注意一些or子句和union子句之間的替換
ix
注意表之間連接的數據類型
避免不同類型數據之間的連接
x
注意存儲過程中參數和數據類型的關系
xi
注意insert
update操作的數據量
防止與其他應用沖突
如果數據量超過
個數據頁面(
k)
那麼系統將會進行鎖升級
頁級鎖會升級成表級鎖
b) 索引的使用規范
i
索引的創建要與應用結合考慮
建議大的OLTP表不要超過
個索引
ii
盡可能的使用索引字段作為查詢條件
尤其是聚簇索引
必要時可以通過index index_name來強制指定索引
iii
避免對大表查詢時進行table scan
必要時考慮新建索引
iv
在使用索引字段作為條件時
如果該索引是聯合索引
那麼必須使用到該索引中的第一個字段作為條件時才能保證系統使用該索引
否則該索引將不會被使用
v
要注意索引的維護
周期性重建索引
重新編譯存儲過程
c) tempdb的使用規范
i
盡量避免使用distinct
order by
group by
having
join
***pute
因為這些語句會加重tempdb的負擔
ii
避免頻繁創建和刪除臨時表
減少系統表資源的消耗
iii
在新建臨時表時
如果一次性插入數據量很大
那麼可以使用select into代替create table
避免log
提高速度
如果數據量不大
為了緩和系統表的資源
建議先create table
然後insert
iv
如果臨時表的數據量較大
需要建立索引
那麼應該將創建臨時表和建立索引的過程放在單獨一個子存儲過程中
這樣才能保證系統能夠很好的使用到該臨時表的索引
v
如果使用到了臨時表
在存儲過程的最後務必將所有的臨時表顯式刪除
先truncate table
然後drop table
這樣可以避免系統表的較長時間鎖定
vi
慎用大的臨時表與其他大表的連接查詢和修改
減低系統表負擔
因為這種操作會在一條語句中多次使用tempdb的系統表
d) 合理的算法使用
根據上面已提到的SQL優化技術和ASE Tuning手冊中的SQL優化內容
結合實際應用
采用多種算法進行比較
以獲得消耗資源最少
效率最高的方法
具體可用ASE調優命令
set statistics io on
set statistics time on
set showplan on 等
From:http://tw.wingwit.com/Article/program/MySQL/201311/29522.html