1. 程式人生 > >挽救資料庫效能的30條黃金法則

挽救資料庫效能的30條黃金法則

1. 優化查詢,應儘量避免全表掃描,應該在用於檢索資料和排序資料的欄位上建立索引,如where子句用於搜尋,order by子句用於排序,所以在這兩個子句涉及到的欄位上需要建立索引。

2. 應該在where子句中使用否定的操作符,如不等於(!=或<>)、否則資料庫引擎將放棄使用索引而進行全表掃描。

3. 在儘量避免在where子句中使用或(or)作為連線條件,否則資料庫引擎將放棄使用索引而進行全表掃描。
如下面的SQL語句可能會帶來效能問題

select id,name,age from persons 
where name = 'Bill' or age > 30

由於這條SQL語句使用了or,所以資料庫引擎會進行全表掃描,為了避免全表掃描,可以將這條SQL語句改成下面的形式。

select id,name,age from persons where name = 'Bill'
union all
select id,name,age from persons where num = 20

4. 應該儘量避免在where子句中使用null進行判斷,否則資料庫引擎將放棄使用索引而進行全表掃描。
先看下面的SQL語句:

select id,name,age from persons where age is null


為了避免使用null,可以設定age欄位的預設值為0,這樣就可以通過下面的SQL語句達到同樣的結果。

select id,name,age from persons where age = 0


5. 儘量不用使用like檢索資料,因為也會導致資料庫引擎將放棄使用索引而進行全表掃描。

例如,下面的SQL語句執行的效率會非常低:

select id,name,age from persons where name like '%John%'


如果真想進行模糊查詢,可以使用全文檢索。

6. 在where子句中應儘量避免在欄位中使用表示式(包括函式運算、算數運算等),否則據庫引擎將放棄使用索引而進行全表掃描。
例如,下面的SQL語句執行的效能比較差

select id,name,age from persons age / 2 > 12


應該利用表示式變換,改成下面的形式:

select id,name,age from persons age > 2 * 12


或者乾脆改成下面的形式:

select id,name,age from persons age > 24

 

7. 應儘量避免使用in和not in,否則也會導致全表掃描。

如並不推薦下面的寫法:

select id, name,age from persons where age in (22,23,24)


如果數值是連續的,應該使用between,而不要用in,如果數值是不連續的,可以分成多個SQL,用union all連線查詢結果。

select id,name,age from persons where age between 22 and 24

select id,name,age from persons where age = 22
union all
select id,name,age from persons where age = 26
union all
select id,name,age from persons where age = 30

 

8. 應該儘量避免在where子句中使用引數,否則也將導致全表掃描。這是因為引數需要在SQL執行時才進行替換,而SQL優化(使用索引屬於優化的一部分)是在編譯時進行的。所以資料庫引擎在檢索到引數時,由於引數的具體指是未知的,所以也就無法優化了,當然也就無法使用索引了。

不使用索引的SQL語句:

select id,name,age from persons where name = @name


為了使用索引,可以改成下面強制使用索引的方式:

select id,name,age from persons with(index(name_index)) where name = @name


其中name_index是建立在name欄位上的索引名。

9. 儘量不要執行一些沒意義的查詢,如條件完全為false的查詢:

select id,name,age into persons1 from persons where age < 0


這樣的程式碼會返回一個空結果集,而且會大量消耗系統資源,如果真的想建一個空表,應該直接用create table語句。

10. 如果使用的索引是符合索引,只有使用該符合索引的第1個欄位作為條件時才能保證資料庫引擎使用該符合索引,否則該符合索引不會被使用。並且應該儘可能讓欄位順序與索引順序一致。例如,name_index是first_name和last_name欄位的符合索引,使用下面的SQL語句會使用該索引。

select id,first_name,last_name
from persons
where first_name = 'Bill'


11. 如果非要在SQL語句中使用in,那麼使用exists代替in是一個好主意:

select id,num from t 
where num in (select num from h)


應該用下面的SQL語句代替:

select id,num form t
where exists(select 0 from h where num = t.num)

12. 索引並不是在任何時候都有效,如果索引列有大量重複的資料,那麼資料庫引擎可能不會去利用索引。例如,sex欄位的值只有兩種可能:male和female,可能這兩個值各佔一半,這樣在sex欄位上建立索引就沒有任何意義。

13. 能使用數值型欄位就使用數值型欄位。因為比較數值型欄位的效率要遠比字元型欄位的效率高,這是因為比較字元型的值,要一個字母一個字母地比較,而數值型的值,只是比較一個數。所以如果只包含數值資訊的值,應該儘量使用數值型別的欄位。例如,age、salary等。

