萬盛學電腦網

 萬盛學電腦網 >> 數據庫 >> mssql數據庫 >> SQLSERVER SQL性能優化技巧

SQLSERVER SQL性能優化技巧

1.選擇最有效率的表名順序(只在基於規則的優化器中有效)

SQLSERVER的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表driving table)將被最先處理,在FROM子句中包含多個表的情況下,必須選擇記錄條數最少的表作為基礎表,當SQLSERVER處理多個表時,會運用排序及合並的方式連接它們,

首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行排序;然後掃描第二個表(FROM子句中最後第二個表);最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合並
例如: 表 TAB1 16,384 條記錄表 TAB2 5 條記錄,選擇TAB2作為基礎表 (最好的方法) select count(*) from tab1,tab2 執行時間0.96秒,選擇TAB2作為基礎表 (不佳的方法) select count(*) from tab2,tab1 執行時間26.09秒;

如果有3個以上的表連接查詢,那就需要選擇交叉表(intersection table)作為基礎表,交叉表是指那個被其他表所引用的表

例如:

EMP表描述了LOCATION表和CATEGORY表的交集

SELECT * 
FROM LOCATION L, 
CATEGORY C, 
EMP E 
WHERE E.EMP_NO BETWEEN 1000 AND 2000 
AND E.CAT_NO = C.CAT_NO 
AND E.LOCN = L.LOCN 

將比下列SQL更有效率

SELECT * 
FROM EMP E , 
LOCATION L , 
CATEGORY C 
WHERE E.CAT_NO = C.CAT_NO 
AND E.LOCN = L.LOCN 
AND E.EMP_NO BETWEEN 1000 AND 2000 

2.WHERE子句中的連接順序

SQLSERVER采用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前,那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾
例如:
(低效,執行時間156.3秒)

SELECT * 
FROM EMP E 
WHERE SAL > 50000 
AND JOB = 'MANAGER' 
AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO); 
(高效,執行時間10.6秒) 
SELECT * 
FROM EMP E 
WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO) 
AND SAL > 50000 
AND JOB = 'MANAGER'; 

3.SELECT子句中避免使用'*'。當你想在SELECT子句中列出所有的COLUMN時,使用動態SQL列引用'*'是一個方便的方法,不幸的是,這是一個非常低效的方法。實際上,SQLSERVER在解析的過程中,會將'*'依次轉換成所有的列名,這個工作是通過查詢數據字典完成的,這意味著將耗費更多的時間


4.減少訪問數據庫的次數。當執行每條SQL語句時,SQLSERVER在內部執行了許多工作:解析SQL語句,估算索引的利用率,綁定變量,讀數據塊等等
由此可見,減少訪問數據庫的次數,就能實際上減少SQLSERVER的工作量,例如:
以下有三種方法可以檢索出雇員號等於0342或0291的職員
方法1 (最低效)

SELECT EMP_NAME, SALARY, GRADE 
FROM EMP 
WHERE EMP_NO = 342;  
SELECT EMP_NAME, SALARY, GRADE 
FROM EMP 
WHERE EMP_NO = 291; 

方法2 (次低效)

DECLARE 
CURSOR C1 (E_NO NUMBER) IS 
SELECT EMP_NAME,SALARY,GRADE 
FROM EMP 
WHERE EMP_NO = E_NO; 
BEGIN 
OPEN C1(342); 
FETCH C1 INTO …,…,…; 
… 
OPEN C1(291); 
FETCH C1 INTO …,…,…; 
… 
CLOSE C1; 
END; 

方法2 (高效)

SELECT A.EMP_NAME, A.SALARY, A.GRADE, 
B.EMP_NAME, B.SALARY, B.GRADE 
FROM EMP A, EMP B 
WHERE A.EMP_NO = 342 
AND B.EMP_NO = 291; 

5.使用DECODE函數來減少處理時間

使用DECODE函數可以避免重復掃描相同記錄或重復連接相同的表

例如:

SELECT COUNT(*), SUM(SAL) 
FROM EMP 
WHERE DEPT_NO = '0020' 
AND ENAME LIKE 'SMITH%'; 
 
SELECT COUNT(*), SUM(SAL) 
FROM EMP 
WHERE DEPT_NO = '0030' 
AND ENAME LIKE 'SMITH%'; 

你可以用DECODE函數高效地得到相同結果

SELECT COUNT(DECODE(DEPT_NO, '0020', 'X', NULL)) D0020_COUNT, 
COUNT(DECODE(DEPT_NO, '0030', 'X', NULL)) D0030_COUNT, 
SUM(DECODE(DEPT_NO, '0020', SAL, NULL)) D0020_SAL, 
SUM(DECODE(DEPT_NO, 0030, SAL, NULL)) D0030_SAL 
FROM EMP 
WHERE ENAME LIKE 'SMITH%'; 

'X'表示任何一個字段
類似的,DECODE函數也可以運用於GROUP BY和ORDER BY子句中

6.用Where子句替換HAVING子句

避免使用HAVING子句,HAVING只會在檢索出所有記錄之後才對結果集進行過濾,這個處理需要排序、統計等操作

