mysql索引查詢開發優化100點

一、字段

1,儘量使用TINYINT、SMALLINT、MEDIUM_INT作爲整數類型而非INT,如果非負則加上UNSIGNED;

2,VARCHAR的長度只分配真正需要的空間;

3,使用枚舉或整數代替字符串類型;

4,儘量使用TIMESTAMP而非DATETIME;

5,單表不要有太多字段,建議在 20 以內;

6,避免使用 NULL 字段,很難查詢優化且佔用額外索引空間;

7,用整型來存 IP。

8,如果其他數據需要經常需要查詢,而 blob/text 不需要,則將 blob/text 數據域其他數據分離。

9,壓縮 text 和 blob 數據類型 — 爲了節省空間,減少從磁盤讀數據。

10,主鍵最好使用自增型,保證數據連續性(mysql innodb 主鍵默認採用b+tree,索引和數據放在同一個btree中),不要使用uuid、hash、md5等

二、索引

1,索引並不是越多越好,要根據查詢有針對性的創建,考慮在WHERE和ORDER BY命令上涉及的列建立索引,可根據EXPLAIN來查看是否用了索引還是全表掃描;

2,應儘量避免在WHERE子句中對字段進行NULL值判斷,否則將導致引擎放棄使用索引而進行全表掃描;

3,值分佈很稀少數據重複即區分度很低的字段不適合建索引,例如 "性別",真假值 這種只有兩三個值的字段。區分度的公式是count(distinct col)/count(*)。

4,字符字段只建前綴索引;

5,字符字段最好不要做主鍵;

6,不用外鍵,由程序保證約束;

7,儘量不用UNIQUE,由程序保證約束;

8,使用多列索引時主意順序和查詢條件保持一致,同時刪除不必要的單列索引。

9,頻繁更新的字段不適合建立索引,更多的索引意味更多的維護成本

10,where條件中用不到的字段不適合建立索引,都用不到建立索引沒有意義還浪費空間

11,表數據可以確定比較少的不需要建索引

12,參與列計算的列不適合建索引

13,大字段(blob)不要建立索引,查詢也不會走索引。

14,當時間存儲爲時間戳保存的可以建立前綴索引。

15,當一個表中有100萬數據,而經常用到的數據只有40萬或40萬以下,是不用考慮建立索引的,沒什麼性能提升。過小的表,建索引可能會更慢哦 :(讀個2頁的宣傳手冊,你還先去找目錄?)

16,在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。創建複合索引,需要注意把區分度最大的放到最前面。如果第一個字段出現 範圍符號的查找,那麼將不會用到索引,如果我是第二個或者第三個字段使用範圍符號的查找,那麼他會利用索引。

17,負向條件查詢不能使用索引,可以優化爲 in 查詢。負向條件有:!=、<>、not in、not exists、not like 等。

18,聯合索引最左前綴原則(又叫最左側查詢),如果在(a,b,c)三個字段上建立聯合索引,那麼它能夠加快 a | (a,b) | (a,b,c) 三組查詢速度。
如果建立了(a,b)聯合索引,就不必再單獨建立 a 索引。同理,如果建立了(a,b,c)聯合索引,就不必再單獨建立 a、(a,b) 索引。

19,範圍列可以用到索引(聯合索引必須是最左前綴)。範圍條件有:<、<=、>、>=、between等。範圍列可以用到索引(聯合索引必須是最左前綴),但是範圍列後面的列無法用到索引,索引最多用於一個範圍列,如果查詢條件中有兩個範圍列則無法全用到索引。
假如有聯合索引 (empno、title、fromdate),那麼下面的 SQL 中 emp_no 可以用到索引,而 title 和 from_date 則使用不到索引。
select * fromemployees.titles where emp_no < 10010' and title='Senior Engineer'and from_date between '1986-01-01' and '1986-12-31'

20,如果有 order by、group by 的場景,請注意利用索引的有序性。
order by 最後的字段是組合索引的一部分,並且放在索引組合順序的最後,避免出現file_sort的情況,影響查詢性能。
例如對於語句 where a=? and b=? order by c,可以建立聯合索引(a,b,c)。
如果索引中有範圍查找,那麼索引有序性無法利用,如 WHERE a>10 ORDER BY b;,索引(a,b)無法排序。

21,單表索引建議控制在5個以內,單索引字段數不允許超過5個。字段超過5個時,實際已經起不到有效過濾數據的作用了。

