一. MySQL內部如何選索引
數據准備:
用到的還是employees表,name-age-position為聯合索引。
CREATE TABLE `employees` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名', `age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡', `position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位', `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間', PRIMARY KEY (`id`), KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';
1. 案例1
EXPLAIN select * from employees where name > 'a';
執行計划如下:

分析:如果用name索引需要遍歷name字段聯合索引樹,然后還需要根據遍歷出來的主鍵值去主鍵索引樹里再去查出最終數據,成本比全表掃描 還高,所以mysql優化器直接選擇使用全表掃描。
2. 案例2
EXPLAIN select name,age,position from employees where name > 'a' ;
執行計划如下:

分析:使用覆蓋索引優化,這樣只需要遍歷name字段的聯合索引樹就能拿到所有結果。
3. 案例3
EXPLAIN select * from employees where name > 'zzz' ;
執行計划如下:

分析:對於上面這兩種 name>'a' 和 name>'zzz' 的執行結果,mysql最終是否選擇走索引或者一張表涉及多個索引,mysql最終如何選擇索引,我們可以用trace工具來一查究竟,開啟trace工具會影響mysql性能,所以只能臨時分析sql使用,用 完之后立即關閉。
Trace工具分析
1 mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐開啟trace 2 mysql> select * from employees where name > 'a' order by position; 3 mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE; 4 5 查看trace字段: 6 { 7 "steps": [ 8 { 9 "join_preparation": { ‐‐第一階段:SQL准備階段 10 "select#": 1, 11 "steps": [ 12 { 13 "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`empl oyees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position`" 14 } 15 ] /* steps */ 16 } /* join_preparation */ 17 }, 18 { 19 "join_optimization": { ‐‐第二階段:SQL優化階段 20 "select#": 1, 21 "steps": [ 22 { 23 "condition_processing": { ‐‐條件處理 24 "condition": "WHERE", 25 "original_condition": "(`employees`.`name` > 'a')", 26 "steps": [ 27 { 28 "transformation": "equality_propagation", 29 "resulting_condition": "(`employees`.`name` > 'a')" 30 }, 31 { 32 "transformation": "constant_propagation", 33 "resulting_condition": "(`employees`.`name` > 'a')" 34 }, 35 { 36 "transformation": "trivial_condition_removal", 37 "resulting_condition": "(`employees`.`name` > 'a')" 38 } 39 ] /* steps */ 40 } /* condition_processing */ 41 }, 42 { 43 "substitute_generated_columns": { 44 } /* substitute_generated_columns */ 45 }, 46 { 47 "table_dependencies": [ ‐‐表依賴詳情 48 { 49 "table": "`employees`", 50 "row_may_be_null": false, 51 "map_bit": 0, 52 "depends_on_map_bits": [ 53 ] /* depends_on_map_bits */ 54 } 55 ] /* table_dependencies */ 56 }, 57 { 58 "ref_optimizer_key_uses": [ 59 ] /* ref_optimizer_key_uses */ 60 }, 61 { 62 "rows_estimation": [ ‐‐預估表的訪問成本 63 { 64 "table": "`employees`", 65 "range_analysis": { 66 "table_scan": { ‐‐全表掃描情況 67 "rows": 10123, ‐‐掃描行數 68 "cost": 2054.7 ‐‐查詢成本 69 } /* table_scan */, 70 "potential_range_indexes": [ ‐‐查詢可能使用的索引 71 { 72 "index": "PRIMARY", ‐‐主鍵索引 73 "usable": false, 74 "cause": "not_applicable" 75 }, 76 { 77 "index": "idx_name_age_position", ‐‐輔助索引 78 "usable": true, 79 "key_parts": [ 80 "name", 81 "age", 82 "position", 83 "id" 84 ] /* key_parts */ 85 } 86 ] /* potential_range_indexes */, 87 "setup_range_conditions": [ 88 ] /* setup_range_conditions */, 89 "group_index_range": { 90 "chosen": false, 91 "cause": "not_group_by_or_distinct" 92 } /* group_index_range */, 93 "analyzing_range_alternatives": { ‐‐分析各個索引使用成本 94 "range_scan_alternatives": [ 95 { 96 "index": "idx_name_age_position", 97 "ranges": [ 98 "a < name" ‐‐索引使用范圍 99 ] /* ranges */, 100 "index_dives_for_eq_ranges": true, 101 "rowid_ordered": false, ‐‐使用該索引獲取的記錄是否按照主鍵排序 102 "using_mrr": false, 103 "index_only": false, ‐‐是否使用覆蓋索引 104 "rows": 5061, ‐‐索引掃描行數 105 "cost": 6074.2, ‐‐索引使用成本 106 "chosen": false, ‐‐是否選擇該索引 107 "cause": "cost" 108 } 109 ] /* range_scan_alternatives */, 110 "analyzing_roworder_intersect": { 111 "usable": false, 112 "cause": "too_few_roworder_scans" 113 } /* analyzing_roworder_intersect */ 114 } /* analyzing_range_alternatives */ 115 } /* range_analysis */ 116 } 117 ] /* rows_estimation */ 118 }, 119 { 120 "considered_execution_plans": [ 121 { 122 "plan_prefix": [ 123 ] /* plan_prefix */, 124 "table": "`employees`", 125 "best_access_path": { ‐‐最優訪問路徑 126 "considered_access_paths": [ ‐‐最終選擇的訪問路徑 127 { 128 "rows_to_scan": 10123, 129 "access_type": "scan", ‐‐訪問類型:為scan,全表掃描 130 "resulting_rows": 10123, 131 "cost": 2052.6, 132 "chosen": true, ‐‐確定選擇 133 "use_tmp_table": true 134 } 135 ] /* considered_access_paths */ 136 } /* best_access_path */, 137 "condition_filtering_pct": 100, 138 "rows_for_plan": 10123, 139 "cost_for_plan": 2052.6, 140 "sort_cost": 10123, 141 "new_cost_for_plan": 12176, 142 "chosen": true 143 } 144 ] /* considered_execution_plans */ 145 }, 146 { 147 "attaching_conditions_to_tables": { 148 "original_condition": "(`employees`.`name` > 'a')", 149 "attached_conditions_computation": [ 150 ] /* attached_conditions_computation */, 151 "attached_conditions_summary": [ 152 { 153 "table": "`employees`", 154 "attached": "(`employees`.`name` > 'a')" 155 } 156 ] /* attached_conditions_summary */ 157 } /* attaching_conditions_to_tables */ 158 }, 159 { 160 "clause_processing": { 161 "clause": "ORDER BY", 162 "original_clause": "`employees`.`position`", 163 "items": [ 164 { 165 "item": "`employees`.`position`" 166 } 167 ] /* items */, 168 "resulting_clause_is_simple": true, 169 "resulting_clause": "`employees`.`position`" 170 } /* clause_processing */ 171 }, 172 { 173 "reconsidering_access_paths_for_index_ordering": { 174 "clause": "ORDER BY", 175 "steps": [ 176 ] /* steps */, 177 "index_order_summary": { 178 "table": "`employees`", 179 "index_provides_order": false, 180 "order_direction": "undefined", 181 "index": "unknown", 182 "plan_changed": false 183 } /* index_order_summary */ 184 } /* reconsidering_access_paths_for_index_ordering */ 185 }, 186 { 187 "refine_plan": [ 188 { 189 "table": "`employees`" 190 } 191 ] /* refine_plan */ 192 } 193 ] /* steps */ 194 } /* join_optimization */ 195 }, 196 { 197 "join_execution": { ‐‐第三階段:SQL執行階段 198 "select#": 1, 199 "steps": [ 200 ] /* steps */ 201 } /* join_execution */ 202 } 203 ] /* steps */ 204 } 205 206 結論:全表掃描的成本低於索引掃描,所以mysql最終選擇全表掃描 207 208 mysql> select * from employees where name > 'zzz' order by position; 209 mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE; 210 211 查看trace字段可知索引掃描的成本低於全表掃描,所以mysql最終選擇索引掃描 212 213 mysql> set session optimizer_trace="enabled=off"; ‐‐關閉trace
二. Order By 和 Group By
1. 案例分析
案例1
explain select * from employees where name='LiLei' and position='dev' order by age;
執行計划:

