MySQL(四)索引的使用
目錄
索引使用原則
列的離散度
聯合索引最左匹配
覆蓋索引
索引條件下推(ICP)
索引的創建與使用
索引創建基本原則
什么時候用不到索引
?
我們創建索引主要是為了提高查詢數據的效率,但是索引并不是越多越好,也不是所有的列都可以創建索引
索引使用原則
列的離散度
列的離散度的公式:count(distinct(column_name)) : count(*)?列的全部不同值和所有數據行的比例。
數據行數相同的情況下,分子越大,列的離散度就越高。簡單來說,如果列的重復值越多,離散度就越低,重復值越少,離散度就越高。
當我們建立的索引后去檢索數據,如果重復值太多,那么就需要掃描更多的行數
原則一:建立索引,要使用離散度更高的字段。
聯合索引最左匹配
在實際使用時,我們往往需要進行多條件查詢,會建立聯合索引。單列索引可以看成是特殊的聯合索引
#建表語句 CREATE TABLE `user_innodb` (`id` int(11) PRIMARY KEY AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`gender` tinyint(1) DEFAULT NULL,`phone` varchar(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;#創建聯合索引 ALTER TABLE user_innodb add INDEX comidx_name_phone(name,phone);聯合索引在B+樹中是復合的數據結構,將多個字段當作一個整體來組合,它是按照從左到右的順序來建立搜索樹的(name在左邊,phone在右邊)。從上圖不難發現,name是有序的,phone是無序的(因為索引的B+樹要求有序)。當name相等的時候, phone才是有序的,也就是當name相等的時候才有可以通過phone的"索引"來進行檢索
比方說使用這樣的條件查詢where name='Tom' and phone = '137XXX'去查詢數據的時候,B+樹會優先比較 name 來確定之后是向右子樹還是左子樹檢索。當找到name相同的節點的時候才會去比較phone。但是如果查詢條件只有phone,那么MySQL就不知道如何去比較節點,因為建立B+樹的時候name是第一個比較元素,只用phone是無序的,無法比較。
原則二:最左匹配原則,建立聯合索引的時候,一定要把最常用的列放在最左邊。
舉例:
對于上述的聯合索引
SELECT * FROM user_innodb WHERE name= 'chenpp' AND phone='150992121'; 可以使用到索引
SELECT * FROM user_innodb WHERE name= 'chenpp' 也可以使用到索引
SELECT * FROM user_innodb WHERE phone='150992121';不可以使用到索引
也就是說我們創建聯合索引(name,phone)的時候就相當于建立了兩個索引(name)和(name,phone)。
如果我們創建三個字段的聯合索引index(a,b,c),相當于創建三個索引:index(a),index(a,b),index(a,b,c)
用 where b=? 和 where b=? and c=? 是不能使用到索引的。但是where a=? and c=?是可以使用到索引的
不能不用第一個字段
可以驗證下:
CREATE TABLE `user_innodb2` (`id` int(11) PRIMARY KEY AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`alias` varchar(11) DEFAULT NULL,`gender` tinyint(1) DEFAULT NULL,`phone` varchar(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ALTER TABLE user_innodb2 add INDEX comidx_name_alias_phone(name,alias,phone);覆蓋索引
回表:對于非主鍵索引,我們先通過索引找到主鍵索引的鍵值,再通過主鍵值查出主鍵索引里面的數據值,它比基于主鍵索引的查詢多掃描了一棵索引樹,這個過程就叫回表
對于innoDB里通過非主鍵索引進行查詢,就會發生回表
在輔助索引里面,不管是單列索引還是聯合索引,如果select的數據列只用從索引中就能夠取得,不必從數據區中讀取,這時候使用的索引就叫做覆蓋索引,這樣就避免了回表。--- 這是為什么不建議開發使用select * from XXX
Extra里面值為“Using index”代表使用了覆蓋索引。
索引條件下推(ICP)
默認索引條件下推是開啟的
CREATE TABLE `employees` (`emp_no` int(11) NOT NULL,`birth_date` date NULL,`first_name` varchar(14) NOT NULL,`last_name` varchar(16) NOT NULL,`gender` enum('M','F') NOT NULL,`hire_date` date NULL,PRIMARY KEY (`emp_no`) ) ENGINE=InnoDB DEFAULT;alter table employees add index idx_lastname_firstname(last_name,first_name);#關閉ICP set optimizer_switch='index_condition_pushdown=off'; #開啟ICP set optimizer_switch='index_condition_pushdown=on'; #查看ICP參數 show variables like 'optimizer_switch';INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (1, NULL, '698', 'liu', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (2, NULL, 'd99', 'zheng', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (3, NULL, 'e08', 'huang', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (4, NULL, '59d', 'lu', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (5, NULL, '0dc', 'yu', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (6, NULL, '989', 'wang', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (7, NULL, 'e38', 'wang', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (8, NULL, '0zi', 'wang', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (9, NULL, 'dc9', 'xie', 'F', NULL); INSERT INTO `employees` (`emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`) VALUES (10, NULL, '5ba', 'zhou', 'F', NULL);如果我們要查詢所有姓wang,并且名字最后一個字是zi的員工,比如王麻子,王瘦子
SQL: select * from employees where last_name='wang' and first_name LIKE '%zi' ;
這條SQL有兩種執行方式:
1.根據聯合索引查出所有姓wang的二級索引數據,然后回表,到主鍵索引上查詢全部符合條件的數據(3 條數據)。然后返回給 Server 層,在Server 層過濾出名字以zi結尾的員工。
2.根據聯合索引查出所有姓wang的二級索引數據(3個索引),然后從二級索引中篩選出first_name以zi結尾的索引(1個索引),然后再回表,到主鍵索引上查詢全部符合條件的數據(1條數據),返回給Server 層。
很明顯第二種辦法查詢索引樹的次數更少
注意,索引的比較是在存儲引擎進行的,而數據記錄的比較在Server層進行的。而當first_name的條件不能用于索引過濾時,Server 層不會把first_name的條件傳遞給存儲引擎,所以會多讀取兩條無用的記錄。
這時候,如果滿足last_name='wang'的記錄有100000條,就會有99999條無用的記錄需要讀取,這是對資源的浪費。
實際執行一下
ICP開啟時:
此時會把first_name LIKE '%zi'下推給存儲引擎,只會從數據表讀取所需的1條記錄。
索引條件下推(Index Condition Pushdown),是MySQL5.6以后完善的功能。只適用于二級索引。
ICP關閉后:
Using Where代表從存儲引擎取回的數據不全部滿足條件,需要在Server 層過濾。
先用last_name 條件進行索引檢索,讀取數據表記錄后,再進行比較,檢查是否符合first_name LIKE '%zi' 的條件。此時3條中只有1條符合條件。
總的來說,索引條件下推的目標就是為了減少訪問表的完整行數從而減少 I/O 操作。
索引的創建與使用
Mysql在查詢的時候只會使用一個索引,但不是一個字段
索引創建基本原則
1.在用于where判斷,order排序(B+樹便于進行排序和范圍查詢),join的(on)字段上創建索引
2.索引的個數不要過多。 —— 因為更新數據的時候,還需要對索引也進行更新,過多的索引會造成空間浪費,導致更新變慢。
3.區分度低的字段,例如性別,不要建索引。—— ?離散度太低,導致掃描行數過多。
4.頻繁更新的值,不要作為主鍵或者索引。—— ?因為會發生頁分裂和頁合并,造成額外的開銷
5.組合索引把散列性高(區分度高)或者常用的值放在前面。
6.盡量創建復合索引,而不是修改單列索引。
7.?索引會忽略null值,所以在設計數據庫的時候需要設置為NOT NULL。
8.不適用于負向查詢操作(not in , <>, !=,or) //用到or地方,盡量用union,或者程序兩次查找
9.過長的字段,怎么建立索引? ?
可以使用前綴索引或者對這個較長字段進行hash運算,使用其作為索引(如果發生hash沖突就再過濾就好了)
10.不建議用無序的值(例如身份證、UUID )作為索引,主要是主鍵索引
使用遞增的id作為索引的時候,在新增數據的時候,由于是遞增的,如果原來的葉已滿,就會創建新的葉,而如果索引是無序的,那么在新增數據的時候,會強行插入到已滿的葉中,導致葉分裂,從而有額外的開銷;所以主鍵一般不建議使用uuid,其他索引不能保證有序
什么時候用不到索引
1.在索引列上使用函數(replace\SUBSTR\CONCAT\sum count avg)、表達式、計算(+ - * /):
對索引字段做函數操作,可能會破壞索引值的有序性,因此優化器就決定放棄使用索引樹查詢。
MySQL 無法再使用索引快速定位功能,而只能使用全索引掃描或者全表掃描。
2.字符串條件查詢不加引號,出現隱式轉換(如果是id = '1'是可以走索引的)
ALTER TABLE user_innodb add INDEX comidx_name_phone(name,phone);
為什么varchar字段的查詢條件不加引號會導致無法使用索引呢?
因為在MySQL 中,字符串和數字做比較的話,是將字符串轉換成數字再比較的。
對于非數字的字符串,MySQL里統一都會轉成數字0
這樣一來,判斷name = 136的時候,如果想走索引,就需要把對應索引樹的name的值全部轉化成數字,一個是轉化時候費時,而轉化后之后原來name的大小關系也被破壞了(都變成了0) 開銷成本太大,還不如全表掃描(不過當使用覆蓋索引的時候可以使用到索引,index連接類型,掃描全部的二級索引樹)
其實這樣的SQL在寫的時候也是不推薦的,一個是性能問題,一個是查詢出來的數據也不準確
3.like條件中前面帶%
對于%模糊匹配,%chenpp無法使用到索引,但是chenpp%可以使用到。如果實在需要進行模糊匹配,可以使用全文索引。
4. 負向查詢 不是一定不能使用到索引
NOT LIKE 不能使用索引:
explain select * from employees where last_name not like'wang'
!= (<>)和NOT IN 在某些情況下可以:
explain select * from employees where emp_no not in (1)
explain select * from employees where emp_no <> 1
?
注意一個SQL語句是否使用索引,跟數據庫版本、數據量、數據選擇度都有關系。
?
實際執行一個SQL的時候,用不用索引,使用什么索引,最終都是由優化器來決定的。
MySQL的優化器是基于cost開銷的優化器(Cost Base Optimizer),而不是基于規則(Rule-Based Optimizer)。基于開銷的優化器不像基于規則的優化器,有具體的執行標準,它在執行的時候會受多種因素影響(比方說數據庫版本、數據量、數據選擇度),會實時計算不同的執行路徑的開銷,去選擇開銷最小的方案。
最后再總結下索引的幾種概念 :?
聚集索引:
聚集索引是指索引鍵值的邏輯順序和數據庫表行數據的物理順序相同。一個表只能有一個聚集索引,因為一個表的物理順序只有一種情況。 在innodb里,其主鍵索引就是聚集索引
主鍵索引 :?是一種特殊的唯一索引,一張表中只能定義一個主鍵索引,通常有一列或列組合,用于唯一標識一條記錄,在InnoDB里就是依賴主鍵索引來組織數據的
聯合索引 :?與之相對的是單列索引,就是索引是由不少于2個的數據列組織的,像INDEX(columnA, columnB),這就是聯合索引。
覆蓋索引 :?InnoDB里輔助索引的葉子節點保存的是主鍵索引的值,所以使用輔助索引(非主鍵索引)查詢,會先在二級索引樹找到對應的主鍵索引值,再根據主鍵索引去查詢數據,這個過程叫做回表, 如果一個SQL只需要查詢一次索引樹不需要回表就可以找到需要返回的數據(也就是說查詢的字段在輔助索引里就可以完全找到),那么就是覆蓋索引
索引條件下推 :?ICP就是為了減少完整記錄讀取的條數,它對InnoDB聚集索引無效,只能對輔助索引有效 。
一個SQL并不是所有查詢條件都可以使用到索引,如果使用了索引條件下推,那么在通過二級索引樹檢索到主鍵索引值后,會在存儲引擎端根據其他的條件先篩選滿足的主鍵值,然后根據篩選后的主鍵集合去回表獲取最終的表數據。 所以使用了索引條件下推可以減少需要回表的次數,就不需要全部查詢出來后在server端過濾?
?? ??? ??? ?? ?
補充2020-03-01:
即使查詢條件不滿足最左匹配原則,如果使用到了覆蓋索引,那么最終查詢的時候也是可以走索引的
比如:這里雖然只以phone為條件不滿足最左匹配原則,但是因為查詢列只有name,phone 那么只要掃描整個輔助索引樹就可以查詢到結果了,不需要進行全表掃描(但是如果在查詢列里加上gender字段,就會全表掃描) ?不過這里使用的是index連接類型(?Full Index Scan),會查詢全部索引中的數據,雖然比全表掃描快但也是需要優化的
反過來,即使查詢條件滿足最左匹配原則,根據實際執行的情況也可能不走索引(因為MySQL的優化器是基于cost開銷的優化器)
#創建這樣的一張表,隨意添加10條數據(age 都大于3) CREATE TABLE `user_innodb` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`gender` tinyint(1) DEFAULT NULL,`phone` varchar(11) DEFAULT NULL,`age` int(4) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_age_phone` (`age`,`phone`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=100002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;從執行計劃可以看出最終走了全表掃描,這是因為使用age>1查詢輔助索引樹(二級索引樹),查詢出來所有的主鍵都滿足,那么就需要對每個主鍵進行回表,開銷太大了,直接全表掃描更快
如果使用age>10來查詢就不會出現全表掃描了
或者刪除數據只剩余3條,就可以看到會使用到索引
再次強調:一個SQL語句是否使用索引,跟數據庫版本、數據量、數據選擇度都有關系。
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的MySQL(四)索引的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JDBC连接失败java.sql.SQL
- 下一篇: MySQL(五)MySQL事务