一、開門見山,問題所在
sql語句性能達不到你的要求,執行效率讓你忍無可忍,一般會時下面幾種情況。
- 網速不給力,不穩定。
- 服務器內存不夠,或者SQL 被分配的內存不夠。
- sql語句設計不合理
- 沒有相應的索引,索引不合理
- 沒有有效的索引視圖
- 表數據過大沒有有效的分區設計
- 數據庫設計太2,存在大量的數據冗余
- 索引列上缺少相應的統計信息,或者統計信息過期
- ....
那么我們如何給找出來導致性能慢的的原因呢?
- 首先你要知道是否跟sql語句有關,確保不是機器開不開機,服務器硬件配置太差,沒網你說p啊
- 接著你使用我上一篇文章中提到的2柯南sql性能檢測工具--sql server profiler,分析出sql慢的相關語句,就是執行時間過長,占用系統資源,cpu過多的
- 然后是這篇文章要說的,sql優化方法跟技巧,避免一些不合理的sql語句,取暫優sql
- 再然后判斷是否使用啦,合理的統計信息。sql server中可以自動統計表中的數據分布信息,定時根據數據情況,更新統計信息,是很有必要的
- 確認表中使用啦合理的索引,這個索引我前面博客中也有提過,不過那篇博客之后,還要進一步對索引寫篇文章
- 數據太多的表,要分區,縮小查找范圍
二、分析比較執行時間計劃讀取情況
select * from dbo.Product
執行上面語句一般情況下只給你返回結果和執行行數,那么你怎么分析呢,怎么知道你優化之后跟沒有優化的區別呢。
下面給你說幾種方法。
1.查看執行時間和cpu占用時間
set statistics time on select * from dbo.Product set statistics time off
打開你查詢之后的消息里面就能看到啦。
2.查看查詢對I/0的操作情況
set statistics io on select * from dbo.Product set statistics io off
執行之后
- 掃描計數:索引或表掃描次數
- 邏輯讀取:數據緩存中讀取的頁數
- 物理讀取:從磁盤中讀取的頁數
- 預讀:查詢過程中,從磁盤放入緩存的頁數
- lob邏輯讀取:從數據緩存中讀取,image,text,ntext或大型數據的頁數
- lob物理讀取:從磁盤中讀取,image,text,ntext或大型數據的頁數
- lob預讀:查詢過程中,從磁盤放入緩存的image,text,ntext或大型數據的頁數
如果物理讀取次數和預讀次說比較多,可以使用索引進行優化。
如果你不想使用sql語句命令來查看這些內容,方法也是有的,哥教你更簡單的。
查詢--->>查詢選項--->>高級
被紅圈套上的2個選上,去掉sql語句中的set statistics io/time on/off 試試效果。哦也,你成功啦。。
3.查看執行計劃,執行計劃詳解
選中查詢語句,點擊
然后看消息里面,會出現下面的圖例
首先我這個例子的語句太過簡單,你整個復雜的,包涵啊。
分析:鼠標放在圖標上會顯示此步驟執行的詳細內容,每個表下面都顯示一個開銷百分比,分析站百分比多的的一塊,可以根據重新設計數據結構,或這重寫sql語句,來對此進行優化。如果存在掃描表,或者掃描聚集索引,這表示在當前查詢中你的索引是不合適的,是沒有起到作用的,那么你就要修改完善優化你的索引。
三、select查詢藝術
1.保證不查詢多余的列與行。
盡量避免select * 的存在,使用具體的列代替*,避免多余的列
使用where限定具體要查詢的數據,避免多余的行
使用top,distinct關鍵字減少多余重復的行
2.慎用distinct關鍵字
distinct在查詢一個字段或者很少字段的情況下使用,會避免重復數據的出現,給查詢帶來優化效果。
但是查詢字段很多的情況下使用,則會大大降低查詢效率。
由這個圖,分析下:
很明顯帶distinct的語句cpu時間和占用時間都高于不帶distinct的語句。原因是當查詢很多字段時,如果使用distinct,數據庫引擎就會對數據進行比較,過濾掉重復數據,然而這個比較,過濾的過程則會毫不客氣的占用系統資源,cpu時間。
3.慎用union關鍵字
此關鍵字主要功能是把各個查詢語句的結果集合并到一個結果集中返回給你。用法
<select 語句1> union <select 語句2> union <select 語句3> ...
滿足union的語句必須滿足:1.列數相同。 2.對應列數的數據類型要保持兼容。
執行過程:
依次執行select語句-->>合并結果集--->>對結果集進行排序,過濾重復記錄。
select * from((orde o left join orderproduct op on o.orderNum = op.orderNum) inner join product p on op.proNum = p.productnum) where p.id < 10000 union select * from((orde o left join orderproduct op on o.orderNum = op.orderNum) inner join product p on op.proNum = p.productnum) where p.id < 20000 and p.id >= 10000 union select * from((orde o left join orderproduct op on o.orderNum = op.orderNum) inner join product p on op.proNum = p.productnum) where p.id > 20000-- -這里可以寫p.id > 100 結果一樣, 因為他篩選過啦 -- -- -- -對比上下兩個語句-- -- -- -- select * from((orde o left join orderproduct op on o.orderNum = op.orderNum) inner join product p on op.proNum = p.productnum)
由此可見效率確實低,所以不是在必要情況下避免使用。其實有他執行的第三部:對結果集進行排序,過濾重復記錄。就能看出不是什么好鳥。然而不對結果集排序過濾,顯然效率是比union高的,那么不排序過濾的關鍵字有嗎?答,有,他是union all,使用union all能對union進行一定的優化。。
4.判斷表中是否存在數據
select count(*) from product select top(1) id from product
很顯然下面完勝
5.連接查詢的優化
首先你要弄明白你想要的數據是什么樣子的,然后再做出決定使用哪一種連接,這很重要。
各種連接的取值大小為:
內連接結果集大小取決于左右表滿足條件的數量
左連接取決與左表大小,右相反。
完全連接和交叉連接取決與左右兩個表的數據總數量
select * from ( (select * from orde where OrderId>10000) o left join orderproduct op on o.orderNum=op.orderNum ) select * from ( orde o left join orderproduct op on o.orderNum=op.orderNum ) where o.OrderId>10000
由此可見減少連接表的數據數量可以提高效率。
四、insert插入優化
--創建臨時表 create table# tb1 ( id int, name nvarchar(30), createTime datetime ) declare@ i int declare@ sql varchar(1000) set@ i = 0 while (@i < 100000) --循環插入10w條數據 begin set@ i = @i + 1 set@ sql = ' insert into #tb1 values ('+convert(varchar(10),@i)+', '' erzi '+ convert(nvarchar(30), @i) + '' ',' '' + convert(nvarchar(30), getdate()) + '' ')' exec(@sql) end
我這里運行時間是51秒
--創建臨時表 create table# tb2 ( id int, name nvarchar(30), createTime datetime ) declare@ i int declare@ sql varchar(8000) declare@ j int set@ i = 0 while (@i < 10000) --循環插入10w條數據 begin set@ j = 0 set@ sql = ' insert into #tb2 select ' + convert(varchar(10), @i * 100 + @j) + ',' 'erzi' + convert(nvarchar(30), @i * 100 + @j) + '' ',' '' + convert(varchar(50), getdate()) + '' '' set@ i = @i + 1 while (@j < 10) begin set@ sql = @sql + ' union all select ' + convert(varchar(10), @i * 100 + @j) + ',' ' erzi '+convert(nvarchar(30),@i*100+@j)+' '', '' '+convert(varchar(50),getdate())+' '' ' set@ j = @j + 1 end exec(@sql) end drop table# tb2 select count(1) from# tb2
我這里運行時間大概是20秒
分析說明:insert into select批量插入,明顯提升效率。所以以后盡量避免一個個循環插入。
五、優化修改刪除語句
如果你同時修改或刪除過多數據,會造成cpu利用率過高從而影響別人對數據庫的訪問。
如果你刪除或修改過多數據,采用單一循環操作,那么會是效率很低,也就是操作時間過程會很漫長。
這樣你該怎么做呢?
折中的辦法就是,分批操作數據。
delete product where id<1000 delete product where id>=1000 and id<2000 delete product where id>=2000 and id<3000 .....
當然這樣的優化方式不一定是最優的選擇,其實這三種方式都是可以的,這要根據你系統的訪問熱度來定奪,關鍵你要明白什么樣的語句是什么樣的效果。
總結:優化,最重要的是在于你平時設計語句,數據庫的習慣,方式。如果你平時不在意,匯總到一塊再做優化,你就需要耐心的分析,然而分析的過程就看你的悟性,需求,知識水平啦。