分析:
利用最左前綴法則:中間字段不能斷,因此查詢用到了name索引,從key_len=74也能看出,age索引列用在排序過程中,因為Extra字段里沒有using filesort的出現。
案例2
explain select * from employees where name='LiLei' order by position;
執行計划:

分析:
從explain的執行結果來看:key_len=74,查詢使用了name索引,由於用了position進行排序,跳過了 age,出現了Using filesort。
案例3
explain select * from employees where name='LiLei' order by age,position;
執行計划:

分析:
查找只用到索引name,age和position用於排序,無Using filesort。
案例4
explain select * from employees where name='LiLei' order by position,age;
執行計划:

分析:
出現了Using filesort,因為索引的創建順序為 name,age,position,但是排序的時候是先排position,后排age,顯然根據B+Tree的結構,索引無法達到目的,所以用到了filesort。
案例5
explain select * from employees where name='LiLei' and age=18 order by position,age;
執行計划:

分析:
在Extra中並未出現Using filesort,因為age為常量,在排序中被優化,所以索引未顛倒, 不會出現Using filesort。
案例6
explain select * from employees where name='zhuge' order by age asc,position desc;
執行計划:

分析:
雖然排序的字段列與索引順序一樣,且order by默認升序,這里position desc變成了降序,導致與索引的排序方式(聯合索引每個字段都是升序排列)不同,從而產生Using filesort。Mysql8以上版本有降序索引可以支持該種查詢方式。
案例7
explain select * from employees where name in ('LiLei','zhuge') order by age,position;
執行計划:

