Sql語句優化和索引
1.Innerjoin和左連接,右連接,子查詢
A. inner join內連接也叫等值連接是,left/rightjoin是外連接。
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 ON B A.id= B.id;
SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;
經過來之多方面的證實inner join性能比較快,因為inner join是等值連接,或許返回的行數比較少。但是我們要記得有些語句隱形的用到了等值連接,如:
SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;
推薦:能用inner join連接盡量使用inner join連接
B.子查詢的性能又比外連接性能慢,盡量用外連接來替換子查詢。
Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);
A表的數據為十萬級表,B表為百萬級表,在本機執行差不多用2秒左右,我們可以通過explain可以查看到子查詢是一個相關子查詢(DEPENDENCE SUBQUERY);Mysql是先對外表A執行全表查詢,然後根據uuid逐次執行子查詢,如果外層表是一個很大的表,我們可以想象查詢性能會表現比這個更加糟糕。
一種簡單的優化就是用innerjoin的方法來代替子查詢,查詢語句改為:
Select* from A inner join B using(uuid) where b.uuid>=3000;
這個語句執行測試不到一秒;
C.在使用ON 和 WHERE 的時候,記得它們的順序,如:
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id WHERE B.NAME=’XXX’
執行過程會先執行ON 後面先過濾掉B表的一些行數。然而WHERE是後再過濾他們兩個連接產生的記錄。
不過在這裡提醒一下大家:ON後面的條件只能過濾出B表的條數,但是連接返回的記錄的行數還是A表的行數是一樣。如:
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;
返回的記錄數是A表的條數,ON後面的條件只起到過濾B表的記錄數,而
SELECT A.id,A.name,B.id,B.name FROM A ,B WHERE A.id = B.id
返回的條數,是笛卡爾積後,符合A.id = B.id這個條件的記錄
D.使用JOIN時候,應該用小的結果驅動打的結果(left join 左邊表結果盡量小,如果有條件應該放到左邊先處理,right join同理反向),同事盡量把牽涉到多表聯合的查詢拆分多個query(多個表查詢效率低,容易鎖表和阻塞)。如:
Select * from A left join B ona.id=B.ref_id where B.ref_id>10;
可以優化為:select * from (select * from A wehre id >10) T1 left join B onT1.id=B.ref_id;
2.建立索引,加快查詢性能.
A.在建立復合索引的時候,在where條件中用到的字段在復合索引中,則最好把這個字段放在復合索引的最左端,這樣才能使用索引,才能提高查詢。
B.保證連接的索引是相同的類型,意思就是A表和B表相關聯的字段,必須是同類型的。這些類型都建立了索引,這樣才能兩個表都能使用索引,如果類型不一樣,至少有一個表使用不了索引。
C.索引,不僅僅是主鍵和唯一鍵,也可以是其他的任何列。在使用like其中一個有索引的字段列的時候。
如: select *from A name like ‘xxx%’;
這個sql會使用name的索引(前提name建立了索引);而下面的語句就使用不了索引
Select * from A name like ‘%xxx’;
因為‘%’代表任何字符,%xxx不知道怎麼去索引的,所以使用不了索引。
D.復合索引
比如有一條語句這樣的:select* from users where area =’beijing’ and age=22;
如果我們是在area和age上分別創建索引的話,由於mysql查詢每次只能使用一個索引,所以雖然這樣已經相對不做索引時全表掃描提高了很多效率,但是如果area,age兩列上創建復合索引的話將帶來更高的效率。如果我們創建了(area,age,salary)的復合索引,那麼其實相當於創建了(area,age,salary),(area,age),(area)三個索引,這樣稱為最佳左前綴特性。因此我們在創建復合索引的應該將最常用作限制條件的列放在最左邊,依次遞減。
E.索引不會包含有NULL值的列
只要列中包含有NULL值都將不會被包含在索引中(除非是唯一值的域,可以存在一個NULL),復合索引中只要有一列含有NULL值,那麼這一列對於此復合索引是無效的。所以我們在數據庫設計時不要讓字段的默認值為NULL.
F.使用短索引
對串列進行索引,如果可能應該指定一個前綴長度。例如,如果有一個CHAR(255)的列,如果在錢10個或者20字符內,多數值是唯一的,那麼就不要對整個列進行索引。短索引不僅可以提高查詢速度而且可以節省磁盤空間和I/O操作。
G.排序的索引問題
Mysql查詢只是用一個索引,因此如果where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求情況下不要使用排序操作;盡量不要包含多個列的排序,如果需要最好給這些列創建復合索引。
3.limit千萬級分頁的時候優化。
A.在我們平時用limit,如:
Select * from A order by id limit 1,10;
這樣在表數據很少的時候,看不出什麼性能問題,倘若到達千萬級,如:
Select * from A order by id limit10000000,10;
雖然都是只查詢10記錄,但是這個就性能就讓人受不了了。所以為什麼當表數據很大的時候,我們還繼續用持久層框架如hibernate,ibatis就會有一些性能問題,除非持久層框架對這些大數據表做過優化。
B.在遇見上面的情況,我們可以用另外一種語句優化,如:
Select * from A where id>=(Select idfrom a limit 10000000,1) limit 10;
確實這樣快了很多,不過前提是,id字段建立了索引。也許這個還不是最優的,其實還可以這樣寫:
Select * from A where id between 10000000and 10000010;
這樣的效率更加高。
4.盡量避免Select * 命令
A.從表中讀取越多的數據,查詢會變得更慢。它會增加磁盤的操作時間,還是在數據庫服務器與web服務器是獨立分開的情況下,你將會經歷非常漫長的網絡延遲。僅僅是因為數據不必要的在服務器之間傳輸。
5.盡量不要使用BY RAND()命令
A.如果您真需要隨機顯示你的結果,有很多更好的途徑實現。而這個函數可能會為表中每一個獨立的行執行BY RAND()命令—這個會消耗處理器的處理能力,然後給你僅僅返回一行。
6.利用limit 1取得唯一行
A.有時要查詢一張表時,你要知道需要看一行,你可能去查詢一條獨特的記錄。你可以使用limit 1.來終止數據庫引擎繼續掃描整個表或者索引,如:
Select * from A where namelike ‘%xxx’ limit 1;
這樣只要查詢符合like ‘%xxx’的記錄,那麼引擎就不會繼續掃描表或者索引了。
7.盡量少排序
A.排序操作會消耗較多的CPU資源,所以減少排序可以在緩存命中率高等
8.盡量少OR
A.當where子句中存在多個條件以“或”並存的時候,Mysql的優化器並沒有很好的解決其執行計劃優化問題,再加上mysql特有的sql與Storage分層架構方式,造成了其性能比較地下,很多時候使用union all或者union(必要的時候)的方式代替“or”會得到更好的效果。
9.盡量用union all 代替union
A.union和union all的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的cpu運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重復結果集或者不在乎重復結果集的時候,盡量使用union all而不是union.
10.避免類型轉換
A.這裡所說的“類型轉換”是指where子句中出現column字段的類型和傳入的參數類型不一致的時候發生的類型轉換。人為的上通過轉換函數進行轉換,直接導致mysql無法使用索引。如果非要轉型,應該在傳入參數上進行轉換。
11.不要在列上進行運算
A. 如下面:select * fromusers where YEAR(adddate)<2007;將在每個行進行運算,這些導致索引失效進行全表掃描,因此我們可以改成:
Select * from users where adddate<’2007-01-01’;
12.盡量不要使用NOT IN和<>操作
A. NOT IN和<>操作都不會使用索引,而是將會進行全表掃描。NOT IN可以NOT EXISTS代替,id<>3則可以使用id>3 or id <3;如果NOT EXISTS是子查詢,還可以盡量轉化為外連接或者等值連接,要看具體sql的業務邏輯。
B.把NOT IN轉化為LEFT JOIN如:
SELECT * FROM customerinfo WHERE CustomerIDNOT in (SELECT CustomerID FROM salesinfo );
優化:
SELECT * FROM customerinfo LEFT JOINsalesinfoON customerinfo.CustomerID=salesinfo. CustomerID WHEREsalesinfo.CustomerID IS NULL;
13.使用批量插入節省交互(最好是使用存儲過程)
A. 盡量使用insert intousers(username,password) values(‘test1’,’pass1’), (‘test2’,’pass2’), (‘test3’,’pass3’);
14. 鎖定表
A. 盡管事務是維護數據庫完整性的一個非常好的方法,但卻因為它的獨占性,有時會影響數據庫的性能,尤其是在很多的應用系統中.由於事務執行的過程中,數據庫將會被鎖定,因此其他的用戶請求只能暫時等待直到該事務結算.如果一個數據庫系統只有少數幾個用戶來使用,事務造成的影響不會成為一個太大問題;但假設有成千上萬的用戶同時訪問一個數據庫系統,例如訪問一個電子商務網站,就會產生比較嚴重的響應延遲.其實有些情況下我們可以通過鎖定表的方法來獲得更好的性能.如:
LOCK TABLE inventory write
Select quanity from inventory whereitem=’book’;
…
Update inventory set quantity=11 whereitem=’book’;
UNLOCK TABLES;
這裡,我們用一個select語句取出初始數據,通過一些計算,用update語句將新值更新到列表中。包含有write關鍵字的LOCK TABLE語句可以保證在UNLOCK TABLES命令被執行之前,不會有其他的訪問來對inventory進行插入,更新或者刪除的操作。
15.對多表關聯的查詢,建立視圖
A.對多表的關聯可能會有性能上的問題,我們可以對多表建立視圖,這樣操作簡單話,增加數據安全性,通過視圖,用戶只能查詢和修改指定的數據。且提高表的邏輯獨立性,視圖可以屏蔽原有表結構變化帶來的影響。