本文介紹一些優化 MySQL 索引設計和查詢的建議。在進行優化工做前,請務必瞭解MySQL EXPLAIN命令: 查看執行計劃html
索引在邏輯上是指從索引列(關鍵字)到數據的映射,經過索引能夠快速的由關鍵字查找到數據記錄。順序查找複雜度爲O(n), 樹狀索引查找複雜度爲O(logn), 哈希索引爲O(1)。算法
MySQL中的索引通常是指BTree索引, InnoDB存儲引擎使用B+樹來實現BTree索引。sql
BTree索引保持數據之間的順序,能夠極大的加快精確搜索(=, in)、範圍搜索(<,>), 去重(DISTINCT), 排序(ORDER BY) 和 聚合(GROUP BY)。數據庫
總結來講使用索引有三個優勢:函數
由於寫入數據時須要爲新行創建索引,因此索引會減慢寫入速度。請儘可能避免建立無用的索引。oop
示例:性能
SELECT * FROM `user` WHERE `id`=5; -- 可使用索引 SELECT * FROM `user` WHERE `id` + 1 = 5; -- 索引列做爲表達式一部分時沒法使用索引 SELECT * FROM `user` WHERE MD5(first_name)='MD5'; -- 索引列做爲函數參數時沒法使用索引
BTree索引具備最左匹配性質, 即只能按照索引列的順序自左向右搜索,不能跳過索引列。優化
聯合索引中存在範圍查詢(<, >, like, between) 會致使後面的索引列失效。設計
定義表和索引:code
CREATE TABLE `user` ( `id` INT, `first_name` VARCHAR(16), `middle_name` VARCHAR(16), `last_name` VARCHAR(16), PRIMARY KEY (`id`), KEY `idx_name` (`first_name`, `middle_name`, `last_name`) );
示例:
SELECT * FROM `user` WHERE `first_name`='a'; -- 可使用 idx_name 索引 SELECT * FROM `user` WHERE `first_name`='a' AND `middle_name`='b'; -- 可使用 idx_name 索引 SELECT * FROM `user` WHERE `first_name`='a' AND `middle_name`='b' AND `last_name`='c'; -- 可使用 idx_name 索引 SELECT * FROM `user` WHERE `middle_name`='b'; -- 不能使用 idx_name 索引 SELECT * FROM `user` WHERE `middle_name`='b' AND `last_name`='c'; -- 不能使用 idx_name 索引 SELECT * FROM `user` WHERE `first_name`='a' AND `last_name`='c'; -- 不能使用 idx_name 索引
上文中說的"可使用索引"是指能夠用ref
,eq_ref
或 range
方式進行查詢。
使用 EXPLAIN 命令查看3個不能使用索引示例的執行計劃,能夠發現 type 字段爲 index, 這是在索引樹上進行順序查找。雖然性能優於全表掃描, 但比 ref 和 range 查詢來講要慢不少。
索引列爲字符串等類型時, 可使用索引列的前綴字符串進行模糊查詢
select * from user where first_name = 'abc' AND middle_name like 'de%';
這條語句的類型的爲 range, 即在索引列上進行範圍查詢。
將聯合索引理解爲: 將索引列(關鍵字)按順序拼接, 把拼接後的關鍵字與數據創建映射。最左匹配便是使用關鍵字前綴縮小搜索範圍。
在進行多列搜索時有一條經驗法則: 首先使用選擇性高的列進行搜索。
咱們能夠將選擇性定義爲 count(distinct ) / count(*), 也就是說知足條件的數據越少,則條件的選擇性越高。
假設用戶名name比性別gender選擇性高, 那麼查詢應該寫做WHERE name='finley' AND gender='M'
而不是WHERE gender='M' AND name='finley'
。
實際上兩條語句是等效的, 當存在多個查詢條件時 MySQL 優化器會根據索引和選擇性決定最優的過濾順序。
爲每個列單獨創建索引,並不能有效支持多列查詢
CREATE TABLE `user` ( `id` INT, `first_name` VARCHAR(16), `middle_name` VARCHAR(16), `last_name` VARCHAR(16), PRIMARY KEY (`id`), KEY `idx_first_name` (`first_name`), KEY `idx_middle_name` (`middle_name`) );
查詢語句:
select * from user where first_name = 'a' AND middle_name = 'bc';
查看查詢計劃:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | user | ALL | idx_first_name,idx_middle_name | NULL | NULL | NULL | 4 | 100.00 | Using where |
根據最左匹配法則和優先使用高選擇性列的經驗法則,能夠得出一條建議:
對於須要進行多列查詢的表,應創建包含全部參與查詢列的聯合索引, 索引的順序應按照列的選擇性從強到弱排列
一般在使用索引檢索到數據以後,須要訪問磁盤上數據表文件讀取所須要的列,這種操做稱爲"回表"。
若索引中包含查詢的全部列,則不須要回表操做直接從索引文件中讀取數據便可, 這種索引稱爲覆蓋索引。
在查詢時儘可能減小"SELECT *"只查詢須要的行, 條件容許時儘可能創建覆蓋索引。
《數據庫索引設計與優化》一書中提出了判斷最佳索引的"三星索引"概念:
MySQL 在索引包含 null 的列時須要額外的開銷, 儘可能避免容許索引列上存在 null。
除非有很是嚴格的一致性要求,不然應避免使用外鍵。
關於主鍵:
在查詢較多且業務容許的狀況下, 推薦使用自增主鍵。
不知道放哪兒好的兩條建議:
MySQL在執行多表查詢時能夠採用Nest Loop Join算法,即選擇數據集較小的一張表(數據集)做爲驅動表, 遍歷驅動表中全部記錄並鏈接另外一張表中符合條件的記錄。
在使用 JOIN 進行查詢時 MySQL 會自動選擇數據集較小的一張表做爲驅動表。
LEFT JOIN 強制左表做爲驅動表, RIGHT JOIN 則強制選擇右表做爲驅動表。
MySQL 的 STRAIGHT_JOIN 結果與 INNER JOIN 相同, 但強制使用左表做爲驅動表, 可用來分析選擇不一樣驅動表的效果。
在業務容許的狀況下, 讓 MySQL 自行決定驅動表。
在使用 IN 進行多表查詢時通常會把 IN 內部的嵌套循環做爲驅動表, 應儘可能減小IN數據集的大小。實際上, MySQL 也會對 IN 和 EXISTS 查詢進行優化, 選擇最優的驅動表。