分析:
對於排序來說,多個相等條件也是范圍查詢。
案例8
(1).
explain select * from employees where name > 'a' order by name;

(2). 用覆蓋索引優化
explain select name,age,position from employees where name > 'a' order by name;

2. 優化總結
(1). MySQL支持兩種方式的排序filesort和index,Using index是指MySQL掃描索引本身完成排序。index 效率高,filesort效率低。
(2). order by滿足兩種情況會使用Using index。
A. order by語句使用索引最左前列。
B. 使用where子句與order by子句條件列組合滿足索引最左前列。
(3). 盡量在索引列上完成排序,遵循索引建立(索引創建的順序)時的最左前綴法則。
(4). 如果order by的條件不在索引列上,就會產生Using filesort。
(5). 能用覆蓋索引盡量用覆蓋索引
(6). group by與order by很類似,其實質是先排序后分組,遵照索引創建順序的最左前綴法則。對於group by的優化如果不需要排序的可以加上order by null禁止排序。注意,where高於having,能寫在where中 的限定條件就不要去having限定了。
3. Using filesort文件排序原理詳解
(1). 文件排序方式
A. 單路排序:是一次性取出滿足條件行的所有字段,然后在sort buffer中進行排序;用trace工具可 以看到sort_mode信息里顯示< sort_key, additional_fields >或者< sort_key, packed_additional_fields >
B. 雙路排序(又叫回表排序模式):是首先根據相應的條件取出相應的排序字段和可以直接定位行 數據的行 ID,然后在 sort buffer 中進行排序,排序完后需要再次取回其它需要的字段;用trace工具 可以看到sort_mode信息里顯示< sort_key, rowid >
PS: MySQL 通過比較系統變量 max_length_for_sort_data(默認1024字節) 的大小和需要查詢的字段總大小來 判斷使用哪種排序模式。
如果 max_length_for_sort_data 比查詢字段的總長度大,那么使用 單路排序模式;
如果 max_length_for_sort_data 比查詢字段的總長度小,那么使用 雙路排序模式。
(2). 文件排序過程

