SQL優化主要就是在優化索引。
索引的弊端:
1.索引本身很大, 可以存放在內存/硬盤(通常爲硬盤)
2.索引不是所有情況均適用: a.少量數據 b.頻繁更新的字段 c.很少使用的字段
3.索引會降低增刪改的效率(增刪改查)
優勢:1.提高查詢效率(降低IO使用率)
2.降低CPU使用率 (...order by age desc,因爲 B樹索引 本身就是一個 好排序的結構,因此在排序時 可以直接使用)
索引分類:
主鍵索引 :不能重複。id 不能是null
唯一索引 :不能重複。id 可以是null
單值索引 :單列, age ;一個表可以多個單值索引,name。
複合索引 :多個列構成的索引 (相當於 二級目錄 ) (name,age) (a,b,c,d,...,n)
創建索引:
方式一:create 索引類型 索引名 on 表(字段)
單值索引:create index dept_index on tb(dept);
唯一索引:create unique index name_index on tb(name) ;
複合索引:create index dept_name_index on tb(dept,name);
方式二:alter table 表名 索引類型 索引名(字段)
單值索引:alter table tb add index dept_index(dept) ;
唯一索引:alter table tb add unique index name_index(name);
複合索引:alter table tb add index dept_name_index(dept,name);
注意:如果一個字段是primary key,則改字段默認就是主鍵索引
刪除索引:
drop index 索引名 on 表名 ;
drop index name_index on tb ;
查詢索引:
show index from 表名 ;
show index from 表名 \G;
SQL性能分析:
查詢執行計劃: explain +SQL語句
explain select * from tb ;可以模擬SQL優化器執行SQL語句,從而讓開發人員知道自己編寫的SQL狀況
優化方法,官網:https://dev.mysql.com/doc/refman/5.5/en/optimization.html
id : 標識符
select_type :查詢類型
table :輸出行表 partitions:匹配的分區
type :聯接類型
possible_keys :可能的索引選擇
key :實際使用的索引
key_len :所選鍵的長度
ref :與索引比較的列
rows :估計要檢查的行
filtered:按表條件過濾的行百分比
Extra :額外的信息
部分列詳細解釋
- id: id值相同,從上往下 順序執行,數據小的表 優先查詢; id值不同:id值越大越優先查詢;
- select_type:
PRIMARY:包含子查詢SQL中的 主查詢 (最外層)
SUBQUERY:包含子查詢SQL中的 子查詢 (非最外層)
simple:簡單查詢(不包含子查詢、union)
derived:衍生查詢(使用到了臨時表)
a.在from子查詢中只有一張表
explain select cr.cname from ( select * from course where tid in (1,2) ) cr b.在from子查詢中, 如果有table1 union table2 ,則table1 就是derived,table2就是union
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
union:上例
union result :從 union 臨時表檢索結果的 select
3.type:索引類型
依次從最優到最差分別爲:system>const>eq_ref>ref>range>index>all ,要對type進行優化的前提:有索引 。 其中:system,const只是理想情況;實際能達到 ref>range ; system(忽略): 只有一條數據的系統表 ;或衍生表只有一條數據的主查詢。
const:僅僅能查到一條數據的SQL ,用於Primary key 或unique索引 (類型與索引類型有關)
explain select tid from test01 where tid =1 ;
alter table test01 drop primary key ;
create index test01_index on test01(tid) ;
eq_ref:唯一性索引:對於每個索引鍵的查詢,返回匹配唯一行數據(有且只有1個,不能多 、不能0)
select ... from ..where name = ... .常見於唯一索引和主鍵索引。
alter table teacherCard add constraint pk_tcid primary key(tcid);
alter table teacher add constraint uk_tcid unique index(tcid) ;
explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;
以上SQL,用到的索引是 t.tcid,即teacher表中的tcid字段;
如果teacher表的數據個數和連接查詢的數據個數一致(都是3條數據),則有可能滿足eq_ref級別;否則無法滿足。
ref:非唯一性索引,對於每個索引鍵的查詢,返回匹配的所有行(0行或者多行)
準備數據:
insert into teacher values(4,'tz',4) ;
insert into teacherCard values(4,'tz222');
測試:
alter table teacher add index index_name (tname) ;
explain select * from teacher where tname = 'tz';
range:檢索指定範圍的行 ,where後面是一個範圍查詢(between ,> < >=, 特殊:in有時候會失效 ,從而轉爲無索引all)
alter table teacher add index tid_index (tid) ;
explain select t.* from teacher t where t.tid in (1,2) ;
explain select t.* from teacher t where t.tid <3 ;
index:查詢全部索引中數據
explain select tid from teacher ; --tid 是索引, 只需要掃描索引表,不需要所有表中的所有數據
all:查詢全部表中的數據
explain select cid from course ; --cid不是索引,需要全表掃描,即需要所有表中的所有數據
system/const: 結果只有一條數據
eq_ref:結果多條;但是每條數據是唯一的 ;
ref:結果多條;但是每條數據是是0或多條 ;
4.rows: 被索引優化查詢的數據個數 (實際通過索引而查詢到的數據個數)
explain select * from course c,teacher t where c.tid = t.tid
and t.tname = 'tz' ;
5.key_len:這一列顯示了mysql在索引裏使用的字節數,通過這個值可以算出具體使用了索引中的哪些列
key_len計算規則如下:
字符串
char(n):n字節長度
varchar(n):2字節存儲字符串長度,如果是utf-8,則長度 3n + 2
數值類型
tinyint:1字節
smallint:2字節
int:4字節
bigint:8字節
時間類型
date:3字節
timestamp:4字節
datetime:8字節
如果字段允許爲 NULL,需要1字節記錄是否爲 NULL
索引最大長度是768字節,當字符串過長時,mysql會做一個類似左前綴索引的處理,將前半部分的字符提取出來做索引。
6. Extra:
(i).using filesort : 性能消耗大;需要“額外”的一次排序(查詢) 。常見於 order by 語句中。
explain select * from test02 where a1 ='' order by a2 ; --using filesort
小結:對於單索引, 如果排序和查找是同一個字段,則不會出現using filesort;如果排序和查找不是同一個字段,則會出現 using filesort;
避免: where哪些字段,就order by哪些字段
複合索引:不能跨列(最佳左前綴)
drop index idx_a1 on test02;
drop index idx_a2 on test02;
drop index idx_a3 on test02;
alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ;
explain select *from test02 where a1='' order by a3 ; --using filesort
explain select *from test02 where a2='' order by a3 ; --using filesort
explain select *from test02 where a1='' order by a2 ;
explain select *from test02 where a2='' order by a1 ; --using filesort
小結:避免: where和order by 按照複合索引的順序使用,不要跨列或無序使用。
(ii). using temporary:性能損耗大 ,用到了臨時表。一般出現在group by 語句中。
explain select a1 from test02 where a1 in ('1','2','3') group by a1 ;
explain select a1 from test02 where a1 in ('1','2','3') group by a2 ; --using temporary
避免:查詢那些列,就根據那些列 group by .
(iii). using index :性能提升; 索引覆蓋(覆蓋索引)。原因:不讀取原文件,只從索引文件中獲取數據 (不需要回表查詢)
只要使用到的列 全部都在索引中,就是索引覆蓋using index
例如:test02表中有一個複合索引(a1,a2,a3)
explain select a1,a2 from test02 where a1='' or a2= '' ; --using index
drop index idx_a1_a2_a3 on test02;
alter table test02 add index idx_a1_a2(a1,a2) ;
explain select a1,a3 from test02 where a1='' or a3= '' ;
如果用到了索引覆蓋(using index時),會對 possible_keys和key造成影響:
a.如果沒有where,則索引只出現在key中;
b.如果有where,則索引 出現在key和possible_keys中。
explain select a1,a2 from test02 where a1='' or a2= '' ;
explain select a1,a2 from test02 ;
(iii).using where (需要回表查詢)
假設age是索引列
但查詢語句select age,name from ...where age =...,此語句中必須回原表查Name,因此會顯示using where.
explain select a1,a3 from test02 where a3 = '' ; --a3需要回原表查詢
(iv). impossible where :where子句永遠爲false
explain select * from test02 where a1='x' and a1='y' ;
索引數據結構:
- 二叉樹
- 紅黑樹
- Hash表
- B-Tree
根據這個鏈接可以模仿這些如何進行增加節點,轉換的:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
爲什麼索引會採用B+Tree數據結果呢?以下分析:
二叉樹,在某種情況下回會退化成鏈表結構:
紅黑樹會進行自適應,進行平衡,但是數據量大的時候也會出現問題,深度過高,會增加I/O次數:
hash表:hash查詢速度也快,但是進行範圍查找就不行。
B-Tree:橫向擴展,高度低。(橫向查找在同一頁中,不需要更多的磁盤I/O,內存中的遍歷速度可以忽略不計)
B+Tree:有指針,能很好的支撐範圍查找。(葉子節點中其實是雙向指針)
索引最佳實踐
使用的表
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='員工記錄表';
INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
最佳實踐
1. 全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
2.最佳左前綴法則
如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
3.不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉向全表掃描
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';
4.存儲引擎不能使用索引中範圍條件右邊的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';
5.儘量使用覆蓋索引(只訪問索引的查詢(索引列包含查詢列)),減少select *語句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
6.mysql在使用不等於(!=或者<>)的時候無法使用索引會導致全表掃描
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'
7.is null,is not null 也無法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
8.like以通配符開頭('$abc...')mysql索引失效會變成全表掃描操作
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
問題:解決like'%字符串%'索引不被使用的方法?
a)使用覆蓋索引,查詢字段必須是建立覆蓋索引字段
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';
b)當覆蓋索引指向的字段是varchar(380)及380以上的字段時,覆蓋索引會失效!
9.字符串不加單引號索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
10.少用or,用它連接時很多情況下索引會失效
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';
like KK%相當於=常量,%KK和%KK% 相當於範圍