14. 應儘量避免使用固定長度的欄位,如char、nchar。使用可變長度的欄位是一個非常好的選擇。因為可變長度欄位佔用的空間是按需分配的,所以佔用空間比較少。對於查詢來說,毫無疑問,當然是佔用空間小的欄位的查詢效率更高了。

15. 儘量按需返回欄位和記錄,例如:

select id,name,age from persons where age > 20


儘量如要使用“*”返回所有不需要的欄位,也不需要一下就查詢出所有的記錄,如下面的SQL語句在資料量很大時查詢效率是非常低的。

select * from persons


16. 索引有利有弊,增加索引,可以提高select的執行效率,但付出的代價是在進行insert和update操作時,可能會降低效率。因為進行insert和update操作時通常需要重建索引。所以在一個表中並不是索引越多越好。我的建議如下:
(1)如果一個表大多數時進行的是select操作,那麼索引多一些大多數時候確實可以提升效能,但這有一個前提,就是不能頻繁進行insert和update操作。
(2)一個表中的索引數不能太多,最好不要超過6個,否則就好考慮優化一下資料庫了。

17. 應儘可能的避免更新 clustered 索引資料列,因為 clustered 索引資料列的順序就是表記錄的物理儲存順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引資料列,那麼需要考慮是否應將該索引建為 clustered 索引。

18. 應儘量避免向客戶端返回大理資料,如果資料量過大,應該改變一下需求,或採用分頁返回的方式,如使用MySQL中的limit子句現在返回的資料。

19. 儘量避免使用遊標,因為遊標的效率較差,如果遊標操作的資料超過1萬行,那麼就應該採用其他方案。

20. 使用基於遊標的方法或臨時表方法之前,應先尋找基於資料集的解決方案來解決問題,基於資料集的方法通常更有效。

21. 如果使用到了臨時表,在儲存過程的最後務必將所有的臨時表顯式刪除,先用 truncate table清除表中的資料 ,然後 用drop table徹底刪除物理表 ,這樣可以避免系統表的較長時間鎖定。

22. 避免頻繁建立和刪除臨時表,以減少系統表資源的消耗。

23. 在新建臨時表時,如果一次性插入的資料量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高執行效率;如果資料量不大,為了緩和系統表的資源,應先create table,然後使用insert插入資料。

24. 在所有的儲存過程和觸發器的開始處設定 SET NOCOUNT ON ,在結束時設定 SET NOCOUNT OFF 。無需在執行儲存過程和觸發器的每個語句後向客戶端傳送 DONE_IN_PROC 訊息。

25. 儘量避免大事務操作,提高系統併發能力。

26. 應儘量一次性插入多條資料,例如,使用下面的SQL語句效能會很低:

insert into persons(id,name,age) values('Bill',24)
insert into persons(id,name,age) values('Mike',26)
insert into persons(id,name,age) values('John',20)


為了提升效能,可以一次性插入這3條記錄。

insert into persons(id,name,age) values('Bill',24),('Mike',26),('John',20)


27. 如果不得不使用like進行模糊查詢時,不要在關鍵字前面加%。

反例:

select id,name,age from persons where name like '%abc%'


如果在關鍵字前面加%,那麼查詢是肯定要走全表查詢的。

正例:

select id,name,age from persons where name like 'abc%'


28. 儘量用union all代替union

union和union all的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的cpu運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重複結果集或者不在乎重複結果集的時候,儘量使用union all而不是union。

 

29.儘量使用等值連線

等值連線就是inner join,也稱為內聯進,而left join和right join是外連線。

先看下面的SQL語句

select a.id,a.name,b.id,b.name from a left join b on a.id = b.id

select a.id,a.name,b.id,b.name from a right join b on a.id = b.id

select a.id,a.name,b.id,b.name from a inner join b on a.id = b.id


上面的3條SQL語句,前兩條分別使用了左連線和右連線,而最後一條使用了內連線,經過實際執行,使用內連線的SQL語句的執行效率明顯優於左連線和右連線。所以在能滿足需求的前提下,應該儘可能使用內連線(等值連線)。


30. 儘量用外連線來替換子查詢
反例

select id,name from a where exists (select id from b where id>=10 and a.product_id=b.product_id)

 

在上面的SQL語句中,資料庫引擎會先對外表a執行全表查詢,然後根據product_id逐個執行子查詢,如果外層表(a表)中的資料非常多,查詢效能會非常糟糕。所以應該將SQL語句改成下面的形式:

select id,name from a inner join b on A.product_id=b.product_id where b.id>=10

 

&n