我們先看單路排序的詳細過程:
1. 從索引 name 找到第一個滿足 name = ‘zhuge’ 條件的主鍵 id
2. 根據主鍵 id 取出整行,取出所有字段的值,存入 sort_buffer 中
3. 從索引 name 找到下一個滿足 name = ‘zhuge’ 條件的主鍵 id
4. 重復步驟 2、3 直到不滿足 name = ‘zhuge’
5. 對 sort_buffer 中的數據按照字段 position 進行排序
6. 返回結果給客戶端
我們再看下雙路排序的詳細過程:
1. 從索引 name 找到第一個滿足 name = ‘zhuge’ 的主鍵id
2. 根據主鍵 id 取出整行,把排序字段 position 和主鍵 id 這兩個字段放到 sort buffer 中
3. 從索引 name 取下一個滿足 name = ‘zhuge’ 記錄的主鍵 id
4. 重復 3、4 直到不滿足 name = ‘zhuge’
5. 對 sort_buffer 中的字段 position 和主鍵 id 按照字段 position 進行排序
6. 遍歷排序好的 id 和字段 position,按照 id 的值回到原表中取出 所有字段的值返回給客戶端
(3). 剖析
其實對比兩個排序模式,單路排序會把所有需要查詢的字段都放到 sort buffer 中,而雙路排序只會把主鍵 和需要排序的字段放到 sort buffer 中進行排序,然后再通過主鍵回到原表查詢需要的字段。
如果 MySQL 排序內存配置的比較小並且沒有條件繼續增加了,可以適當把 max_length_for_sort_data 配 置小點,讓優化器選擇使用雙路排序算法,可以在sort_buffer 中一次排序更多的行,只是需要再根據主鍵 回到原表取數據。
如果 MySQL 排序內存有條件可以配置比較大,可以適當增大 max_length_for_sort_data 的值,讓優化器 優先選擇全字段排序(單路排序),把需要的字段放到 sort_buffer 中,這樣排序后就會直接從內存里返回查 詢結果了。
所以,MySQL通過 max_length_for_sort_data 這個參數來控制排序,在不同場景使用不同的排序模式, 從而提升排序效率。
注意,如果全部使用sort_buffer內存排序一般情況下效率會高於磁盤文件排序,但不能因為這個就隨便增 大sort_buffer(默認1M),mysql很多參數設置都是做過優化的,不要輕易調整。
三. 分頁查詢優化
數據准備:
用到的還是employees表,下面增加10w條數據, name-age-position為聯合索引
drop procedure if exists insert_emp; delimiter ;; create procedure insert_emp() begin declare i int; set i=1; while(i<=100000)do insert into employees(name,age,position) values(CONCAT('zhuge',i),i,'dev'); set i=i+1; end while; end;; delimiter ; call insert_emp();
分析:
如下SQL語句,表示從表 employees 中取出從 10001 行開始的 10 行記錄。看似只查詢了 10 條記錄,實際這條 SQL 是先讀取 10010 條記錄,然后拋棄前 10000 條記錄,然后讀到后面 10 條想要的數據。因此要查詢一張大表比較靠后的數據,執行效率 是非常低的。
select * from employees limit 10000,10;
1. 根據自增且連續的主鍵排序的分頁查詢
(1). 下面兩句SQL語句查詢結果一致
--,沒添加單獨 order by,表示通過主鍵排序 select * from employees limit 9000,5; select * from employees where id > 9000 limit 5;

(2). 分析這兩句話的執行計划
-- 全表掃描 EXPLAIN select * from employees limit 9000,5; -- range級別 EXPLAIN select * from employees where id > 9000 limit 5;


剖析:一旦刪除數據,這種優化模式就不要用了,上面兩條sql語句查詢出來的結果不一致。
所以這種優化的前提是:① 主鍵自增且連續 ② 查詢結果根據主鍵排序。
2. 根據非主鍵字段排序的分頁查詢
(1).案例
A. 運行下面語句
select * from employees ORDER BY name limit 9000,5;

B. 查看執行計划
explain select * from employees ORDER BY name limit 9000,5;

剖析:這里我們發現,並沒有用到name索引,根據前面的講解,我們知道這是因為:由於是select *,掃描整個索引並查找到所有的行(可能要遍歷多個索引樹)的成本比掃描全表的成本更高,所以優化器放棄使用索引。
(2) 優化
思路:是讓排序時返回的字段盡可能少,所以可以讓排序和分頁操作先查出主鍵,然后根據主鍵查到對應的記錄,如下:
select * from employees e inner join (select id from employees order by name limit 9000,5) ed on e.id = ed.id;
剖析:查詢結果和上面結果一致,執行時間減少了1半多,而且用到了索引,還用到了索引排序。