如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷

例如:
低效

SELECT REGION, AVG(LOG_SIZE) 
FROM LOCATION 
GROUP BY REGION 
HAVING REGION REGION != 'SYDNEY' 
AND REGION != 'PERTH' 
 
高效 
SELECT REGION, AVG(LOG_SIZE) 
FROM LOCATION 
WHERE REGION REGION != 'SYDNEY' 
AND REGION != 'PERTH' 
GROUP BY REGION 

7.減少對表的查詢

在含有子查詢的SQL語句中,要特別注意減少對表的查詢
例如:

低效 
SELECT TAB_NAME 
FROM TABLES 
WHERE TAB_NAME = (SELECT TAB_NAME 
FROM TAB_COLUMNS 
WHERE VERSION = 604) 
AND DB_VER = (SELECT DB_VER 
FROM TAB_COLUMNS 
WHERE VERSION = 604) 
 
高效 
SELECT TAB_NAME 
FROM TABLES 
WHERE (TAB_NAME, DB_VER) = (SELECT TAB_NAME, DB_VER 
FROM TAB_COLUMNS 
WHERE VERSION = 604) 

Update多個Column例子:

低效 
UPDATE EMP 
SET EMP_CAT = (SELECT MAX(CATEGORY) 
FROM EMP_CATEGORIES), 
SAL_RANGE = (SELECT MAX(SAL_RANGE) 
FROM EMP_CATEGORIES) 
WHERE EMP_DEPT = 0020; 
 
高效 
UPDATE EMP 
SET (EMP_CAT, SAL_RANGE) = (SELECT MAX(CATEGORY), MAX(SAL_RANGE) 
FROM EMP_CATEGORIES) 
WHERE EMP_DEPT = 0020; 

8.使用表的別名(Alias),當在SQL語句中連接多個表時,請使用表的別名並把別名前綴於每個Column上,這樣可以減少解析的時間並減少那些由Column歧義引起的語法錯誤

9.用EXISTS替代IN

在許多基於基礎表的查詢中,為了滿足一個條件,往往需要對另一個表進行聯接

在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率

低效 
SELECT * 
FROM EMP (基礎表) 
WHERE EMPNO > 0 
AND DEPTNO IN (SELECT DEPTNO 
FROM DEPT 
WHERE LOC = 'MELB') 
 
高效 
SELECT * 
FROM EMP (基礎表) 
WHERE EMPNO > 0 
AND EXISTS (SELECT 'X' 
FROM DEPT 
WHERE DEPT.DEPTNO = EMP.DEPTNO 
AND LOC = 'MELB') 

10.用NOT EXISTS替代NOT IN

在子查詢中,NOT IN子句將執行一個內部的排序和合並

無論在哪種情況下,NOT IN都是最低效的,因為它對子查詢中的表執行了一個全表遍歷

為了避免使用NOT IN,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS

例如:

SELECT … 
FROM EMP 
WHERE DEPT_NO NOT IN (SELECT DEPT_NO 
FROM DEPT 
WHERE DEPT_CAT = 'A'); 

為了提高效率改寫為

高效 
SELECT … 
FROM EMP A, DEPT B 
WHERE A.DEPT_NO = B.DEPT(+) 
AND B.DEPT_NO IS NULL 
AND B.DEPT_CAT(+) = 'A' 
 
最高效 
SELECT … 
FROM EMP E 
WHERE NOT EXISTS (SELECT 'X' 
FROM DEPT D 
WHERE D.DEPT_NO = E.DEPT_NO 
AND DEPT_CAT = 'A'); 

11.用表連接替換EXISTS

通常來說,采用表連接的方式比EXISTS更有效率

例如:

SELECT ENAME 
FROM EMP E 
WHERE EXISTS (SELECT 'X' 
FROM DEPT 
WHERE DEPT_NO = E.DEPT_NO 
AND DEPT_CAT = 'A'); 
 
更高效 
SELECT ENAME 
FROM DEPT D, EMP E 
WHERE E.DEPT_NO = D.DEPT_NO 
AND DEPT_CAT = 'A'; 

12.用EXISTS替換DISTINCT

當提交一個包含多表信息(比如部門表和雇員表)的查詢時,避免在SELECT子句中使用DISTINCT,一般可以考慮用EXIST替換

例如:

低效 
SELECT DISTINCT DEPT_NO, DEPT_NAME 
FROM DEPT D, EMP E 
WHERE D.DEPT_NO = E.DEPT_NO 
 
高效 
SELECT DEPT_NO, DEPT_NAME 
FROM DEPT D 
WHERE EXISTS (SELECT 'X' 
FROM EMP E 
WHERE E.DEPT_NO = D.DEPT_NO); 

EXISTS使查詢更為迅速,因為RDBMS核心模塊將在子查詢的條件一旦滿足後,立刻返回結果

13.用索引提高效率

索引是表的一個概念部分,用來提高檢索數據的效率。實際上,SQLSERVER使用了一個復雜的自平衡B-tree結構

通常,通過索引查詢數據比全表掃描要快。當SQLSE

copyright © 萬盛學電腦網 all rights reserved