做爲免費又高效的數據庫,mysql基本是首選。良好的安全鏈接,自帶查詢解析、sql語句優化,使用讀寫鎖(細化到行)、事物隔離和多版本併發控制提升併發,完備的事務日誌記錄,強大的存儲引擎提供高效查詢(表記錄可達百萬級),若是是InnoDB,還可在崩潰後進行完整的恢復,優勢很是多。即便有這麼多優勢,仍依賴人去作點優化,看書後寫個總結鞏固下,有錯請指正。mysql
完整的mysql優化須要很深的功底,大公司甚至有專門寫mysql內核的,sql優化攻城獅,mysql服務器的優化,各類參數常量設定,查詢語句優化,主從複製,軟硬件升級,容災備份,sql編程,須要的不是一星半點的知識與時間來掌握,做爲一名像俺這樣的菜鳥開發,強吃這麼多消化不了也沒意義:沒地兒用啊,何況還有運維和dba,還不如把手頭的業務寫好,也就是寫好點的sql,並且不少sql語句優化跟索引仍是有很大關係的。sql
首先,mysql的查詢流程大體是:mysql客戶端經過協議與mysql服務器創建鏈接,發送查詢語句,先檢查查詢緩存,若是命中,直接返回結果,不然進行語句解析,有一系列預處理,好比檢查語句是否寫正確了,而後是查詢優化(好比是否使用索引掃描,若是是一個不可能的條件,則提早終止),生成查詢計劃,而後查詢引擎啓動,開始執行查詢,從底層存儲引擎調用API獲取數據,最後返回給客戶端。怎麼存數據、怎麼取數據,都與存儲引擎有關。而後,mysql默認使用的BTREE索引,而且一個大方向是,不管怎麼折騰sql,至少在目前來講,mysql最多隻用到表中的一個索引。數據庫
mysql經過存儲引擎取數據,天然跟存儲引擎有很大關係,不一樣的存儲引擎索引也不同,如MyISAM的全文索引,即使索引叫一個名字內部組織方式也不盡相同,最經常使用的固然就是InnoDB了(還有徹底兼容mysql的MariaDB,它的默引擎是XtraDB,跟InnoDB很像),這裏寫的是InnoDB引擎。而索引的實現也跟存儲引擎,按照實現方式分,InnoDB的索引目前只有兩種:BTREE索引和HASH索引。一般咱們說的索引不出意外指的就是B樹索引,InnoDB的BTREE索引,實際是用B+樹實現的,由於在查看錶索引時,mysql一概打印BTREE,因此簡稱爲B樹索引。至於B樹與B+樹的區別,原諒的俺數據結構沒好好學,也是須要補的地方。編程
使用了BTREE索引,意味着全部的索引是按順序排列存儲的(升序),mysql就是這麼幹的,mysl中的BTREE索引抽象結構以下圖(參考高性能mysql)。緩存
結構中,每一層節點均從左往右從小到大排列,key1 < key2 < ... < keyN,對於小於key1或在[key1,key2)或其餘的值的節點,在進入葉子節點查找,是一個範圍分佈,同時,同一層節點之間可直接訪問,由於他們之間有指針指向聯繫(MyISAM的BTREE索引沒有)。每次搜索是一個區間搜索,有的話就找到了,沒有的話就是空。索引能加快訪問速度,由於有了它無需全表掃描數據(不老是這樣),根據查找的值,跟節點中的值比較,一般使用二分查找,對於排好序的數值來講,平均速度幾乎是最快的。安全
val指向了哪裏,對於InnoDB,它指向的就是表數據,由於InnoDB的表數據自己就是索引文件,這是與MyISAM索引的顯著區別,MyISAM的索引指向的是表數據的地址(val指向的是相似於0x7DFF..之類)。好比對於InnoDB一個主鍵索引來講,多是這樣服務器
InnoDB的索引節點val值直接指向表數據,即它的葉子節點就是表數據,它們連在一塊兒,表記錄行沒有再單獨放在其餘地方,葉子節點(數據)之間可訪問。mysql優化
前面在BTREE的抽象結構中,索引值的節點是放在頁中的,這裏有兩個需注意的問題:數據結構
1. 葉子頁、頁中的值(上上圖),即所謂的頁是啥,俺加了個節點註釋,即這裏的頁最小可近似當作是單個節點。咱們知道計算機的存儲空間是一塊一塊的,一般一塊用完了再用另外一塊,若是上一塊只剩餘5kb,但這裏恰好要申請8kb的空間,就得在一個新的塊上申請這個空間,而後之後的申請又接在這個8kb後面,只要這個塊的空間足夠,那麼上一塊的5kb一般就成了所謂的「碎片」,電腦用多了會有不少這樣零散的碎片空間,所以有碎片整理。在mysql中,這裏的頁可理解爲塊存儲空間,即索引的樹節點是存放在頁中的,每一頁(稱爲邏輯頁)有固定大小,InnoDB目前是16kb,一頁用完了,當繼續插入表生成新的索引節點時,就去新的頁中存儲這個節點,再有新的節點就繼續放在這個新的頁的節點後面。併發
2. 頁分裂問題,一頁總要被存滿,而後新開一頁繼續,這種行爲被稱做頁分裂。什麼時候開闢新的頁,mysql規定了一個分裂因子,達到頁存儲空間的15/16則存到下一頁。頁分裂的存在可能極大影響性能維護索引的性能。一般提倡的是,設定一個無心義的整數自增索引,有利於索引存儲
若是非自增或不是整數索引,如非自增整數、相似MD5的字符串,以他們做爲索引值時,由於待插入的下一條數據的值不必定比上一條大,甚至比當前頁全部值都小,須要跑到前幾頁去比較而找到合適位置,InnoDB沒法簡單的把新行插入到上一行後面,而找到並插入索引後,可能致使該頁達到分裂因子閥值,須要頁分裂,進一步致使後面全部的索引頁的分裂和排序,數據量小也許沒什麼問題,數據量大的話可能會浪費大量時間,產生許多碎片。
主鍵老是惟一且非空,InnoDB自動對它創建了索引(primary key),對於非主鍵字段上創建的索引,又稱輔助索引,索引排列也是順序排列,只是它還附帶一個本條記錄的主鍵值的數據域,不是指向本數據行的指針,在使用輔助索引查找時,先找到對應這一列的索引值,再根據索引節點上的另外一個數據域---主鍵值,來查找該行記錄,即每次查找實際通過查找了兩次。額外的數據域存儲主鍵值的好處是,當頁分裂發生時,無需修改數據域的值,由於即便頁分裂,該行的主鍵值是不變的,而地址就變了。好比name字段的索引簡示以下
包含一列的索引稱爲單列索引,多列的稱爲複合索引,由於BTREE索引是順序排列的,因此比較適合範圍查詢,可是在複合索引中,還應注意列數目、列的順序以及前面範圍查詢的列對後邊列的影響。
好比有這樣一張表
create table staffs( id int primary key auto_increment, name varchar(24) not null default '' comment '姓名', age int not null default 0 comment '年齡', pos varchar(20) not null default '' comment '職位', add_time timestamp not null default current_timestamp comment '入職時間' ) charset utf8 comment '員工記錄表';
添加三列的複合索引
alter table staffs add index idx_nap(name, age, pos);
在BTREE索引的使用上,如下幾種狀況能夠用到該索引或索引的一部分(使用explain簡單查看使用狀況):
1. 全值匹配
如select * from staffs where name = 'July' and age = '23' and pos = 'dev' ,key字段顯示使用了idx_nap索引
2. 匹配最左列,對於複合索引來講,不老是匹配全部字段列,可是能夠匹配索引中靠左的列
如select * from staffs where name = 'July' and age = '23',key字段顯示用到了索引,注意,key_len字段(表示本次語句使用的索引長度)數值比上一條小了,意思是它並未使用所有索引列(一般這個長度可估摸着用了哪些索引列,埋個坑),事實上只用到了name和age列
再試試select * from staffs where name = 'July',它也用了索引,key_len值更小,實際只用到了索引中的name列
3. 匹配列前綴,即一個索引中列的前一部分,主要用在模糊匹配,如select * from staffs where name like 'J%',explain信息的key字段表示使用了索引,可是mysql的B樹索引不能非列前綴的模糊匹配,如select * from staffs where name like '%y' 或者 like '%u%',聽說是因爲底層存儲引擎的API限制
4. 匹配範圍,如select * from staffs where name > 'Mary',但俺在測試時發現>能夠,>=卻不行,至少在字符串列上不行(測試mysql版本5.5.12),然而在時間類型(timestamp)上卻能夠,不測試下還真不能肯定說就用到了索引==
出於好奇測了下整型字段的索引(idx_cn(count, name),count爲整型),發現整型受限制少不少,下面的都能用到索引,連前模糊匹配的都行
select * from indexTest1 where count > '10' select * from indexTest1 where count >= '10' select * from indexTest1 where count > '10%' select * from indexTest1 where count >= '10%' select * from indexTest1 where count > '%10%' select * from indexTest1 where count >= '%10%'
5. 精確匹配一列並範圍匹配右側相鄰列,即前一列是固定值,後一列是範圍值,它用了name與age兩個列的索引(key_len推測)
如select * from staffs where name = 'July' and age > 25
6. 只訪問索引的查詢,好比staffs表的狀況,索引創建在(name,age,pos)上面,前面一直是讀取的所有列,若是咱們用到了哪些列的索引,查詢時也只查這些列的數據,就是隻訪問索引的查詢,如
select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev' select name,age from staffs where name = July and age > 25
第一句用到了所有索引列,第二句只用了索引前兩列,select的字段就最多隻能是這兩列,這種查詢狀況的索引,mysql稱爲覆蓋索引,就是索引包含(覆蓋)了查詢的所有字段。是否是用到了索引查詢,在explain中須要看最後一個Extra列的信息,Using index代表使用了覆蓋索引,同時Using where代表也使用了where過濾
7. 前綴索引
區別於列前綴(相似like 'J%'形式的模糊匹配)和最左列索引(順序取索引中靠左的列的查詢),它只取某列的一部分做爲索引。一般在說InnoDB跟MyISAM的區別時,一個明顯的區別是:MyISAM支持全文索引,而InnoDB不行,甚至對於text、blob這種超長的字符串或二進制數據時,MyISAM會取前多少個字符做爲索引,InnoDb的前綴索引跟這個相似,某些列,通常是字符串類型,很長,所有做爲索引大大增長存儲空間,索引也須要維護,對於長字符串,又想做爲索引列,一個可取的辦法就是取前一部分(前綴),表明一整列做爲索引串,問題是:如何確保這個前綴能表明或大體表明這一列?因此mysql中有個概念是索引的選擇性,是指索引中不重複的值的數目(也稱基數)與整個表該列記錄總數(#T)的比值,好比一個列表(1,2,2,3),總數是4,不重複值數目爲3,選擇性爲3/4,所以選擇性範圍是[1/#T, 1],這個值越大,表示列中不重複值越多,越適合做爲前綴索引,惟一索引(UNIQUE KEY)的選擇性是1。
好比有一列a varchar(255),以它做前綴索引,好比以7個測試,逐個增長看看選擇性值增加到那個數基本不變,就表示能夠表明整列了,再結合這個長度的索引列是否存儲數據太多,作個權衡,基本就好了。但若是這個選擇性原本就小的可憐仍是算了
select count(distinct left(a, 7))/count(*) as non_repeat from tab;
定好一個前綴數目,如9,添加索引時能夠這樣
alter table tab add index idx_pn(name(9)) --單獨前綴索引 alter table tab add index idx_cpn(count, name(9)) --複合前綴索引
以上爲常見的使用索引的方式,有這麼些狀況不能用或不能全用,有的就是上面狀況的反例,以key(a, b, c)爲例
1. 跳過列,where a = 1 and c = 3,最多用到索引列a;where b = 2 and c = 3,一個也用不到,必須從最左列開始
2. 前面是範圍查詢,where a = 1 and b > 2 and c = 3,最多用到 a, b兩個索引列;
3. 順序顛倒,where c = 3 and b = 2 and a = 1,一個也用不到;
4. 索引列上使用了表達式,如where substr(a, 1, 3) = 'hhh',where a = a + 1,表達式是一大忌諱,再簡單mysql也不認。有時數據量不是大到嚴重影響速度時,通常能夠先查出來,好比先查全部有訂單記錄的數據,再在程序中去篩選以'cp1001'開頭的訂單,而不是寫sql過濾它;
5. 模糊匹配時,儘可能寫 where a like 'J%',字符串放在左邊,這樣纔可能用獲得a列索引,甚至可能還用不到,固然這得看數據類型,最好測試一下。
排序對索引的影響
order by是常常用的語句,排序也遵循最左前綴列的原則,好比key(a, b),下面語句能夠用到(測試爲妙)
select * from tab where a > 1 order by b select * from tab where a > 1 and b > '2015-12-01 00:00:00' order by b select * from tab order by a, b
如下狀況用不到
1. 非最左列,select * from tab order by b;
2. 不按索引列順序來的,select * from tab where b > '2015-12-01 00:00:00' order by a;
3. 多列排序,但列的順序方向不一致,select * from tab a asc, b desc。
初步瞭解以上內容後,就知道了索引冗餘了,好比有了(a,b)索引,(a)就是冗餘的,須要建立(a)索引時,直接建立(a)就行,而不是(id,a),id指主鍵,主鍵primary key已是UNIQUE KEY了,不用加惟一限制。
聚簇索引與覆蓋索引
前面說到,mysql索引從結構上只有兩類,BTREE與HASH,覆蓋索引只是在查詢時,要查詢的列恰好與使用的索引列徹底一致,mysql直接掃描索引,而後就可返回數據,大大提升效率,由於不需再去原表查詢、過濾,這種形式下的索引稱做覆蓋索引,好比key(a,b),查詢時select a,b from tab where a = 1 and b > 2,本質緣由:BTREE索引存儲了原表數據。
聚簇索引也不是單獨的索引,前面簡要寫到,BTREE索引會把數據放在索引中,即索引的葉子頁中,包括主鍵,主鍵是跟表數據緊挨着放在一塊兒的,由於表數據只有一份,一列鍵值要跟每一行數據都緊挨在一塊兒,因此一張表只有一個聚簇索引,對於mysql來講,就是主鍵列,它是默認的。
聚簇索引將表數據組織到了一塊兒(參考前面主鍵索引簡略圖),插入時嚴重依賴主鍵順序,最好是連續自增,不然面臨頻繁頁分裂問題,移動許多數據。
哈希索引
簡要說下,相似於數據結構中簡單實現的HASH表(散列表)同樣,當咱們在mysql中用哈希索引時,也是對索引列計算一個散列值(相似md五、sha一、crc32),而後對這個散列值以順序(默認升序)排列,同時記錄該散列值對應數據表中某行的指針,固然這只是簡略模擬圖
好比對姓名列創建hash索引,生成hash值按順序排列,可是順序排列的hash值並不對應表中記錄,從地址指針可反應出來,並且,hash索引可能創建在兩列或者更多列上,取得是多列數據後的hash值,它不存儲表中數據。它先計算列數據的hash值,與索引中的hash值比較,找到了而後比對列數據是否相等,可能涉及其餘列條件,而後返回數據。hash固然會有衝突,即碰撞,除非有不少衝突,通常hash索引效率很高,不然hash維護成本較高,所以哈希索引一般用在選擇性較高的列上面。哈希索引的結構決定了它的特色:
1. hash索引只是hash值順序排列,跟表數據沒有關係,沒法應用於order by;
2. hash索引是對它的全部列計算哈希值,所以在查詢時,必須帶上全部列,好比有(a, b)哈希索引,查詢時必須 where a = 1 and b = 2,少任何一個不行;
3. hash索引只能用於比較查詢 = 或 IN,其餘範圍查詢無效,本質仍是因不存儲表數據;
4. 一旦出現碰撞,hash索引必須遍歷全部的hash值,將地址所指向數據一一比較,直到找到全部符合條件的行。
填坑
前面提到經過explain的key_len字段,可大體估計出用了哪些列,索引列的長度跟索引列的數據類型直接相關,通常,咱們說int是4字節,bigint8字節,char是1字節,考慮到建表時要指定字符集,好比utf8,還跟選的字符集有關(==!),在utf8下邊,一個char是3字節,可是知道這些仍不能說key_len就是將用到的索引列的數據類型表明字節數一加不就完啦?事實總有點區別,測試方法比較機械(如下基於mysql 5.5.2)
建表,加索引,int型
--測試表 create table keyLenTest1( id int primary key auto_increment, typeKey int default 0 , add_time timestamp not null default current_timestamp ) charset utf8 --添加索引 alter table keyLenTest1 add index idx_k(typeKey);
可知int型索引默認長度爲5,在4字節基礎上+1
char型
--改成char型,1個字符 alter table keyLenTest1 modify typeKey char(1);
--改成char型,2個字符 alter table keyLenTest1 modify typeKey char(2);
可知,char型初始是4字節(3+1 bytes),後續按照3字節遞增
varchar型
--改成varchar型,1個字符 alter table keyLenTest1 modify typeKey varchar(1);
--改成varchar型,2個字符 alter table keyLenTest1 modify typeKey varchar(2);
可知,varchar型,1個字符時,key_len爲6,之後以3字節遞增
因此,若是一個語句用到了int、char、varchar,key_len如何計算以及用了哪些索引列應該很清楚了。
若是想了解的更詳細點,explain各字段意義,索引的更多細節,除了explain,還有show profiles、慢查詢日誌等(沒細看),推薦看高性能mysql,畢竟俺寫的太膚淺。
end~