22,SQL 性能優化 explain 中的 type:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts 最好。
consts:單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到數據。
ref:使用普通的索引(Normal Index)。
range:對索引進行範圍檢索。
當 type=index 時,索引物理文件全掃,速度非常慢。

23,業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。不要以爲唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的。
另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生。

24,建立索引的列,不允許爲 null。單列索引不存 null 值,複合索引不存全爲 null 的值,如果列允許爲 null,可能會得到“不符合預期”的結果集,所以,請使用 not null 約束以及默認值。

只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。所以我們在數據庫設計時不要讓字段的默認值爲NULL。

25,超過三個表最好不要 join。需要 join 的字段,數據類型必須一致,多表關聯查詢時,保證被關聯的字段需要有索引。

26,保證索引簡單,不要在同一列上加多個索引。

27,有時,增加列時,先刪除索引,之後在加上索引會更快。

28,不得使用外鍵與級聯,一切外鍵概念必須在應用層解決

29,如何使用索引來排序?

規則:1)ORDER BY子句後的列順序要與組合索引的列順序一致,且所有排序列的排序方向(正序/倒序)需一致

2)所查詢的字段值需要包含在索引列中,及滿足覆蓋索引

通過例子分析:

在user_test表上創建一個組合索引

ALTER TABLE user_test ADD INDEX index_user(user_name , city , age);

可以使用到索引排序的案例sql

1、SELECT user_name, city, age FROM user_test ORDER BY user_name;
 
2、SELECT user_name, city, age FROM user_test ORDER BY user_name, city;
 
3、SELECT user_name, city, age FROM user_test ORDER BY user_name DESC, city DESC;
 
4、SELECT user_name, city, age FROM user_test WHERE user_name = 'feinik' ORDER BY city;

注:第4點比較特殊一點,如果where查詢條件爲索引列的第一列,且爲常量條件,那麼也可以使用到索引

無法使用索引排序的案例

1)sex不在索引列中

SELECT user_name, city, age FROM user_test ORDER BY user_name, sex;

2)排序列的方向不一致

SELECT user_name, city, age FROM user_test ORDER BY user_name ASC, city DESC;

3)所要查詢的字段列sex沒有包含在索引列中

SELECT user_name, city, age, sex FROM user_test ORDER BY user_name;

4)where查詢條件後的user_name爲範圍查詢,所以無法使用到索引的其他列

SELECT user_name, city, age FROM user_test WHERE user_name LIKE 'feinik%' ORDER BY city;

5、多表連接查詢時,只有當ORDER BY後的排序字段都是第一個表中的索引列(需要滿足以上索引排序的兩個規則)時,方可使用索引排序。如:再創建一個用戶的擴展表user_test_ext,並建立uid的索引。

DROP TABLE IF EXISTS user_test_ext;
 
