MSSQL Server查詢優化 (整理修改)
/***** 在SQLServer上檢視SQL語句的執行時間的方法 *******
比較簡單的查詢方法,通過查詢前的時間和查詢後的時間差來計算的
declare @begin_date datetime
declare @end_date datetime
select @begin_date = getdate()
<這裡寫上你的語句...>
select @end_date = getdate()
select datediff(ms,@begin_date,@end_date) as '用時/毫秒'
OR:select (@[email protected]
2:方法二比較全面,將執行每個語句時採取的步驟作為行集返回,通過層次結構樹的形式展示出來
程式碼如下:
set statistics profile on
set statistics io on
set statistics time on
go
<這裡寫上你的語句...>
go
set statistics profile off
set statistics io off
set statistics time off
*************/
SQL執行順序
(1)、 FROM:對FROM子句中的前兩個表執行笛卡爾積(交叉聯接),生成虛擬表VT1。
(2)、 ON:對VT1應用ON篩選器,只有那些使為真才被插入到TV2。
(3)、 OUTER (JOIN):如果指定了OUTER JOIN(相對於CROSS JOIN或INNER JOIN),保留表中未找到匹配的行將作為外部行新增到VT2,生成TV3。如果FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重複執行步驟1到步驟3,直到處理完所有的表位置。
(4)、 WHERE:對TV3應用WHERE篩選器,只有使為true的行才插入TV4。
(5)、 GROUP BY:按GROUP BY子句中的列列表對TV4中的行進行分組,生成TV5。
(6)、 CUTE|ROLLUP:把超組插入VT5,生成VT6。
(7)、 HAVING:對VT6應用HAVING篩選器,只有使為true的組插入到VT7。
(8)、 SELECT:處理SELECT列表,產生VT8。
(9)、 DISTINCT:將重複的行從VT8中刪除,產品VT9。
(10)、ORDER BY:將VT9中的行按ORDER BY子句中的列列表順序,生成一個遊標(VC10)。
(11)、TOP:從VC10的開始處選擇指定數量或比例的行,生成表TV11,並返回給呼叫者
1.用select top xx 來限制使用者返回的行數或者SET ROWCOUNT來限制操作的行
2.儘量不用 "IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE", and "LIKE '%500'",這些全表掃描,不會執行索引
SELECT ID FROM T WHERE NUM IS NULL
可以在NUM上設定預設值0,確保表中NUM列沒有NULL值( 應該沒什麼影響 ),然後這樣查詢:
SELECT ID FROM T WHERE NUM=0
----------------------------------------------------------
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”
-----------------------------------------------------------
對UNION ALL結果集新增Where條件
select * from (
[SQL 語句 1]
UNION
[SQL 語句 2]
) tmp
where 姓名 like 'xxx%'
注意:union子句不能直接加order by,應用如下方法
select * From (select top 5 1 as order1,* from ProContent where ProductName like 'a%') as Tb1
union
select * From (select top 5 2 as order1,* from ProContent where ProductName like 'b%' order by order1, id) as Tb2
order by order1,id desc
相同欄位,不必每個union 加order by xxx ,只需在任意一個union子句中新增一次即可,結果等價
如果子查詢的排序與總查詢相同,則在子查詢無需排序order by
-----------------------------------------------------------
SELECT ID FROM T WHERE NUM IN(1,2,3)
對於連續的數值,能用 BETWEEN 不要用 IN :
SELECT ID FROM T WHERE NUM BETWEEN 1 AND 3”
3.不要在WHere字句中的列名加函式,如Convert,substring等,如果必須用函式的時候, 建立計算列再建立索引來替代.
還可以變通寫法:WHERE SUBSTRING(firstname,1,1) = 'm'改為WHERE firstname like 'm%'(索引掃描),
like 'a%' 使用索引
like '%a' 不使用索引
4.NOT IN會多次掃描表,使用EXISTS、NOT EXISTS ,IN , LEFT OUTER JOIN 來替代,特別是左連線,而Exists比IN更快,最慢的是NOT操作.
在IN後面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最後面,減少判斷的次數
5.MIN() 和 MAX()能使用到合適的索引,
SELECT COUNT(*)的效率教低,儘量變通他的寫法,而EXISTS快
不要對索引欄位進行運算,而要想辦法做變換,比如
SELECT ID FROM T WHERE NUM/2=100
應改為:
SELECT ID FROM T WHERE NUM=100*2
6.如果要插入大的二進位制值到Image列,使用儲存過程,不要用內嵌INsert來插入(不知JAVA是否)。因為這樣應用程式首 先將二進位制值轉換成字串(尺寸是它的兩倍),伺服器受到字元後又將他轉換成二進位制值.儲存過程就沒有這些動作:
方法:Create procedure p_insert as insert into table(Fimage) values (@image),
在前臺調 用這個儲存過程傳入二進位制引數,這樣處理速度明顯改善。
7.Between在某些時候比IN速度更快,Between能夠更快地根據索引找到範圍。用查詢優化器可見到差別。
select * from chineseresume where title in ('男','女')
Select * from chineseresume where between '男' and '女'
是一樣的。由於in會在比較多次,所以有時會慢些
8.用OR的字句可以分解成多個sql查詢,並且 通過UNION 連線多個查詢
9.一般在GROUP BY 個HAVING字句之前就能剔除多餘的行,所以儘量不要用它們來做剔除行的工作。他們的執行順序應該如下最優:select 的Where字句選擇所有合適的行,Group By用來分組個統計行,Having字句用來剔除多餘的分組。這樣Group By 個Having的開銷小,查詢快.對於大的資料行進行分組和Having十分消耗資源。如果Group BY的目的不包括計算,只是分組,那麼用Distinct更快
10.一次更新多條記錄比分多次更新每次一條快,
11.(1) IF 沒有輸入負責人程式碼 THEN
code1=0
code2=9999
ELSE
code1=code2= 負責人程式碼
END IF
執行SQL語句為:
SELECT 負責人名 FROM P2000 WHERE 負責人程式碼>=:code1 AND負責人程式碼 <=:code2
(2) IF 沒有輸入負責人程式碼 THEN
SELECT 負責人名 FROM P2000
ELSE
code= 負責人程式碼
SELECT 負責人程式碼 FROM P2000 WHERE 負責人程式碼=:code
END IF
第一種方法只用了一條SQL語句,第二種方法用了兩條SQL語句。在沒有輸入負責人代 碼時,第二種方法顯然比第一種方法執行效率高,因為它沒有限制條件;在輸入了負責人程式碼時,第二種方法仍然比第一種方法效率高,不僅是少了一個限制條件, 還因相等運算是最快的查詢運算
12.資料型別儘量小,這裡的儘量小是指在滿足可以預見的未來需求的前提下的。
儘量不要允許NULL,除非必要,可以用NOT NULL+DEFAULT代替。
SELECT INTO後的WHERE子句,因為SELECT INTO把資料插入到臨時表,這個過程會鎖定一些系統表,如果這個WHERE子句返回的資料過多或者速度太慢,會造成系統表長期鎖定,諸塞其他程序。
13.日期查詢的例子:
WHERE DATEDIFF(DAY, 日期,'2005-11-30')=0應改為:WHERE 日期 >='2005-11-30' AND 日期 <'2005-12-1‘
WHERE DATEDIFF(DAY, 日期,'2005-11-30')>0應改為:WHERE 日期 <'2005-11-30‘
WHERE DATEDIFF(DAY, 日期,'2005-11-30')>=0應改為:WHERE 日期 <'2005-12-01‘
WHERE DATEDIFF(DAY, 日期,'2005-11-30')<0應改為:WHERE 日期>='2005-12-01‘
WHERE DATEDIFF(DAY, 日期,'2005-11-30')<=0應改為:WHERE 日期>='2005-11-30‘
14.sql not in與left join百萬級資料測試比較
select OrgId as 公司編碼,OrgName as 公司名稱
from Organise
where OrgLev=2
and item_id not in
(select OrgidS from WagesPerMonthHis
where WagesYear='2010' and WagesMonth=
'01' Group by OrgidS,OrgNameS)
order by Orgid
語句執行要33秒之久,記憶體和CPU在執行時都沒有出現瓶頸,以為是
程式碼如下 複製程式碼
(select OrgidS from WagesPerMonthHis
where WagesYear='2010' and WagesMonth=
'01' Group by OrgidS,OrgNameS)
這條語句執行緩慢所致,單獨執行這條卻發現執行速度很快,大約不到2秒就出來了,於是癥結出來了,是not in 這個全掃描關鍵詞帶來的效能下降.最直接的是導致頁面失去響應,一個關鍵功能使用不了.
試了not exist語句,發現效果是一樣的,並不象網上所說可以提高很多效能.
於是重新優化語句如下
程式碼如下 複製程式碼
select a.OrgId as 公司編碼,a.OrgName as 公司名稱,a.item_id
from Organise a
left outer join (select distinct b.OrgIdS from WagesPerMonthHis b
where WagesYear='2010' and WagesMonth='01') as b
on a.item_id = b.OrgidS
where a.OrgLev = 2
and b.OrgIdS is Null
order by 公司編碼
改用左外連線(其實左連線也可以)後,整個語句執行速度為400ms
14.大資料量排序
在資料庫中有一張表mytable,資料記錄7000萬條,有如下兩條SQL語句
(1)select top 100 * from mytable order by operateDate
(2)select * from (select top 100 * from mytable ) a order by a.operateDate (快)
15.索引的建立規則
(1)、表的主鍵、外來鍵必須有索引; (2)、資料量超過300的表應該有索引; (3)、經常與其他表進行連線的表,在連線欄位上應該建立索引; (4)、經常出現在Where子句中的欄位,特別是大表的欄位,應該建立索引; (5)、索引應該建在選擇性高的欄位上; (6)、索引應該建在小欄位上,對於大的文字欄位甚至超長欄位,不要建索引; (7)、複合索引的建立需要進行仔細分析;儘量考慮用單欄位索引代替: (A)、正確選擇複合索引中的主列欄位,一般是選擇性較好的欄位; (B)、複合索引的幾個欄位是否經常同時以AND方式出現在Where子句中?單欄位查詢是否極少甚至沒有?如果是,則可以建立複合索引;否則考慮單欄位索引; (C)、如果複合索引中包含的欄位經常單獨出現在Where子句中,則分解為多個單欄位索引; (D)、如果複合索引所包含的欄位超過3個,那麼仔細考慮其必要性,考慮減少複合的欄位; (E)、如果既有單欄位索引,又有這幾個欄位上的複合索引,一般可以刪除複合索引; (8)、頻繁進行資料操作的表,不要建立太多的索引; (9)、刪除無用的索引,避免對執行計劃造成負面影響;