本文主要從工作經驗中總結出來的經驗總結sql語句優化問題,下面我們用Demo來具體說明如何提高sql的執行效率:
1、關於limit分頁優化的問題
SELECT * FROM message_1 LIMIT 10000,10
這條語句執行速度很快,當我們把語句改成下面語句的時候再看看用了多久?
SELECT * FROM message_1 LIMIT 1000000,10
運行結果如下圖:
上面的語句整整用了17.7秒!這麼慢,用戶可是等不了了,那麼我們如何優化這句sql呢?
解決方法:
我們加“order by id”改造後的sql爲:
SELECT * FROM message_1 order by id LIMIT 1000000,10
執行圖如下:
只用了1.1秒,可以說稍微改動一下,效率提升了17倍。這是因爲這個sql語句使用了主鍵id來作爲索引,所以速度很快。
2、like語句的優化
SELECT content message_1 A WHERE content like '%我要學習%'
like後面使用了“%”,所以該sql語句查詢會進行全表掃描,使索引失效,因此速度會很慢,如果想走索引的話,可以改下下面的語句:
SELECT content FROM message_1 WHERE content like '我要學習%'
3、避免where語句中使用IS NULL或IS NOT NULL語句
SELECT content FROM message_1 WHERE content IS NULL
上面的語句同樣會使索引失效而進行全表掃描,我們在where語句中避免使用 IS NULL或者IS NOT NULL來進行條件選擇,這樣做會使索引失效,從而導致查詢速度很慢
4、不要在where條件中出現函數、算數運算或其他表達式運算
SELECT content FROM message_1 WHERE datediff(day,createTime,'2020-05-25')=0
改爲
SELECT content FROM message_1 WHERE createTime>='2020-05-01' and createTime<'2020-05-25'
這句話告訴我們不要在where條件中出現函數、算數運算或其他表達式運算,否則也會使索引失效。
5、排序的索引問題
如果mysql查詢語句中只用了一個索引,而where條件中已經使用了索引,則order by中的字段就不會使用索引。因此儘量不要同時對多個字段進行排序,如果有這樣的場景存在的話,那最好給這些字段設置聯合索引。
6、union all替換union
union和union all的區別在於前者需要合併兩個以上的結果集,然後在進行唯一性過濾操作,這樣做肯定會涉及到數據的排序,增大cpu的運算與資源消耗和延遲。因此,當在確定沒有重複數據或者不關心重複數據的情況下,要使用union all。
7、left joinright join和inner join
SELECT A.id,A.name,B.id,B.name FROM message_1 LEFT JOIN message_2 b ON b.id =B.id;
SELECT A.id,A.name,B.id,B.name FROM message_1 RIGHT JOIN message_2 b ON B A.id= B.id;
SELECT A.id,A.name,B.id,B.name FROM message_1 INNER JOIN message_2 b ON A.id =B.id;
上面的語句運行後發現inner join運行速度比較快,因爲inner join是等值連接,返回的行數比較少。所以在項目開發過程中最好使用inner join
8、索引的數量控制在5個以內
因爲不是索引越多就越好。索引儘管提高了查詢效率,但是也是降低修改和新增的效率。而且insert和update有重建索引的可能,所以一張表的索引數最好不要超過五個,如果超過五個那麼就得針對這張表做優化處理了
9、任何情況下都禁止使用 select * from table
因爲這樣會進行全表掃描而導致效率很低
SELECT * FROM message_1
應改爲
SELECT content FROM message_1
10、優化批量插入
INSERT INTO message_1(id,content) values(1,'內容1')
INSERT INTO message_1(id,content) values(2,'內容2')
INSERT INTO message_1(id,content) values(3,'內容3')
INSERT INTO message_1(id,content) values(4,'內容4')
INSERT INTO message_1(id,content) values(5,'內容5')
應改爲
INSERT INTO message_1(id,content) values(1,'內容1'),(2,'內容2'),(3,'內容3'),(4,'內容4'),(5,'內容5'
11、使用聯合索引時,注意索引列的順序,一般遵循最左匹配原則
表結構:(有一個聯合索引 idxuseridage,userId 在前,age 在後)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userId` int(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_userid_age` (`userId`,`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
反例:
select * from user where age = 10;
正例:
//符合最左匹配原則
select * from user where userid=10 and age =10;
//符合最左匹配原則
select * from user where userid =10;
理由如下:
-
當我們創建一個聯合索引的時候,如(k1,k2,k3),相當於創建了(k1)、(k1,k2)和(k1,k2,k3)三個索引,這就是最左匹配原則。
-
聯合索引不滿足最左原則,索引一般會失效,但是這個還跟 MySQL 優化器有關的。
12、慎用 distinct 關鍵字
distinct 關鍵字一般用來過濾重複記錄,以返回不重複的記錄。在查詢一個字段或者很少字段的情況下使用時,給查詢帶來優化效果。但是在字段很多的時候使用,卻會大大降低查詢效率。
反例:
SELECT DISTINCT * from user;
正例:
select DISTINCT name from user;
理由:帶 distinct 的語句 CPU 時間和佔用時間都高於不帶 distinct 的語句。
因爲當查詢很多字段時,如果使用 distinct,數據庫引擎就會對數據進行比較,過濾掉重複數據,然而這個比較、過濾的過程會佔用系統資源,CPU 時間。
13 、exist & in 優化
SELECT * from A WHERE id in ( SELECT id from B )
SELECT * from A WHERE id EXISTS ( SELECT 1 from A.id= B.id )
分析:
in 是在內存中遍歷比較
exist 需要查詢數據庫,所以當B的數據量比較大時,exists效率優於in**
in()只執行一次,把B表中的所有id字段緩存起來,之後檢查A表的id是否與B表中的id相等,如果id相等則將A表的記錄加入到結果集中,直到遍歷完A表的所有記錄。
In 操作的流程原理如同一下代碼
List resultSet={};
Array A=(select * from A);
Array B=(select id from B);
for(int i=0;i<A.length;i++) {
for(int j=0;j<B.length;j++) {
if(A[i].id==B[j].id) {
resultSet.add(A[i]);
break;
}
}
}
return resultSet;
可以看出,當B表數據較大時不適合使用in(),因爲會把B表數據全部遍歷一次
如:A表有10000條記錄,B表有1000000條記錄,那麼最多有可能遍歷10000*1000000次,效率很差。
再如:A表有10000條記錄,B表有100條記錄,那麼最多有可能遍歷10000*100次,遍歷次數大大減少,效率大大提升。
結論:in()適合B表比A表數據小的情況
exist()會執行A.length()次,執行過程代碼如下
List resultSet={};
Array A=(select * from A);
for(int i=0;i<A.length;i++) {
if(exists(A[i].id) { //執行select 1 from B where B.id=A.id是否有記錄返回
resultSet.add(A[i]);
}
}return resultSet;
當B表比A表數據大時適合使用exists(),因爲它沒有那麼多遍歷操作,只需要再執行一次查詢就行。
如:A表有10000條記錄,B表有1000000條記錄,那麼exists()會執行10000次去判斷A表中的id是否與B表中的id相等。
如:A表有10000條記錄,B表有100000000條記錄,那麼exists()還是執行10000次,因爲它只執行A.length次,可見B表數據越多,越適合exists()發揮效果。
再如:A表有10000條記錄,B表有100條記錄,那麼exists()還是執行10000次,還不如使用in()遍歷10000*100次,因爲in()是在內存裏遍歷比較,而exists()需要查詢數據庫
原創聲明:本文爲【Java學習提升】原創博文,轉載請註明出處。
本文來源於公衆號:【Java學習提升】 專注於Java領域技術分享,Java知識體系學習、分享面試經驗,讓我們結伴而行,共同成長!