1、初識索引#
1.1 爲何要有索引?#
通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,所以對查詢語句的優化顯然是重中之重。提及加速查詢,就不得不提到索引了。html
1.2 什麼是索引?#
索引在MySQL中也叫是一種「鍵」,是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要。mysql
索引優化應該是對查詢性能優化最有效的手段了。索引可以輕易將查詢性能提升好幾個數量級。ios
索引至關於字典的音序表,若是要查某個字,若是不使用音序表,則須要從幾百頁中逐頁去查。算法
1.3 你是否對索引存在誤解?#
索引是應用程序設計和開發的一個重要方面。若索引太多,應用程序的性能可能會受到影響。而索引太少,對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能相當重要。一些開發人員老是在過後纔想起添加索引----我一直認爲,這源於一種錯誤的開發模式。若是知道數據的使用,從一開始就應該在須要處添加索引。開發人員每每對數據庫的使用停留在應用的層面,好比編寫SQL語句、存儲過程之類,他們甚至可能不知道索引的存在,或認爲過後讓相關DBA加上便可。DBA每每不夠了解業務的數據流,而添加索引須要經過監控大量的SQL語句進而從中找到問題,這個步驟所需的時間確定是遠大於初始添加索引所需的時間,而且可能會遺漏一部分的索引。固然索引也並非越多越好,我曾經遇到過這樣一個問題:某臺MySQL服務器iostat顯示磁盤使用率一直處於100%,通過分析後發現是因爲開發人員添加了太多的索引,在刪除一些沒必要要的索引以後,磁盤使用率立刻降低爲20%。可見索引的添加也是很是有技術含量的。sql
2、索引的原理#
2.1 索引原理#
索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定位到章,而後定位到該章下的一個小節,而後找到頁數。類似的例子還有:查字典,查火車車次,飛機航班等數據庫
本質都是:經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。vim
數據庫也是同樣,但顯然要複雜的多,由於不只面臨着等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎麼樣的方式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段查詢呢?最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二段,201到300分紅第三段......這樣查第250條數據,只要找第三段就能夠了,一會兒去除了90%的無效數據。但若是是1千萬的記錄呢,分紅幾段比較好?稍有算法基礎的同窗會想到搜索樹,其平均複雜度是lgN,具備不錯的查詢性能。但這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮的。而數據庫實現比較複雜,一方面數據是保存在磁盤上的,另一方面爲了提升性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。性能優化
2.2 磁盤IO與預讀#
前面提到了訪問磁盤,那麼這裏先簡單介紹一下磁盤IO和預讀,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行約450萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供你們參考:服務器
考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。數據結構
3、索引的數據結構#
MySQL索引的數據結構-B+樹介紹:http://www.javashuo.com/article/p-rzligenp-cy.html
4、MySQL索引管理#
4.1 功能#
-
索引的功能就是加速查找
-
mysql中的primary key,unique,聯合惟一也都是索引,這些索引除了加速查找之外,還有約束的功能
4.2 MySQL經常使用的索引#
-
普通索引INDEX:加速查找
-
惟一索引:
-
主鍵索引PRIMARY KEY:加速查找+約束(不爲空、不能重複)
-
惟一索引UNIQUE:加速查找+約束(不能重複)
-
-
聯合索引:
-
PRIMARY KEY(id,name):聯合主鍵索引
-
UNIQUE(id,name):聯合惟一索引
-
INDEX(id,name):聯合普通索引
-
4.3 各個索引應用場景#
Copy舉個例子來講,好比你在爲某商場作一個會員卡的系統。 這個系統有一個會員表 有下列字段: 會員編號 INT 會員姓名 VARCHAR(10) 會員身份證號碼 VARCHAR(18) 會員電話 VARCHAR(10) 會員住址 VARCHAR(50) 會員備註信息 TEXT 那麼這個 會員編號,做爲主鍵,使用 PRIMARY 會員姓名 若是要建索引的話,那麼就是普通的 INDEX 會員身份證號碼 若是要建索引的話,那麼能夠選擇 UNIQUE (惟一的,不容許重複)# 除此以外還有全文索引,即FULLTEXT會員備註信息 , 若是須要建索引的話,能夠選擇全文搜索。 用於搜索很長一篇文章的時候,效果最好。 用在比較短的文本,若是就一兩行字的,普通的 INDEX 也能夠。 但其實對於全文搜索,咱們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來作全文搜索。# 其餘的如空間索引SPATIAL,瞭解便可,幾乎不用各個索引的應用場景 各個索引的應用場景
4.4 索引的兩大類型hash與btree#
咱們能夠在建立上述索引的時候,爲其指定索引類型,分兩類:
-
hash類型的索引:查詢單條快,範圍查詢慢
-
btree類型的索引:b+樹,層數越多,數據量指數級增加(咱們就用它,由於innodb默認支持它)
不一樣的存儲引擎支持的索引類型也不同:
-
InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
-
MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
-
Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
-
NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
-
Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;
4.5 建立/刪除索引的語法#
Copy# 方法一:建立表時 CREATE TABLE 表名 ( 字段名1 數據類型 [完整性約束條件…], 字段名2 數據類型 [完整性約束條件…], [UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY [索引名] (字段名[(長度)] [ASC |DESC]) ); # 方法二:CREATE在已存在的表上建立索引 CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名 ON 表名 (字段名[(長度)] [ASC |DESC]) ; # 方法三:ALTER TABLE在已存在的表上建立索引 ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名 (字段名[(長度)] [ASC |DESC]) ; # 刪除索引:DROP INDEX 索引名 ON 表名字;
4.6 示例#
Copy# 方式一create table t1( id int, name char, age int, sex enum('male','female'), unique key uni_id(id), index ix_name(name) # index沒有key);create table t1( id int, name char, age int, sex enum('male','female'), unique key uni_id(id), index(name) # index沒有key); # 方式二create index ix_age on t1(age); # 方式三alter table t1 add index ix_sex(sex);alter table t1 add index(sex); # 查看 mysql> show create table t1; | t1 | CREATE TABLE `t1` ( `id` int(11) DEFAULT NULL, `name` char(1) DEFAULT NULL, `age` int(11) DEFAULT NULL, `sex` enum('male','female') DEFAULT NULL, UNIQUE KEY `uni_id` (`id`), KEY `ix_name` (`name`), KEY `ix_age` (`age`), KEY `ix_sex` (`sex`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1
5、測試索引#
5.1 數據準備#
Copy# 1. 準備表create table s1(id int,name varchar(20), gender char(6), email varchar(50) ); # 2. 建立存儲過程,實現批量插入記錄 delimiter $$ # 聲明存儲過程的結束符號爲$$create procedure auto_insert1()BEGIN declare i int default 1; while(i<3000000)do insert into s1 values(i,'eva','female',concat('eva',i,'@oldboy')); set i=i+1; end while;END$$ # $$結束 delimiter ; # 從新聲明分號爲結束符號 # 3. 查看存儲過程show create procedure auto_insert1\G # 4. 調用存儲過程call auto_insert1();
一、在沒有索引的前提下測試查詢速度
無索引:mysql根本就不知道究竟是否存在id等於333333333的記錄,只能把數據表從頭至尾掃描一遍,此時有多少個磁盤塊就須要進行多少IO操做,因此查詢速度很慢
Copymysql> select * from s1 where id=333333333; Empty set (0.33 sec)
二、在表中已經存在大量數據的前提下,爲某個字段段創建索引,創建速度會很慢
三、在索引創建完畢後,以該字段爲查詢條件時,查詢速度提高明顯
注意:
-
mysql先去索引表裏根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大下降,於是速度明顯提高
-
咱們能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多了
-
須要注意,以下圖
5.2 小結#
-
必定是爲搜索條件的字段建立索引,好比
select * from s1 where id = 333;
就須要爲id加上索引 -
在表中已經有大量數據的狀況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快,好比
create index idx on s1(id);
會掃描表中全部的數據,而後以id爲數據項,建立索引結構,存放於硬盤的表中。建完之後,再查詢就會很快了。 -
須要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI
MySAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在innodb中,表數據文件自己就是按照B+Tree(BTree即Balance True)組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以innodb表數據文件自己就是主索引。
由於inndob的數據文件要按照主鍵彙集,因此innodb要求表必需要有主鍵(Myisam能夠沒有),若是沒有顯式定義,則mysql系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則mysql會自動爲innodb表生成一個隱含字段做爲主鍵,這字段的長度爲6個字節,類型爲長整型.
6、正確使用索引#
6.1 索引未命中#
並非說咱們建立了索引就必定會加快查詢速度,若想利用索引達到預想的提升查詢速度的效果,咱們在添加索引時,必須遵循如下問題:
一、範圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、大於號、小於號
不等於!=
between ...and...
like
二、儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄。
先把表中的索引都刪除,讓咱們專心研究區分度的問題:
Copymysql> desc s1; +--------+-------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+| id | int(11) | YES | MUL | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(5) | YES | | NULL | | | email | varchar(50) | YES | MUL | NULL | | +--------+-------------+------+-----+---------+-------+rows in set (0.00 sec) mysql> drop index a on s1; Query OK, 0 rows affected (0.20 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> drop index d on s1; Query OK, 0 rows affected (0.18 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> desc s1; +--------+-------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+| id | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(5) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | +--------+-------------+------+-----+---------+-------+rows in set (0.00 sec)
分析緣由:
咱們編寫存儲過程爲表s1批量添加記錄,name字段的值均爲egon,也就是說name這個字段的區分度很低(gender字段也是同樣的,咱們稍後再搭理它)
回憶b+樹的結構,查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,須要保證:在某一層內數據項均是按照從左到右,從小到大的順序依次排開,即左1<左2<左3<...
而對於區分度低的字段,沒法找到大小關係,由於值都是相等的,毫無疑問,還想要用b+樹存放這些等值的數據,只能增長樹的高度,字段的區分度越低,則樹的高度越高。極端的狀況,索引字段的值都同樣,那麼b+樹幾乎成了一根棍。本例中就是這種極端的狀況,name字段全部的值均爲'nick'
如今咱們得出一個結論:爲區分度低的字段創建索引,索引樹的高度會很高,然而這具體會帶來什麼影響呢???
-
若是條件是name='xxxx',那麼確定是能夠第一時間判斷出'xxxx'是不在索引樹中的(由於樹中全部的值均爲'nick’),因此查詢速度很快
-
若是條件正好是name='nick',查詢時,咱們永遠沒法從樹的某個位置獲得一個明確的範圍,只能往下找,往下找,往下找。。。這與全表掃描的IO次數沒有多大區別,因此速度很慢
三、索引列不能在條件中參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’)
四、and/or
-
and與or的邏輯
-
條件1 and 條件2:全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立
-
條件1 or 條件2:只要有一個條件成立則最終結果就成立
-
-
and的工做原理
-
條件:
a = 10 and b = 'xxx' and c > 3 and d =4
-
索引:製做聯合索引(d,a,b,c)
-
工做原理:對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d—>a->b->c的順序
-
-
or的工做原理
-
條件:a = 10 or b = 'xxx' or c > 3 or d =4
-
索引:製做聯合索引(d,a,b,c)
-
工做原理:對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d
-
在左邊條件成立可是索引字段的區分度低的狀況下(name與gender均屬於這種狀況),會依次往右找到一個區分度高的索引字段,加速查詢。
通過分析,在條件爲name='nick' and gender='male' and id>333 and email='xxx'的狀況下,咱們徹底不必爲前三個條件的字段加索引,由於只能用上email字段的索引,前三個字段的索引反而會下降咱們的查詢效率
五、最左前綴匹配原則,很是重要的原則,對於組合索引mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配(指的是範圍大了,有索引速度也慢),好比a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。
六、其餘狀況
Copy- 使用函數 select * from tb1 where reverse(email) = 'nick'; - 類型不一致 若是列是字符串類型,傳入條件是必須用引號引發來,否則... select * from tb1 where email = 999; #排序條件爲索引,則select字段必須也是索引字段,不然沒法命中 - order by select name from s1 order by email desc; 當根據索引排序時候,select查詢的字段若是不是索引,則速度仍然很慢 select email from s1 order by email desc; 特別的:若是對主鍵排序,則仍是速度很快: select * from tb1 order by nid desc; - 組合索引最左前綴 若是組合索引爲:(name,email) name and email -- 命中索引 name -- 命中索引 email -- 未命中索引- count(1)或count(列)代替count(*)在mysql中沒有差異了 - create index xxxx on tb(title(19)) #text類型,必須制定長度
6.2 其餘注意事項#
-
避免使用select *
-
使用count(*)
-
建立表時儘可能使用 char 代替 varchar
-
表的字段順序固定長度的字段優先
-
組合索引代替多個單列索引(因爲mysql中每次只能使用一個索引,因此常用多個條件查詢時更適合使用組合索引)
-
儘可能使用短索引
-
使用鏈接(JOIN)來代替子查詢(Sub-Queries)
-
連表時注意條件類型需一致
-
索引散列值(重複少)不適合建索引,例:性別不適合
7、聯合索引和覆蓋索引#
7.1 聯合索引#
聯合索引是指對錶上的多個列合起來作一個索引。聯合索引的建立方法與單個索引的建立方法同樣,不一樣之處僅在於有多個索引列,以下:
Copymysql> create table t( -> a int, -> b int, -> primary key(a), -> key idx_a_b(a,b) -> ); Query OK, 0 rows affected (0.11 sec)
那麼什麼時候須要使用聯合索引呢?在討論這個問題以前,先來看一下聯合索引內部的結果。從本質上來講,聯合索引就是一棵B+樹,不一樣的是聯合索引的鍵值得數量不是1,而是>=2。接着來討論兩個整型列組成的聯合索引,假定兩個鍵值得名稱分別爲a、b如圖:
能夠看到這與咱們以前看到的單個鍵的B+樹並無什麼不一樣,鍵值都是排序的,經過葉子結點能夠邏輯上順序地讀出全部數據,就上面的例子來講,即(1,1),(1,2),(2,1),(2,4),(3,1),(3,2),數據按(a,b)的順序進行了存放。
所以,對於查詢select * from table where a=xxx and b=xxx, 顯然是可使用(a,b) 這個聯合索引的,對於單個列a的查詢select * from table where a=xxx,也是可使用(a,b)這個索引的。
但對於b列的查詢select * from table where b=xxx,則不可使用(a,b) 索引,其實你不難發現緣由,葉子節點上b的值爲一、二、一、四、一、2顯然不是排序的,所以對於b列的查詢使用不到(a,b) 索引
聯合索引的第二個好處是在第一個鍵相同的狀況下,已經對第二個鍵進行了排序處理,例如在不少狀況下應用程序都須要查詢某個用戶的購物狀況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引能夠幫咱們避免多一次的排序操做,由於索引自己在葉子節點已經排序了,以下
Copy# ===========準備表==============create table buy_log( userid int unsigned not null, buy_date date);insert into buy_log values(1,'2009-01-01'), (2,'2009-01-01'), (3,'2009-01-01'), (1,'2009-02-01'), (3,'2009-02-01'), (1,'2009-03-01'), (1,'2009-04-01');alter table buy_log add key(userid);alter table buy_log add key(userid,buy_date); # ===========驗證============== mysql> show create table buy_log; | buy_log | CREATE TABLE `buy_log` ( `userid` int(10) unsigned NOT NULL, `buy_date` date DEFAULT NULL, KEY `userid` (`userid`), KEY `userid_2` (`userid`,`buy_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 | # 能夠看到possible_keys在這裏有兩個索引能夠用,分別是單個索引userid與聯合索引userid_2,可是優化器最終選擇了使用的key是userid由於該索引的葉子節點包含單個鍵值,因此理論上一個頁能存放的記錄應該更多 mysql> explain select * from buy_log where userid=2; +----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+| 1 | SIMPLE | buy_log | ref | userid,userid_2 | userid | 4 | const | 1 | | +----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+row in set (0.00 sec) # 接着假定要取出userid爲1的最近3次的購買記錄,用的就是聯合索引userid_2了,由於在這個索引中,在userid=1的狀況下,buy_date都已經排序好了 mysql> explain select * from buy_log where userid=1 order by buy_date desc limit 3; +--+-----------+-------+----+---------------+--------+-------+-----+----+------------------------+|id|select_type|table |type|possible_keys | key |key_len|ref |rows| Extra | +--+-----------+-------+----+---------------+--------+-------+-----+----+------------------------+| 1|SIMPLE |buy_log|ref |userid,userid_2|userid_2| 4 |const| 4 |Using where; Using index| +--+-----------+-------+----+---------------+--------+-------+-----+----+------------------------+row in set (0.00 sec) # ps:若是extra的排序顯示是Using filesort,則意味着在查出數據後須要二次排序(以下查詢語句,沒有先用where userid=3先定位範圍,因而即使命中索引也沒用,須要二次排序) mysql> explain select * from buy_log order by buy_date desc limit 3; +--+-----------+-------+-----+-------------+--------+-------+----+----+---------------------------+|id|select_type| table |type |possible_keys|key |key_len|ref |rows|Extra | +--+-----------+-------+-----+-------------+--------+-------+----+----+---------------------------+| 1|SIMPLE |buy_log|index| NULL |userid_2| 8 |NULL| 7 |Using index; Using filesort| +--+-----------+-------+-----+-------------+--------+-------+----+----+---------------------------+# 對於聯合索引(a,b),下述語句能夠直接使用該索引,無需二次排序select ... from table where a=xxx order by b; # 而後對於聯合索引(a,b,c)來首,下列語句一樣能夠直接經過索引獲得結果select ... from table where a=xxx order by b;select ... from table where a=xxx and b=xxx order by c; # 可是對於聯合索引(a,b,c),下列語句不能經過索引直接獲得結果,還須要本身執行一次filesort操做,由於索引(a,c)並未排序select ... from table where a=xxx order by c;
7.2 覆蓋索引#
InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就能夠獲得查詢記錄,而不須要查詢彙集索引中的記錄。
使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的全部信息,故其大小要遠小於彙集索引,所以能夠減小大量的IO操做。
注意:覆蓋索引技術最先是在InnoDB Plugin中完成並實現,這意味着對於InnoDB版本小於1.0的,或者MySQL數據庫版本爲5.0如下的,InnoDB存儲引擎不支持覆蓋索引特性。
對於InnoDB存儲引擎的輔助索引而言,因爲其包含了主鍵信息,所以其葉子節點存放的數據爲(primary key1,priamey key2,...,key1,key2,...)。例如:
Copyselect age from s1 where id=123 and name = 'nick'; #id字段有索引,可是name字段沒有索引,該sql命中了索引,但未覆蓋,須要去彙集索引中再查找詳細信息。 最牛逼的狀況是,索引字段覆蓋了全部,那全程經過索引來加速查詢以及獲取結果就ok了 mysql> desc s1; +--------+-------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra | +--------+-------------+------+-----+---------+-------+| id | int(11) | NO | | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(6) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | +--------+-------------+------+-----+---------+-------+rows in set (0.21 sec) mysql> explain select name from s1 where id=1000; #沒有任何索引 +--+-----------+-----+----------+----+-------------+----+-------+----+-------+--------+-----------+|id|select_type|table|partitions|type|possible_keys|key |key_len|ref | rows |filtered| Extra | +--+-----------+-----+----------+----+-------------+----+-------+----+-------+--------+-----------+| 1| SIMPLE | s1 | NULL |ALL | NULL |NULL| NULL |NULL|2688336| 10.00 |Using where| +--+-----------+-----+----------+----+-------------+----+-------+----+-------+--------+-----------+row in set, 1 warning (0.00 sec) mysql> create index idx_id on s1(id); #建立索引 Query OK, 0 rows affected (4.16 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select name from s1 where id=1000; #命中輔助索引,可是未覆蓋索引,還須要從彙集索引中查找name +--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----+|id|select_type|table|partitions|type|possible_keys| key|key_len| ref |rows|filtered|Extra| +--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----+| 1| SIMPLE | s1 | NULL | ref| idx_id |idx_id| 4 |const| 1 | 100.00 | NULL| +--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----+row in set, 1 warning (0.08 sec) mysql> explain select id from s1 where id=1000; #在輔助索引中就找到了所有信息,Using index表明覆蓋索引 +--+-----------+-----+----------+----+-------------+------+-------+-------+------+----------+-----+|id|select_type|table|partitions|type|possible_keys| key |key_len| ref |rows|filtered| Extra | +--+-----------+-----+----------+----+--------------------+-------+-------+------+----------+-----+| 1| SIMPLE | s1 | NULL | ref| idx_id |idx_id| 4 |const| 1 | 100.00 |Using index| +--+-----------+-----+----------+----+-------------+------+-------+-----+----+--------+-----------+row in set, 1 warning (0.03 sec)
覆蓋索引的另一個好處是對某些統計問題而言的。基於上一小結建立的表buy_log,查詢計劃以下:
Copymysql> explain select count(*) from buy_log; +--+-----------+-------+-----+-------------+------+-------+----+----+-----------+|id|select_type|table | type|possible_keys|key |key_len|ref |rows|Extra | +--+-----------+-------+-----+-------------+------+-------+----+----+-----------+| 1| SIMPLE |buy_log|index| NULL |userid| 4 |NULL| 7 |Using index| +--+-----------+-------+-----+-------------+------+-------+----+----+-----------+row in set (0.00 sec)
innodb存儲引擎並不會選擇經過查詢彙集索引來進行統計。因爲buy_log表有輔助索引,而輔助索引遠小於彙集索引,選擇輔助索引能夠減小IO操做,故優化器的選擇如上key爲userid輔助索引
對於(a,b)形式的聯合索引,通常是不能夠選擇b中所謂的查詢條件。但若是是統計操做,而且是覆蓋索引,則優化器仍是會選擇使用該索引,以下:
Copy# 聯合索引userid_2(userid,buy_date),通常狀況,咱們按照buy_date是沒法使用該索引的,但特殊狀況下:查詢語句是統計操做,且是覆蓋索引,則按照buy_date當作查詢條件時,也可使用該聯合索引 mysql> explain select count(*) from buy_log where buy_date >= '2011-01-01' and buy_date < '2011-02-01'; +--+-----------+-------+-----+-------------+--------+-------+----+----+------------------------+|id|select_type| table |type |possible_keys| key |key_len|ref |rows|Extra | +--+-----------+-------+-----+-------------+--------+-------+----+----+------------------------+| 1| SIMPLE |buy_log|index| NULL |userid_2| 8 |NULL| 7 |Using where; Using index| +--+-----------+-------+-----+-------------+--------+-------+----+----+------------------------+row in set (0.00 sec)
7.3 合併索引#
Copymysql> explain select count(email) from index_t where id = 1000000 or email='eva100000@oldboy'; +--+-----------+------+--------------+--------------------------------+---------------+--------+-----+----+-----------------------------------------+| id | select_type| table | type | possible_keys | key | key_len | ref |rows | Extra | +--+-----------+------+--------------+--------------------------------+---------------+--------+-----+----+-----------------------------------------+| 1 | SIMPLE | index_t| index_merge | PRIMARY,email,ind_id,ind_email | PRIMARY,email | 4,51 |NULL| 2 |Using union(PRIMARY,email); Using where | +--+-----------+------+--------------+--------------------------------+---------------+--------+-----+----+-----------------------------------------+row in set (0.01 sec)
8、查詢優化神器-explain#
MySQL性能分析之Explain:http://www.javashuo.com/article/p-pzshfquv-dk.html
9、慢查詢優化的基本步驟#
-
先運行看看是否真的很慢,注意設置SQL_NO_CACHE
-
where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每一個字段分別查詢,看哪一個字段的區分度最高
-
explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)
-
order by limit 形式的sql語句讓排序的表優先查
-
瞭解業務方使用場景
-
加索引時參照建索引的幾大原則
-
觀察結果,不符合預期繼續從0分析
10、慢日誌管理#
Copy慢日誌 - 執行時間 > 10 - 未命中索引 - 日誌文件路徑 配置: - 內存 show variables like '%query%'; show variables like '%queries%'; set global 變量名 = 值 - 配置文件 mysqld --defaults-file='E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\my-default.ini' my.conf內容: slow_query_log = ON slow_query_log_file = D:/.... 注意:修改配置文件以後,須要重啓服務
CopyMySQL日誌管理 ======================================================== 錯誤日誌: 記錄 MySQL 服務器啓動、關閉及運行錯誤等信息 二進制日誌: 又稱binlog日誌,以二進制文件的方式記錄數據庫中除 SELECT 之外的操做 查詢日誌: 記錄查詢的信息 慢查詢日誌: 記錄執行時間超過指定時間的操做 中繼日誌: 備庫將主庫的二進制日誌複製到本身的中繼日誌中,從而在本地進行重放 通用日誌: 審計哪一個帳號、在哪一個時段、作了哪些事件 事務日誌或稱redo日誌: 記錄Innodb事務相關的如事務執行時間、檢查點等 ======================================================== 1、bin-log1. 啓用 # vim /etc/my.cnf [mysqld]log-bin[=dir\[filename]] # service mysqld restart2. 暫停 //僅當前會話SET SQL_LOG_BIN=0;SET SQL_LOG_BIN=1; 3. 查看 查看所有: # mysqlbinlog mysql.000002 按時間: # mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56"# mysqlbinlog mysql.000002 --stop-datetime="2012-12-05 11:02:54"# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56" --stop-datetime="2012-12-05 11:02:54" 按字節數: # mysqlbinlog mysql.000002 --start-position=260# mysqlbinlog mysql.000002 --stop-position=260# mysqlbinlog mysql.000002 --start-position=260 --stop-position=9304. 截斷bin-log(產生新的bin-log文件) a. 重啓mysql服務器 b. # mysql -uroot -p123 -e 'flush logs' 5. 刪除bin-log文件 # mysql -uroot -p123 -e 'reset master' 2、查詢日誌 啓用通用查詢日誌 # vim /etc/my.cnf [mysqld] log[=dir\[filename]] # service mysqld restart 3、慢查詢日誌 啓用慢查詢日誌 # vim /etc/my.cnf [mysqld] log-slow-queries[=dir\[filename]] long_query_time=n # service mysqld restart MySQL 5.6: slow-query-log=1 slow-query-log-file=slow.log long_query_time=3 單位爲秒 查看慢查詢日誌 測試:BENCHMARK(count,expr) SELECT BENCHMARK(50000000,2*3);