---
title: 不懂SQL優化?那你就OUT了(四)
sql 怎么優化 where 子句
date: 2018-11-03
categories: 數據庫優化
---
在關系型數據庫中,除前期對數據庫的物理設計、關系規范化等方面進行優化外,一個簡單直接有效的方法是對SQL語句進行調整,進行優化來減少計算量和內存需求,提高響應速度。
本篇文章將討論mysql如何優化where子句
## 1.where條件的順序(*)
在網上有很多的博客都說數據庫執行where條件的的順序都是從左往右執行,所以把過濾數據多的的條件放左邊,過濾數據少的條件放右邊,從而達到提高查詢的效率。
其實這個觀點是***不完全正確的***
通常數據庫選擇從左到右進行計算,還是從右到左進行計算,是由數據庫軟件自行決定的。如果數據庫認為優先計算其他表達式可以提高計算速度,那么他可以自行選擇求值順序。
代碼示例
首先創建一個測試表: t_testWhere
```
CREATE TABLE t_testWhere(
-- 學生編號
studentId INT PRIMARY KEY AUTO_INCREMENT, -- 創建了唯一索引(主鍵)
-- 學生姓名
studentName VARCHAR(20),
-- 學生性別
studentGender CHAR(3),
-- 學生年齡
studentAge INT,
-- 家庭住址
studentAddress VARCHAR(255),
-- 所在班級
classId INT
)ENGINE=MYISAM,DEFAULT CHARSET=utf8
```
添加索引
```
CREATE INDEX idx_name ON t_testWhere(studentName);
CREATE INDEX idx_address ON t_testWhere(studentAddress);
CREATE INDEX idx_classid ON t_testWhere(classid);
```
添加的數據
```
DELIMITER $
CREATE PROCEDURE pro_whereDatas()
BEGIN
DECLARE num INT;
DECLARE age INT;
DECLARE result INT;
DECLARE gender CHAR(3);
DECLARE address VARCHAR(25);
DECLARE classid INT;
SET num:=1;
WHILE num <= 8000000 DO
-- 設置年齡
SET age:= 18 + CEIL(RAND()*15);
-- 設置性別
SET result:= CEIL(RAND()*100);
IF result < 50 THEN
SET gender:="男";
ELSE
SET gender:="女";
END IF;
-- 設置城市
SET result:= CEIL(RAND()*100);
IF result < 25 THEN
SET address:="成都市";
ELSEIF result<50 THEN
SET address:="綿陽市";
ELSEIF result<75 THEN
SET address:="昆明市";
ELSE
SET address:="貴陽市" ;
END IF;
-- 設置班級編號
SET classid:=CEIL(RAND()*4);
INSERT INTO t_testWhere(studentName,studentGender,studentAge,studentAddress,classId) VALUES(CONCAT('學生',num),gender,age,address,classid);
SET num:=num+1;
END WHILE;
END $
```
從數據看:
查詢所有男同學為 :3920182( 需要過濾 8000000-3920182=4079818 過濾數據少)
查詢所在城市在成都市為 :1920733(需要過濾 8000000-1920733=6079267 過濾數據多)
執行結果:
從查詢結果看,改變順序並沒有提高查詢的效率,
從sql語句執行計划上看,兩條sql語句執行計划是一樣的。
## 2. like 語句優化
使用like進行模糊查詢時應注意,不要在關鍵詞前加%(例如: %張 或則 %張%),%號加在關鍵詞前面都無法使用索引,從而引發全表掃描.
如果必須使用%x% 可以考慮全文檢索。
例如:(使用之前的 t_customer_two 表進行測試)
SELECT customerName, customerGender FROM t_customer_two WHERE customerName LIKE '張%';
使用索引 idx_name
例如: % 在前 或則 %x%
SELECT customerName, customerGender FROM t_customer_two WHERE customerName LIKE '%張';
SELECT customerName, customerGender FROM t_customer_two WHERE customerName LIKE '%張%';
type=ALL 未使用索引,進行全表掃描,效率低
### 3. 使用 nuion all 來替換 or 條件,避免進行全表掃描,降低效率。
例如:
查詢客戶年齡在20--25之間 或則是 省份是 四川的客戶的信息。
使用 or :
SELECT customerName, customerGender,customerPhone FROM t_customer_two WHERE customerAge BETWEEN 20 AND 25 OR province='四川';
使用關鍵字 or 將導致無法使用索引,從而導致全表掃描
使用 nuion all 來替換 or 條件
SELECT customerName, customerGender,customerPhone FROM t_customer_two WHERE customerAge BETWEEN 20 AND 25
UNION ALL
SELECT customerName, customerGender,customerPhone FROM t_customer_two WHERE province='四川';
從上面的結果看:
第一條 sql語句:能夠使用索引idx_age,但是實際並沒有使用該索引,可能是mysql優化器覺得使用全表掃描比使用索引快,所以它不會使用索引。
SELECT customerName, customerGender,customerPhone FROM t_customer_two WHERE customerAge BETWEEN 20 AND 25
但是第二條sql語句: 使用了索引,所以效率較快
SELECT customerName, customerGender,customerPhone FROM t_customer_two WHERE province='四川';
綜合上述: 使用 union all 來代替 or,效率更高。
### 4. where子句使用 != 或 <>(不等於) 的優化.
在where子句中使用 != 或 <>操作符,將不會使用索引,會進行全表查詢。
例如:
SELECT studentName,studentGender FROM t_testWhere WHERE studentAddress !='成都市';
可以修改為:
SELECT studentName,studentGender FROM t_testWhere WHERE studentAddress ='昆明市'
UNION ALL
SELECT studentName,studentGender FROM t_testWhere WHERE studentAddress ='貴陽市';
### 5. 使用IN 或 NOT IN的優化
in 和 not in也要慎用,否則會導致全表掃描。
解決方法:
1. between 替換 in ( 如果 in 的條件是連續的)
例如:
SELECT studentName,studentAge FROM t_testWhere WHERE classid IN(1,2,3,4);
替換為
SELECT studentName,studentAge FROM t_testWhere WHERE classid BETWEEN 1 AND 4;
-------
2. 用exists替代in、用not exists替代 not in (無論在哪種情況下,not in 都是最低效的 (因為它對子查詢中的表執行了全表掃描))
-------
3. left join 替換 in
例如:
## 6. 優化 is null
在where子句中使用 IS NULL 或 IS NOT NULL進行null值的判斷,可能將導致引擎放棄使用索引而進行全表掃描
解決方案:
***MySQL可以結合col_name(列名)= constant_value(常量)的使用 來對 col_name(列名) IS NULL進行相同的優化。***
例如:
SELECT studentName,studentAge FROM t_testWhere WHERE classid IS NULL;
替換為
SELECT studentName,studentAge FROM t_testWhere WHERE classid= 0;( classid(列名) = 0( 常量))
### 7. 盡量不要在where子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統可能無法正確使用索引。
### 8 .在使用索引字段作為條件時,如果該索引是聯合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統使用該索引(最左前綴原則),否則該索引將不會被使用,並且應盡可能的讓字段順序與索引順序相一致。
###9. 任何地方都不要用 select * from 表名 ,使用具體的字段列表替換"*",不要返回用不到的字段。