四. Join關聯查詢優化
數據准備:
有t1和t2兩張結構完全相同的表,t1表插入1萬條數據,t2表插入100條數據。a字段上有索引。
--t1表 CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_a` (`a`) ) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8; --t2表 create table t2 like t1; --往t1表插入1萬行記錄,往t2表插入100行記錄 drop procedure if exists insert_t1; delimiter ;; create procedure insert_t1() begin declare i int; set i=1; while(i<=10000)do insert into t1(a,b) values(i,i); set i=i+1; end while; end;; delimiter ; call insert_t1(); drop procedure if exists insert_t2; delimiter ;; create procedure insert_t2() begin declare i int; set i=1; while(i<=100)do insert into t2(a,b) values(i,i); set i=i+1; end while; end;; delimiter ; call insert_t2();
補充:
mysql的表關聯常見有兩種算法 ,① Nested-Loop Join 算法(NLJ) ② Block Nested-Loop Join 算法 (BNL)
1. 嵌套循環連接 Nested-Loop Join(NLJ) 算法
補充:驅動表和被驅動表概念
1次一行一行循環地從第一張表(稱為驅動表,驅動表一般是關聯數據較少的那張表)中讀取行,在這行數據中取到關聯字段,根據關聯字段在另一張表(被驅動 表)里取出滿足條件的行,然后取出兩張表的結果合集。
案例:
EXPLAIN select * from t1 inner join t2 on t1.a= t2.a; EXPLAIN select * from t2 inner join t1 on t1.a= t2.a;
(1). 分析執行計划

A. 驅動表是 t2,被驅動表是 t1。先執行的就是驅動表(執行計划結果的id如果一樣則按從上到下順序執行sql);優化器一般會優先選擇小表做驅動表。所以使用 inner join 時,排在前面的表並不一定就是驅動表。
B. 使用了 NLJ算法。一般 join 語句中,如果執行計划 Extra 中未出現 Using join buffer 則表示使用的 join 算 法是 NLJ。
PS:上面兩條SQL語句,無論怎么t1和t2是否顛倒順序,都是t2表為驅動表,這個是mysql內部優化器決定的,選擇小表作為驅動表。
(2). SQL的執行流程
A. 從表 t2 中讀取一行數據;
B. 從第 1 步的數據中,取出關聯字段 a,到表 t1 中查找;
C. 取出表 t1 中滿足條件的行,跟 t2 中獲取到的結果合並,作為結果返回給客戶端;
D. 重復上面 3 步。
分析:
整個過程會讀取 t2 表的所有數據(磁盤IO掃描100行),然后遍歷這每行數據中字段 a 的值,根據 t2 表中 a 的值索引掃描 t1 表 中的對應行 (掃描100次 t1 表的索引,1次掃描可以認為最終只掃描 t1 表一行完整數據,也就是總共 t1 表也掃描了100 行),因此整個過程掃描了 200 行。
注:去t1表中索引掃描,假設B+Tree的高度為3,而根節點通常存在內存中,所有每找一條數據,兩次IO就可以找到,上面寫的t1表掃描100行,是忽略了B+Tree前兩層,只算葉子節點。
拋磚引玉:
如果被驅動表的關聯字段沒索引,使用NLJ算法性能會比較低(下面有詳細解釋),mysql會選擇Block Nested-Loop Join 算法。
2. 基於塊的嵌套循環連接 Block Nested-Loop Join(BNL)算法
原理補充:
把 驅動表 的數據讀入到 join_buffer(可以理解成緩存) 中,然后掃描 被驅動表 ,把 被驅動表 每一行取出來跟 join_buffer 中的數據做對比。
案例:
EXPLAIN select*from t1 inner join t2 on t1.b= t2.b;
(1). 分析執行計划

(2). SQL執行順序
A. 把 t2 的所有數據放入到 join_buffer 中
B. 把表 t1 中每一行取出來,依次跟 join_buffer 中的每行數據做對比
C. 返回滿足 join 條件的數據
分析:
整個過程對表 t1 和 t2 都做了一次全表掃描,因此磁盤IO掃描的總行數為10000(表 t1 的數據總量) + 100(表 t2 的數據總量) = 10100。並且 join_buffer 里的數據是無序的,因此對表 t1 中的每一行,都要做 100 次判斷,所以內存中的判斷次數是 100 * 10000= 100 萬次(內存判斷100w次速度還是很快的)。
3. 靈魂拷問
被驅動表的關聯字段沒索引為什么要選擇使用 BNL 算法而不使用 Nested-Loop Join 呢?
答:如果使用 Nested-Loop Join,那么掃描行數為 100 * 10000 = 100萬次,這個是磁盤IO掃描。很顯然,用BNL磁盤掃描次數少很多,相比於磁盤掃描,BNL的內存計算會快得多。 因此MySQL對於被驅動表的關聯字段沒索引的關聯查詢,一般都會使用 BNL 算法。如果有索引一般選擇 NLJ 算法,有 索引的情況下 NLJ 算法比 BNL算法性能更高。
4. 關聯sql的優化
(1). 關聯字段加索引,讓mysql做join操作時盡量選擇NLJ算法。
(2). 小表驅動大表,寫多表連接sql時如果明確知道哪張表是小表可以用straight_join寫法固定連接驅動方式,省去 mysql優化器自己判斷的時間 。
PS:
straight_join解釋:straight_join功能同join類似,但能讓左邊的表作為驅動表,來驅動右邊的表,能改表優化器對於聯表查詢的執行順序。
比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表制定mysql選着 t2 表作為驅動表。
注意:straight_join只適用於inner join,並不適用於left join,right join。(因為left join,right join已經代表指 定了表的執行順序,left join 左邊的為驅動表,right join 右邊的為驅動表) 盡可能讓優化器去判斷,因為大部分情況下mysql優化器是比人要聰明的。
使用straight_join一定要慎重,因 為部分情況下人為指定的執行順序並不一定會比優化引擎要靠譜。
5. in和exsits優化
原則:小表驅動大表,即小的數據集驅動大的數據集。
(1). in:當B表的數據集小於A表的數據集時,in優於exists
select * from A where id in (select id from B) #等價於: for(select id from B){ select * from A where A.id = B.id }
(2). exists:當A表的數據集小於B表的數據集時,exists優於in 將主查詢A的數據,放到子查詢B中做條件驗證,根據驗證結果(true或false)來決定主查詢的數據是否保留.
select * from A where exists (select 1 from B where B.id = A.id) #等價於: for(select * from A){ select * from B where B.id = A.id }
PS:
A、EXISTS (subquery)只返回TRUE或FALSE,因此子查詢中的SELECT * 也可以用SELECT 1替換,官方說法是實際執行時會 忽略SELECT清單,因此沒有區別
B、EXISTS子查詢的實際執行過程可能經過了優化而不是我們理解上的逐條對比
C、EXISTS子查詢往往也可以用JOIN來代替,何種最優需要具體問題具體分析
五. Count(*)查詢優化
1. 案例
EXPLAIN select count(1) from employees; EXPLAIN select count(id) from employees; EXPLAIN select count(name) from employees; EXPLAIN select count(*) from employees;
上面的四條SQL語句的執行計划相同:使用的都是輔助索引。

剖析:
四個sql的執行計划一樣,說明這四個sql執行效率應該差不多,區別在於 count里面如果是輔助索引,或者是一個不含索引的字段,則不會統計該字段對應的值為null的記錄。
靈魂拷問:
為什么mysql最終選擇輔助索引而不是主鍵聚集索引?
答:因為輔助索引相對主鍵索引存儲數據更少,檢索性能應該更高。
最終耗費的時間大約應該是:count(1)>count(name) ≈ count(*) > count(id) , 在mysql5.7及后續版本中,這4個count耗費的時間已經很相近了。
2. 如何優化
(1). MyIsam存儲引擎特有的
對於myisam存儲引擎的表做不帶where條件的count查詢性能是很高的,因為myisam存儲引擎的表的總行數會被 mysql存儲在磁盤上,查詢不需要計算。
注:對於innodb存儲引擎的表mysql不會存儲表的總記錄行數,查詢count需要實時計算。
(2). show table status
如果只需要知道表總行數的估計值可以用如下sql查詢,性能很高。
show table status like 'employees';

(3). 將總數維護到Redis中
插入或刪除表數據行的時候同時維護redis里的表總行數key的計數值(用incr或decr命令),但是這種方式可能不准,很難 保證表操作和redis操作的事務一致性。
(4). 增加計數表
插入或刪除表數據行的時候同時維護計數表,讓他們在同一個事務里操作。
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