CREATE TABLE user_test_ext(
 
  id int AUTO_INCREMENT PRIMARY KEY,
 
  uid int NOT NULL,
 
  u_password VARCHAR(64) NOT NULL
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
ALTER TABLE user_test_ext ADD INDEX index_user_ext(uid);

走索引排序

SELECT user_name, city, age FROM user_test u LEFT JOIN user_test_ext ue ON u.id = ue.uid ORDER BY u.user_name;

不走索引排序

SELECT user_name, city, age FROM user_test u LEFT JOIN user_test_ext ue ON u.id = ue.uid ORDER BY ue.uid;

30,總結一條:
選擇唯一性索引;
爲經常需要排序、分組和聯合操作的字段建立索引;
爲常作爲查詢條件的字段建立索引;
限制索引的數目;
儘量使用值小的索引;
儘量使用前綴來索引,如果索引字段的值很長,最好使用值的前綴來索引。例如,TEXT和BLOG類型的字段,進行全文檢索會很浪費時間。如果只檢索字段的前面的若干個字符,這樣可以提高檢索速度。
刪除不再使用或者很少使用的索引;
最左前綴匹配原則,非常重要的原則;
索引列不能參與計算,保持列“乾淨”。
儘量的擴展索引,不要新建索引。 比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可






三、查詢
1,可通過開啓慢查詢日誌來找出較慢的 SQL;

2,不做列運算:SELECT id WHERE age + 1 = 10,任何對列的操作都將導致表掃描,它包括數據庫教程函數、計算表達式等等,查詢時要儘可能將操作移至等號右邊;

3,SQL 語句儘可能簡單:一條 SQL 只能在一個 CPU 運算;大語句拆小語句,減少鎖時間;一條大 SQL 可以堵死整個庫;
4,不用SELECT *;

5,OR改寫成IN:OR的效率是 n 級別,IN的效率是 log(n) 級別,in 的個數建議控制在 200 以內;

6,不用函數和觸發器,在應用程序實現;

7,避免%xxx式查詢;

8,少用JOIN;

9,使用同類型進行比較,比如用'123'和'123'比,123和123比;

10,儘量避免在WHERE子句中使用!= 或 <> 操作符,否則將引擎放棄使用索引而進行全表掃描;

11,對於連續數值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5;

12,列表數據不要拿全表,要使用LIMIT來分頁,每頁數量也不要太大。

13,使用count統計數據量的時候建議使用count(*)而不是count(列),因爲count(*)MySQL是做了優化的。

14,什麼時候開MySQL的查詢緩存? 交易系統(寫多、讀少)和SQL優化測試,建議關閉查詢緩存;論壇文章類系統(寫少、讀多),建議開啓查詢緩存。

15,複雜SQL語句優化的思路:首先考慮在一個表中能不能取到有關的信息,儘量少關聯表,關聯條件爭取都走主鍵或外鍵查詢條件,能走到對應的索引,https://gw.alicdn.com/tps/TB10M0wLpXXXXaaaXXXXXXXXXXX-109-109.png
爭取在滿足業務上走小集合數據查找,INNER JOIN 和子查詢哪個更快,場景不一致速度也不同

16,where條件多條件一定要按照小結果集排大結果集前面

17,儘量避免大事務操作,提高系統併發能力,有時無法避免,改用定時器延遲處理。

18,使用UNION ALL 替換OR多條件查詢並集,儘量用 union all 代替 union。

19,在大數據表刪除也是一個問題,避免刪除過程數據庫奔潰,可以考慮分配刪除,一次刪1000條,刪完後等一會繼續刪除

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

21,儘量避免使用遊標,因爲遊標的效率較差,如果遊標操作的數據超過1萬行,那麼就應該考慮改寫。

22,儘量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。

23,很多時候用 exists 代替 in 是一個好的選擇:select num from a where num in(select num from b) 用下面的語句替換: select num from a where exists(select 1 from b where num=a.num)

24, 二次SQL查詢區別不大的時候,不能按照二次執行的時間來判斷優化結果,沒準第一次查詢後又保存緩存數據,導致第二次查詢速度比第二次快,很多時候我們看到的都是假象。

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

26,頁面搜索嚴禁左模糊或者全模糊,如果需要可以用搜索引擎來解決。

27,如果明確知道只有一條結果返回,limit 1 能夠提高效率。

28,使用 INSERT ON DUPLICATE KEY 或 INSERT IGNORE 來代替 UPDATE,避免 UPDATE 前需要先 SELECT。

29,如果合適,用 GROUP BY 代替 DISTINCT。

30,利用延遲關聯或者子查詢優化超多分頁場景。
MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。

31,使用 ISNULL()來判斷是否爲 NULL 值,注意,NULL與任何值的直接比較都爲 NULL

32,禁止單條SQL語句同時更新多個表。

33,減少與數據庫交互次數,儘量採用批量SQL語句。

34,MySQL查詢只使用一個索引,因此如果where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。因此數據庫默認排序可以符合要求的情況下不要使用排序操作;儘量不要包含多個列的排序,如果需要最好給這些列創建複合索引。

那麼,如果在firstname、lastname、age這三個列上分別創建單列索引,效果是否和創建一個firstname、lastname、 age的多列索引一樣呢?答案是否定的,兩者完全不同。當我們執行查詢的時候,MySQL只能使用一個索引。如果你有三個單列的索引,MySQL會試圖選擇一個限制最嚴格的索引。但是,即使是限制最嚴格的單列索引,它的限制能力也肯定遠遠低於firstname、lastname、age這三個列上的多列索引。
 

35,應儘量避免在where子句中對字段進行null值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
低效:select * from t_credit_detail where Flistid is null ;

可以在Flistid上設置默認值0,確保表中Flistid列沒有null值,然後這樣查詢:
高效:select * from t_credit_detail where Flistid =0;

36,能用DISTINCT的就不用GROUP BY

37,提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉。

低效: SELECT JOB , AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = ‘PRESIDENT' OR JOB = ‘MANAGER'

高效: SELECT JOB , AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT' OR JOB = ‘MANAGER’ GROUP by JOB

38,深度分頁時,速度會很慢。可以利用最大最小ID臨界值查詢。

39,離散度更高的索引應該放在聯合索引的前面,因爲離散度高索引的可選擇性高。考慮一種極端的情況,數據表中有100條記錄,若INDEX(a,b)中a只有兩種情況,而b有100種情況。這樣對於查詢唯一記錄a = …,b = …時,先遍歷全部索引看滿足a條件的有50個索引節點,接下來還要再一個個遍歷這50個索引節點。如果是INDEX(b,a),先遍歷全部索引發現滿足b條件的索引節點只有一個,再遍歷這個節點發現也滿足a條件。

40,覆蓋索引優化。

  • 解釋一: 就是select的數據列只用從索引中就能夠取得,不必從數據表中讀取,換句話說查詢列要被所使用的索引覆蓋。
  • 解釋二: 索引是高效找到行的一個方法,當能通過檢索索引就可以讀取想要的數據,那就不需要再到數據表中讀取行了。如果一個索引包含了(或覆蓋了)滿足查詢語句中字段與條件的數據就叫做覆蓋索引。
  • 解釋三:是非聚集組合索引的一種形式,它包括在查詢裏的Select、Join和Where子句用到的所有列(即建立索引的字段正好是覆蓋查詢語句[select子句]與查詢條件[Where子句]中所涉及的字段,也即,索引包含了查詢正在查找的所有數據)。
  • 解釋四:如果索引的葉子節點包含了要查詢的數據,那麼就不用回表查詢了,也就是說這種索引包含(亦稱覆蓋)所有需要查詢的字段的值。

當發起一個被索引覆蓋的查詢時,在explain的Extra列可以看到 Using index的標識。

1) 不是所有類型的索引都可以成爲覆蓋索引。覆蓋索引必須要存儲索引的列,而哈希索引、空間索引和全文索引等都不存儲索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引。

