挽救資料庫性能的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