ALERT TABLE t1 ADD KEY(staff_id); // 添加索引

select sql_no_cache count(staff_id) from t1

無where條件的查詢,可以通過索引來實現索引覆蓋查詢,但前提條件是,查詢返回的字段數足夠少,更不用說select *之類的了。

2)  

explain select sql_no_cache rental_date from t1 where inventory_id<80000\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: range
possible_keys: inventory_id
          key: inventory_id
      key_len: 3
          ref: NULL
         rows: 153734
        Extra: Using index condition
row in set (0.00 sec)

Extra:Using index condition 表示使用的索引方式爲二級檢索,即79999個書籤值被用來進行回表查詢。可想而知,還是會有一定的性能消耗的。

嘗試針對這個SQL建立聯合索引,如下:

alter table t1 add key(inventory_id,rental_date);

 執行計劃:

explain select sql_no_cache rental_date from t1 where inventory_id<80000\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: range
possible_keys: inventory_id,inventory_id_2
          key: inventory_id_2
      key_len: 3
          ref: NULL
         rows: 162884
        Extra: Using index
row in set (0.00 sec)

Extra:Using index 表示沒有會標查詢的過程,實現了索引覆蓋

3)  分頁查詢優化

select tid,return_date from t1 order by inventory_id limit 50000,10;

使用的是全表掃描。加上而外的排序,性能消耗是不低的。

如何通過覆蓋索引優化呢?

  我們創建一個索引,包含排序列以及返回列,由於tid是主鍵字段,因此,下面的複合索引就包含了tid的字段值

alter table t1 add index liu(inventory_id,return_date);

使用到了複合索引,並且不需要回表。

回想一下,如果查詢只需要掃描索引而無須回表,將帶來諸多好處。

(1)索引條目通常遠小於數據行大小,如果只讀取索引,MySQL就會極大地減少數據訪問量。

(2)索引按照列值順序存儲,對於I/O密集的範圍查詢會比隨機從磁盤中讀取每一行數據的I/O要少很多。

(3)InnoDB的輔助索引(亦稱二級索引)在葉子節點中保存了行的主鍵值,如果二級索引能夠覆蓋查詢,則可不必對主鍵索引進行二次查詢了。

    覆蓋索引就是從索引中直接獲取查詢結果,要使用覆蓋索引需要注意select查詢列中包含在索引列中;where條件包含索引列或者複合索引的前導列;查詢結果的字段長度儘可能少。

41,查詢字段和連接字段最好都加上索引!
數據庫多表聯查時,左連接Left join保證右表字段索引,右連接Right join保證左表字段索引,內連接inner join保證任一表連接字段索引。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章