cpci检索为什么那么慢_索引原理与慢查询优化
一、前言
為何要有索引?
一般的應(yīng)用系統(tǒng),讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現(xiàn)性能問題,在生產(chǎn)環(huán)境中,我們遇到最多的,也是最容易出問題的,還是一些復(fù)雜的查詢操作,因此對查詢語句的優(yōu)化顯然是重中之重。說起加速查詢,就不得不提到索引了。
什么是索引?
索引在MySQL中也叫做“鍵”,是存儲引擎用于快速找到記錄的一種數(shù)據(jù)結(jié)構(gòu)。索引對于良好的性能
非常關(guān)鍵,尤其是當(dāng)表中的數(shù)據(jù)量越來越大時,索引對于性能的影響愈發(fā)重要。
索引優(yōu)化應(yīng)該是對查詢性能優(yōu)化最有效的手段了。索引能夠輕易將查詢性能提高好幾個數(shù)量級。
索引相當(dāng)于字典的音序表,如果要查某個字,如果不使用音序表,則需要從幾百頁中逐頁去查。
索引是應(yīng)用程序設(shè)計(jì)和開發(fā)的一個重要方面。若索引太多,應(yīng)用程序的性能可能會受到影響。而索引太少,對查詢性能又會產(chǎn)生影響,要找到一個平衡點(diǎn),這對應(yīng)用程序的性能至關(guān)重要。一些開發(fā)人員總是在事后才想起添加索引----我一直認(rèn)為,這源于一種錯誤的開發(fā)模式。如果知道數(shù)據(jù)的使用,從一開始就應(yīng)該在需要處添加索引。開發(fā)人員往往對數(shù)據(jù)庫的使用停留在應(yīng)用的層面,比如編寫SQL語句、存儲過程之類,他們甚至可能不知道索引的存在,或認(rèn)為事后讓相關(guān)DBA加上即可。DBA往往不夠了解業(yè)務(wù)的數(shù)據(jù)流,而添加索引需要通過監(jiān)控大量的SQL語句進(jìn)而從中找到問題,這個步驟所需的時間肯定是遠(yuǎn)大于初始添加索引所需的時間,并且可能會遺漏一部分的索引。當(dāng)然索引也并不是越多越好,我曾經(jīng)遇到過這樣一個問題:某臺MySQL服務(wù)器iostat顯示磁盤使用率一直處于100%,經(jīng)過分析后發(fā)現(xiàn)是由于開發(fā)人員添加了太多的索引,在刪除一些不必要的索引之后,磁盤使用率馬上下降為20%。可見索引的添加也是非常有技術(shù)含量的。
你是否對索引存在誤解?
二、索引的原理
索引原理
索引的目的在于提高查詢效率,與我們查閱圖書所用的目錄是一個道理:先定位到章,然后定位到該章下的一個小節(jié),然后找到頁數(shù)。相似的例子還有:查字典,查火車車次,飛機(jī)航班等
本質(zhì)都是:通過不斷地縮小想要獲取數(shù)據(jù)的范圍來篩選出最終想要的結(jié)果,同時把隨機(jī)的事件變成順序的事件,也就是說,有了這種索引機(jī)制,我們可以總是用同一種查找方式來鎖定數(shù)據(jù)。
數(shù)據(jù)庫也是一樣,但顯然要復(fù)雜的多,因?yàn)椴粌H面臨著等值查詢,還有范圍查詢(>、
磁盤IO與預(yù)讀
前面提到了訪問磁盤,那么這里先簡單介紹一下磁盤IO和預(yù)讀,磁盤讀取數(shù)據(jù)靠的是機(jī)械運(yùn)動,每次讀取數(shù)據(jù)花費(fèi)的時間可以分為尋道時間、旋轉(zhuǎn)延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所需要的時間,主流磁盤一般在5ms以下;旋轉(zhuǎn)延遲就是我們經(jīng)常聽說的磁盤轉(zhuǎn)速,比如一個磁盤7200轉(zhuǎn),表示每分鐘能轉(zhuǎn)7200次,也就是說1秒鐘能轉(zhuǎn)120次,旋轉(zhuǎn)延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或?qū)?shù)據(jù)寫入磁盤的時間,一般在零點(diǎn)幾毫秒,相對于前兩個時間可以忽略不計(jì)。那么訪問一次磁盤的時間,即一次磁盤IO的時間約等于5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機(jī)器每秒可以執(zhí)行5億條指令,因?yàn)橹噶钜揽康氖请姷男再|(zhì),換句話說執(zhí)行一次IO的時間可以執(zhí)行約450萬條指令,數(shù)據(jù)庫動輒十萬百萬乃至千萬級數(shù)據(jù),每次9毫秒的時間,顯然是個災(zāi)難。下圖是計(jì)算機(jī)硬件延遲的對比圖,供大家參考:
考慮到磁盤IO是非常高昂的操作,計(jì)算機(jī)操作系統(tǒng)做了一些優(yōu)化,當(dāng)一次IO時,不光把當(dāng)前磁盤地址的數(shù)據(jù),而是把相鄰的數(shù)據(jù)也都讀取到內(nèi)存緩沖區(qū)內(nèi),因?yàn)榫植款A(yù)讀性原理告訴我們,當(dāng)計(jì)算機(jī)訪問一個地址的數(shù)據(jù)的時候,與其相鄰的數(shù)據(jù)也會很快被訪問到。每一次IO讀取的數(shù)據(jù)我們稱之為一頁(page)。具體一頁有多大數(shù)據(jù)跟操作系統(tǒng)有關(guān),一般為4k或8k,也就是我們讀取一頁內(nèi)的數(shù)據(jù)時候,實(shí)際上才發(fā)生了一次IO,這個理論對于索引的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)非常有幫助。
三、索引的數(shù)據(jù)結(jié)構(gòu)
前面講了索引的基本原理,數(shù)據(jù)庫的復(fù)雜性,又講了操作系統(tǒng)的相關(guān)知識,目的就是讓大家了解,任何一種數(shù)據(jù)結(jié)構(gòu)都不是憑空產(chǎn)生的,一定會有它的背景和使用場景,我們現(xiàn)在總結(jié)一下,我們需要這種數(shù)據(jù)結(jié)構(gòu)能夠做些什么,其實(shí)很簡單,那就是:每次查找數(shù)據(jù)時把磁盤IO次數(shù)控制在一個很小的數(shù)量級,最好是常數(shù)數(shù)量級。那么我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣,b+樹應(yīng)運(yùn)而生(B+樹是通過二叉查找樹,再由平衡二叉樹,B樹演化而來)。
如上圖,是一顆b+樹,關(guān)于b+樹的定義可以參見B+樹,這里只說一些重點(diǎn),淺藍(lán)色的塊我們稱之為一個磁盤塊,可以看到每個磁盤塊包含幾個數(shù)據(jù)項(xiàng)(深藍(lán)色所示)和指針(黃色所示),如磁盤塊1包含數(shù)據(jù)項(xiàng)17和35,包含指針P1、P2、P3,P1表示小于17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大于35的磁盤塊。真實(shí)的數(shù)據(jù)存在于葉子節(jié)點(diǎn)即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節(jié)點(diǎn)只不存儲真實(shí)的數(shù)據(jù),只存儲指引搜索方向的數(shù)據(jù)項(xiàng),如17、35并不真實(shí)存在于數(shù)據(jù)表中。
###b+樹的查找過程
如圖所示,如果要查找數(shù)據(jù)項(xiàng)29,那么首先會把磁盤塊1由磁盤加載到內(nèi)存,此時發(fā)生一次IO,在內(nèi)存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內(nèi)存時間因?yàn)榉浅6?相比磁盤的IO)可以忽略不計(jì),通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內(nèi)存,發(fā)生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內(nèi)存,發(fā)生第三次IO,同時內(nèi)存中做二分查找找到29,結(jié)束查詢,總計(jì)三次IO。真實(shí)的情況是,3層的b+樹可以表示上百萬的數(shù)據(jù),如果上百萬的數(shù)據(jù)查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數(shù)據(jù)項(xiàng)都要發(fā)生一次IO,那么總共需要百萬次的IO,顯然成本非常非常高。
###b+樹性質(zhì)
1.索引字段要盡量的小:通過上面的分析,我們知道IO次數(shù)取決于b+數(shù)的高度h,假設(shè)當(dāng)前數(shù)據(jù)表的數(shù)據(jù)為N,每個磁盤塊的數(shù)據(jù)項(xiàng)的數(shù)量是m,則有h=㏒(m+1)N,當(dāng)數(shù)據(jù)量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數(shù)據(jù)項(xiàng)的大小,磁盤塊的大小也就是一個數(shù)據(jù)頁的大小,是固定的,如果數(shù)據(jù)項(xiàng)占的空間越小,數(shù)據(jù)項(xiàng)的數(shù)量越多,樹的高度越低。這就是為什么每個數(shù)據(jù)項(xiàng),即索引字段要盡量的小,比如int占4字節(jié),要比bigint8字節(jié)少一半。這也是為什么b+樹要求把真實(shí)的數(shù)據(jù)放到葉子節(jié)點(diǎn)而不是內(nèi)層節(jié)點(diǎn),一旦放到內(nèi)層節(jié)點(diǎn),磁盤塊的數(shù)據(jù)項(xiàng)會大幅度下降,導(dǎo)致樹增高。當(dāng)數(shù)據(jù)項(xiàng)等于1時將會退化成線性表。
2.索引的最左匹配特性:當(dāng)b+樹的數(shù)據(jù)項(xiàng)是復(fù)合的數(shù)據(jù)結(jié)構(gòu),比如(name,age,sex)的時候,b+數(shù)是按照從左到右的順序來建立搜索樹的,比如當(dāng)(張三,20,F)這樣的數(shù)據(jù)來檢索的時候,b+樹會優(yōu)先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最后得到檢索的數(shù)據(jù);但當(dāng)(20,F)這樣的沒有name的數(shù)據(jù)來的時候,b+樹就不知道下一步該查哪個節(jié)點(diǎn),因?yàn)榻⑺阉鳂涞臅r候name就是第一個比較因子,必須要先根據(jù)name來搜索才能知道下一步去哪里查詢。比如當(dāng)(張三,F)這樣的數(shù)據(jù)來檢索時,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等于張三的數(shù)據(jù)都找到,然后再匹配性別是F的數(shù)據(jù)了, 這個是非常重要的性質(zhì),即索引的最左匹配特性。
四、 聚集索引與輔助索引
在數(shù)據(jù)庫中,B+樹的高度一般都在2~4層,這也就是說查找某一個鍵值的行記錄時最多只需要2到4次IO,這倒不錯。因?yàn)楫?dāng)前一般的機(jī)械硬盤每秒至少可以做100次IO,2~4次的IO意味著查詢時間只需要0.02~0.04秒。
數(shù)據(jù)庫中的B+樹索引可以分為聚集索引(clustered index)和輔助索引(secondary index),
聚集索引與輔助索引相同的是:不管是聚集索引還是輔助索引,其內(nèi)部都是B+樹的形式,即高度是平衡的,葉子結(jié)點(diǎn)存放著所有的數(shù)據(jù)。
聚集索引與輔助索引不同的是:葉子結(jié)點(diǎn)存放的是否是一整行的信息
1、聚集索引
#InnoDB存儲引擎表示索引組織表,即表中數(shù)據(jù)按照主鍵順序存放。而聚集索引(clustered index)就是按照每張表的主鍵構(gòu)造一棵B+樹,同時葉子結(jié)點(diǎn)存放的即為整張表的行記錄數(shù)據(jù),也將聚集索引的葉子結(jié)點(diǎn)稱為數(shù)據(jù)頁。聚集索引的這個特性決定了索引組織表中數(shù)據(jù)也是索引的一部分。同B+樹數(shù)據(jù)結(jié)構(gòu)一樣,每個數(shù)據(jù)頁都通過一個雙向鏈表來進(jìn)行鏈接。
#如果未定義主鍵,MySQL取第一個唯一索引(unique)而且只含非空列(NOT NULL)作為主鍵,InnoDB使用它作為聚簇索引。
#如果沒有這樣的列,InnoDB就自己產(chǎn)生一個這樣的ID值,它有六個字節(jié),而且是隱藏的,使其作為聚簇索引。
#由于實(shí)際的數(shù)據(jù)頁只能按照一棵B+樹進(jìn)行排序,因此每張表只能擁有一個聚集索引。在多少情況下,查詢優(yōu)化器傾向于采用聚集索引。因?yàn)榫奂饕軌蛟贐+樹索引的葉子節(jié)點(diǎn)上直接找到數(shù)據(jù)。此外由于定義了數(shù)據(jù)的邏輯順序,聚集索引能夠特別快地訪問針對范圍值得查詢。
View Code
聚集索引的好處之一:它對主鍵的排序查找和范圍查找速度非常快,葉子節(jié)點(diǎn)的數(shù)據(jù)就是用戶所要查詢的數(shù)據(jù)。如用戶需要查找一張表,查詢最后的10位用戶信息,由于B+樹索引是雙向鏈表,所以用戶可以快速找到最后一個數(shù)據(jù)頁,并取出10條記錄
#參照第六小結(jié)測試索引的準(zhǔn)備階段來創(chuàng)建出表s1
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 | |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.00sec)
mysql> explain select * from s1 order by id desc limit 10; #Using filesort,需要二次排序
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 2633472 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
1 row in set, 1 warning (0.11sec)
mysql> alter table s1 add primary key(id); #添加主鍵
Query OK, 0 rows affected (13.37sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from s1 order by id desc limit 10; #基于主鍵的聚集索引在創(chuàng)建完畢后就已經(jīng)完成了排序,無需二次排序
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | index | NULL | PRIMARY | 4 | NULL | 10 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.04 sec)
View Code
聚集索引的好處之二:范圍查詢(range query),即如果要查找主鍵某一范圍內(nèi)的數(shù)據(jù),通過葉子節(jié)點(diǎn)的上層中間節(jié)點(diǎn)就可以得到頁的范圍,之后直接讀取數(shù)據(jù)頁即可
mysql>alter table s1 drop primary key;
Query OK,2699998 rows affected (24.23sec)
Records:2699998Duplicates: 0 Warnings: 0
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 | |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.12sec)
mysql> explain select * from s1 where id > 1 and id < 1000000; #沒有聚集索引,預(yù)估需要檢索的rows數(shù)如下
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 2690100 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00sec)
mysql>alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from s1 where id > 1 and id < 1000000; #有聚集索引,預(yù)估需要檢索的rows數(shù)如下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1343355 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.09 sec)
View Code
2、輔助索引
表中除了聚集索引外其他索引都是輔助索引(Secondary Index,也稱為非聚集索引),與聚集索引的區(qū)別是:輔助索引的葉子節(jié)點(diǎn)不包含行記錄的全部數(shù)據(jù)。
葉子節(jié)點(diǎn)除了包含鍵值以外,每個葉子節(jié)點(diǎn)中的索引行中還包含一個書簽(bookmark)。該書簽用來告訴InnoDB存儲引擎去哪里可以找到與索引相對應(yīng)的行數(shù)據(jù)。
由于InnoDB存儲引擎是索引組織表,因此InnoDB存儲引擎的輔助索引的書簽就是相應(yīng)行數(shù)據(jù)的聚集索引鍵。如下圖
輔助索引的存在并不影響數(shù)據(jù)在聚集索引中的組織,因此每張表上可以有多個輔助索引,但只能有一個聚集索引。當(dāng)通過輔助索引來尋找數(shù)據(jù)時,InnoDB存儲引擎會遍歷輔助索引并通過葉子級別的指針獲得只想主鍵索引的主鍵,然后再通過主鍵索引來找到一個完整的行記錄。
舉例來說,如果在一棵高度為3的輔助索引樹種查找數(shù)據(jù),那需要對這個輔助索引樹遍歷3次找到指定主鍵,如果聚集索引樹的高度同樣為3,那么還需要對聚集索引樹進(jìn)行3次查找,最終找到一個完整的行數(shù)據(jù)所在的頁,因此一共需要6次邏輯IO訪問才能得到最終的一個數(shù)據(jù)頁。
五、MySQL索引管理
1. 功能
#1. 索引的功能就是加速查找#2. mysql中的primary key,unique,聯(lián)合唯一也都是索引,這些索引除了加速查找以外,還有約束的功能
2. MySQL常用的索引
普通索引INDEX:加速查找
唯一索引:-主鍵索引PRIMARY KEY:加速查找+約束(不為空、不能重復(fù))-唯一索引UNIQUE:加速查找+約束(不能重復(fù))
聯(lián)合索引:-PRIMARY KEY(id,name):聯(lián)合主鍵索引-UNIQUE(id,name):聯(lián)合唯一索引-INDEX(id,name):聯(lián)合普通索引
舉個例子來說,比如你在為某商場做一個會員卡的系統(tǒng)。
這個系統(tǒng)有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備注信息 TEXT
那么這個 會員編號,作為主鍵,使用 PRIMARY
會員姓名 如果要建索引的話,那么就是普通的 INDEX
會員身份證號碼 如果要建索引的話,那么可以選擇 UNIQUE (唯一的,不允許重復(fù))#除此之外還有全文索引,即FULLTEXT
會員備注信息 , 如果需要建索引的話,可以選擇全文搜索。
用于搜索很長一篇文章的時候,效果最好。
用在比較短的文本,如果就一兩行字的,普通的 INDEX 也可以。
但其實(shí)對于全文搜索,我們并不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來做全文搜索。#其他的如空間索引SPATIAL,了解即可,幾乎不用
View Code
3.?索引的兩大類型hash與btree
#我們可以在創(chuàng)建上述索引的時候,為其指定索引類型,分兩類
hash類型的索引:查詢單條快,范圍查詢慢
btree類型的索引:b+樹,層數(shù)越多,數(shù)據(jù)量指數(shù)級增長(我們就用它,因?yàn)閕nnodb默認(rèn)支持它)#不同的存儲引擎支持的索引類型也不一樣
InnoDB 支持事務(wù),支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事務(wù),支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事務(wù),支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事務(wù),支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事務(wù),支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;
4.?創(chuàng)建/刪除索引的語法
#方法一:創(chuàng)建表時
CREATE TABLE 表名 (
字段名1 數(shù)據(jù)類型 [完整性約束條件…],
字段名2 數(shù)據(jù)類型 [完整性約束條件…],
[UNIQUE| FULLTEXT | SPATIAL ] INDEX |KEY6.
[索引名] (字段名[(長度)] [ASC|DESC])
);#方法二:CREATE在已存在的表上創(chuàng)建索引
CREATE [UNIQUE | FULLTEXT |SPATIAL ] INDEX 索引名
ON 表名 (字段名[(長度)] [ASC|DESC]) ;#方法三:ALTER TABLE在已存在的表上創(chuàng)建索引
ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT |SPATIAL ] INDEX
索引名 (字段名[(長度)] [ASC|DESC]) ;#刪除索引:DROP INDEX 索引名 ON 表名字;
#方式一
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 index ix_age on t1(age);#方式三
alter table t1 add index ix_sex(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
示范
六、測試索引
1. 準(zhǔn)備
#1. 準(zhǔn)備表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);#2. 創(chuàng)建存儲過程,實(shí)現(xiàn)批量插入記錄
delimiter $$ #聲明存儲過程的結(jié)束符號為$$
create procedure auto_insert1()
BEGIN
declare i int default1;while(i<3000000)do
insert into s1 values(i,'egon','male',concat('egon',i,'@oldboy'));
set i=i+1;
endwhile;
END$$#$$結(jié)束
delimiter ; #重新聲明分號為結(jié)束符號
#3. 查看存儲過程
show create procedure auto_insert1\G#4. 調(diào)用存儲過程
call auto_insert1();
View Code
2. 在沒有索引的前提下測試查詢速度
#無索引:mysql根本就不知道到底是否存在id等于333333333的記錄,只能把數(shù)據(jù)表從頭到尾掃描一遍,此時有多少個磁盤塊就需要進(jìn)行多少IO操作,所以查詢速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)
3. 在表中已經(jīng)存在大量數(shù)據(jù)的前提下,為某個字段段建立索引,建立速度會很慢
4.在索引建立完畢后,以該字段為查詢條件時,查詢速度提升明顯
PS:
1. mysql先去索引表里根據(jù)b+樹的搜索原理很快搜索到id等于333333333的記錄不存在,IO大大降低,因而速度明顯提升
2. 我們可以去mysql的data目錄下找到該表,可以看到占用的硬盤空間多了
3. 需要注意,如下圖
5 總結(jié)
#1. 一定是為搜索條件的字段創(chuàng)建索引,比如select * from s1 where id = 333;就需要為id加上索引
#2. 在表中已經(jīng)有大量數(shù)據(jù)的情況下,建索引會很慢,且占用硬盤空間,建完后查詢速度加快
比如create index idx on s1(id);會掃描表中所有的數(shù)據(jù),然后以id為數(shù)據(jù)項(xiàng),創(chuàng)建索引結(jié)構(gòu),存放于硬盤的表中。
建完以后,再查詢就會很快了。#3. 需要注意的是:innodb表的索引會存放于s1.ibd文件中,而myisam表的索引則會有單獨(dú)的索引文件table1.MYI
MySAM索引文件和數(shù)據(jù)文件是分離的,索引文件僅保存數(shù)據(jù)記錄的地址。而在innodb中,表數(shù)據(jù)文件本身就是按照B+Tree(BTree即Balance True)組織的一個索引結(jié)構(gòu),這棵樹的葉節(jié)點(diǎn)data域保存了完整的數(shù)據(jù)記錄。這個索引的key是數(shù)據(jù)表的主鍵,因此innodb表數(shù)據(jù)文件本身就是主索引。
因?yàn)閕nndob的數(shù)據(jù)文件要按照主鍵聚集,所以innodb要求表必須要有主鍵(Myisam可以沒有),如果沒有顯式定義,則mysql系統(tǒng)會自動選擇一個可以唯一標(biāo)識數(shù)據(jù)記錄的列作為主鍵,如果不存在這種列,則mysql會自動為innodb表生成一個隱含字段作為主鍵,這字段的長度為6個字節(jié),類型為長整型.
總結(jié)
七. 正確使用索引
并不是說我們創(chuàng)建了索引就一定會加快查詢速度,若想利用索引達(dá)到預(yù)想的提高查詢速度的效果,我們在添加索引時,必須遵循一些規(guī)則,避免不恰當(dāng)索引,過多索引的問題
我們知道對于查詢的三種情況:緩存查詢,全盤掃描,索引掃描,其中緩存查詢?yōu)樽羁?#xff0c;要盡量避免全盤掃描。
我們可以通過explain(desc)命令獲取優(yōu)化器選擇后的執(zhí)行計(jì)劃
explain select * from city where countrycode='CHN'\G
#執(zhí)行結(jié)果
id: 1select_type: SIMPLE
table: t1
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len:4ref: NULL
rows:9filtered:100.00Extra: Using where
上述結(jié)果,有些關(guān)鍵的字段值得我們注意
關(guān)鍵字之type
我們可以看到此次的type為range,是索引范圍掃描
對于索引掃描來講,又可以細(xì)劃分,可以判斷是哪一種類的索引掃描
type的具體類型介紹:
數(shù)據(jù)準(zhǔn)備
+-------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| Name | char(35) | NO | | | |
| CountryCode | char(3) | NO | MUL | | |
| District | char(20) | NO | | | |
| Population | int(11) | NO | | 0 | |
+-------------+----------+------+-----+---------+----------------+
#可以看出除了ID建了一個聚集索引,CountryCode建了一個普通索引外,其余字段都沒有索引
city表結(jié)構(gòu)
select count(*) fromcity;+----------+
| count(*) |
+----------+
| 4079 |
+----------+
city表內(nèi)數(shù)據(jù)量
1.? ALL:全盤掃描
desc select * from city where Name="ShangHai"\G;#Name字段沒有建立索引,查詢是全盤掃描
id:1select_type: SIMPLE
table: city
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows:4188filtered:10.00Extra: Using where
View Code
2. Index:全索引掃描
desc select CountryCode fromcity\G;
id:1select_type: SIMPLE
table: city
partitions: NULL
type: index
possible_keys: NULL
key: CountryCode
key_len:3ref: NULL
rows:4188filtered:100.00Extra: Using index
View Code
3. range:索引范圍掃描
desc select * from city where CountryCode in ("CHN","USA");
where后面跟
where> < >= <=
#不能!=
in or between and
#不能是not in
like 'CH%'
#不能是前綴%,如like '%HN'
View Code
in 或者 or改寫成 union all
select* from city where countrycode='CHN'union all
select* from city where countrycode='USA';#type為ref
View Code
4.?ref:輔助索引的等值查詢
select * from city where countrycode='CHN'
View Code
5. eq_ref:多表鏈接查詢(join on )
6.?const ,system:主鍵或唯一鍵等值查詢
desc select * from city where ID=2252;
View Code
關(guān)鍵字之Extra
對于語句在Extra列會出現(xiàn)Using filesort,根據(jù)mysql官方文檔對他的描述:
MySQL must do an extra pass to find out how to retrieve the rows in sorted order. The sort is done by going through all rows according to the join type and storing the sort key and pointer to the row for all rows that match the WHERE clause.
Using filesort 是Mysql里一種速度比較慢的外部排序,盡量避免
usingfilesort: 文件排序
將order by、group by、distinct 后的列和where條件列建立聯(lián)合索引
關(guān)鍵字之possible_keys:代表可能會走的索引
關(guān)鍵字之key: 真正走的索引
八. 聯(lián)合索引和覆蓋索引
一 聯(lián)合索引
聯(lián)合索引時指對表上的多個列合起來做一個索引。聯(lián)合索引的創(chuàng)建方法與單個索引的創(chuàng)建方法一樣,不同之處在僅在于有多個索引列,如下
mysql>create table t(->a int,->b int,->primary key(a),->key idx_a_b(a,b)->);
Query OK, 0 rows affected (0.11 sec)
那么何時需要使用聯(lián)合索引呢?在討論這個問題之前,先來看一下聯(lián)合索引內(nèi)部的結(jié)果。從本質(zhì)上來說,聯(lián)合索引就是一棵B+樹,不同的是聯(lián)合索引的鍵值得數(shù)量不是1,而是>=2。接著來討論兩個整型列組成的聯(lián)合索引,假定兩個鍵值得名稱分別為a、b如圖
可以看到這與我們之前看到的單個鍵的B+樹并沒有什么不同,鍵值都是排序的,通過葉子結(jié)點(diǎn)可以邏輯上順序地讀出所有數(shù)據(jù),就上面的例子來說,即(1,1),(1,2),(2,1),(2,4),(3,1),(3,2),數(shù)據(jù)按(a,b)的順序進(jìn)行了存放。
因此,對于查詢select * from table where a=xxx and b=xxx, 顯然是可以使用(a,b) 這個聯(lián)合索引的,對于單個列a的查詢select * from table where a=xxx,也是可以使用(a,b)這個索引的。
但對于b列的查詢select * from table where b=xxx,則不可以使用(a,b) 索引,其實(shí)你不難發(fā)現(xiàn)原因,葉子節(jié)點(diǎn)上b的值為1、2、1、4、1、2顯然不是排序的,因此對于b列的查詢使用不到(a,b) 索引
聯(lián)合索引的第二個好處是在第一個鍵相同的情況下,已經(jīng)對第二個鍵進(jìn)行了排序處理,例如在很多情況下應(yīng)用程序都需要查詢某個用戶的購物情況,并按照時間進(jìn)行排序,最后取出最近三次的購買記錄,這時使用聯(lián)合索引可以幫我們避免多一次的排序操作,因?yàn)樗饕旧碓谌~子節(jié)點(diǎn)已經(jīng)排序了,如下
#===========準(zhǔn)備表==============
create table buy_log(
userid int unsignednotnull,
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);#===========驗(yàn)證==============
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與聯(lián)合索引userid_2,但是優(yōu)化器最終選擇了使用的key是userid因?yàn)樵撍饕娜~子節(jié)點(diǎn)包含單個鍵值,所以理論上一個頁能存放的記錄應(yīng)該更多
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 | |
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+
1 row in set (0.00sec)#接著假定要取出userid為1的最近3次的購買記錄,用的就是聯(lián)合索引userid_2了,因?yàn)樵谶@個索引中,在userid=1的情況下,buy_date都已經(jīng)排序好了
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 |
+----+-------------+---------+------+-----------------+----------+---------+-------+------+--------------------------+
1 row in set (0.00sec)#ps:如果extra的排序顯示是Using filesort,則意味著在查出數(shù)據(jù)后需要二次排序(如下查詢語句,沒有先用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 |
+----+-------------+---------+-------+---------------+----------+---------+------+------+-----------------------------+
#對于聯(lián)合索引(a,b),下述語句可以直接使用該索引,無需二次排序
select ... from table where a=xxx order by b;#然后對于聯(lián)合索引(a,b,c)來首,下列語句同樣可以直接通過索引得到結(jié)果
select ... from table where a=xxx order by b;
select ...from table where a=xxx and b=xxx order by c;#但是對于聯(lián)合索引(a,b,c),下列語句不能通過索引直接得到結(jié)果,還需要自己執(zhí)行一次filesort操作,因?yàn)樗饕?a,c)并未排序
select ... from table where a=xxx order by c;
View Code
二 覆蓋索引
InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就可以得到查詢記錄,而不需要查詢聚集索引中的記錄。
使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的所有信息,故其大小要遠(yuǎn)小于聚集索引,因此可以減少大量的IO操作
注意:覆蓋索引技術(shù)最早是在InnoDB Plugin中完成并實(shí)現(xiàn),這意味著對于InnoDB版本小于1.0的,或者M(jìn)ySQL數(shù)據(jù)庫版本為5.0以下的,InnoDB存儲引擎不支持覆蓋索引特性
對于InnoDB存儲引擎的輔助索引而言,由于其包含了主鍵信息,因此其葉子節(jié)點(diǎn)存放的數(shù)據(jù)為(primary key1,priamey key2,...,key1,key2,...)。例如
select age from s1 where id=123 and name = 'bob; #id字段有索引,但是name字段沒有索引,該sql命中了索引,但未覆蓋,需要去聚集索引中再查找詳細(xì)信息。
最牛逼的情況是,索引字段覆蓋了所有,那全程通過索引來加速查詢以及獲取結(jié)果就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 | |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.21sec)
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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00sec)
mysql> create index idx_id on s1(id); #創(chuàng)建索引
Query OK, 0 rows affected (4.16sec)
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 |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.08sec)
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 |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.03 sec)
View Code
覆蓋索引的另外一個好處是對某些統(tǒng)計(jì)問題而言的。基于上一小結(jié)創(chuàng)建的表buy_log,查詢計(jì)劃如下
mysql> explain select count(*) frombuy_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 |
+----+-------------+---------+-------+---------------+--------+---------+------+------+-------------+
1 row in set (0.00 sec)
View Code
innodb存儲引擎并不會選擇通過查詢聚集索引來進(jìn)行統(tǒng)計(jì)。由于buy_log表有輔助索引,而輔助索引遠(yuǎn)小于聚集索引,選擇輔助索引可以減少IO操作,故優(yōu)化器的選擇如上key為userid輔助索引
對于(a,b)形式的聯(lián)合索引,一般是不可以選擇b中所謂的查詢條件。但如果是統(tǒng)計(jì)操作,并且是覆蓋索引,則優(yōu)化器還是會選擇使用該索引,如下
#聯(lián)合索引userid_2(userid,buy_date),一般情況,我們按照buy_date是無法使用該索引的,但特殊情況下:查詢語句是統(tǒng)計(jì)操作,且是覆蓋索引,則按照buy_date當(dāng)做查詢條件時,也可以使用該聯(lián)合索引
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 |
+----+-------------+---------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
View Code
九. 索引使用的相關(guān)問題
1.盡量選擇區(qū)分度高的列作為索引,區(qū)分度的公式是count(distinct col)/count(*),表示字段不重復(fù)的比例,比例越大我們掃描的記錄數(shù)越少,唯一鍵的區(qū)分度是1,而一些狀態(tài)、性別字段可能在大數(shù)據(jù)面前區(qū)分度就是0,那可能有人會問,這個比例有什么經(jīng)驗(yàn)值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄
我們編寫存儲過程為表s1批量添加記錄,name字段的值均為egon,也就是說name這個字段的區(qū)分度很低(gender字段也是一樣的,我們稍后再搭理它)
回憶b+樹的結(jié)構(gòu),查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,需要保證:在某一層內(nèi)數(shù)據(jù)項(xiàng)均是按照從左到右,從小到大的順序依次排開,即左1
而對于區(qū)分度低的字段,無法找到大小關(guān)系,因?yàn)橹刀际窍嗟鹊?#xff0c;毫無疑問,還想要用b+樹存放這些等值的數(shù)據(jù),只能增加樹的高度,字段的區(qū)分度越低,則樹的高度越高。極端的情況,索引字段的值都一樣,那么b+樹幾乎成了一根棍。本例中就是這種極端的情況,name字段所有的值均為'egon'
#現(xiàn)在我們得出一個結(jié)論:為區(qū)分度低的字段建立索引,索引樹的高度會很高,然而這具體會帶來什么影響呢???
#1:如果條件是name='xxxx',那么肯定是可以第一時間判斷出'xxxx'是不在索引樹中的(因?yàn)闃渲兴械闹稻鶠?#39;egon’),所以查詢速度很快
#2:如果條件正好是name='egon',查詢時,我們永遠(yuǎn)無法從樹的某個位置得到一個明確的范圍,只能往下找,往下找,往下找。。。這與全表掃描的IO次數(shù)沒有多大區(qū)別,所以速度很慢
View Code
3?=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優(yōu)化器會幫你優(yōu)化成索引可以識別的形式
4?索引列不能參與計(jì)算,保持列“干凈”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數(shù)據(jù)表中的字段值,但進(jìn)行檢索時,需要把所有元素都應(yīng)用函數(shù)才能比較,顯然成本太大。所以語句應(yīng)該寫成create_time = unix_timestamp(’2014-05-29’)
5?最左前綴匹配原則(詳見第八小節(jié)),非常重要的原則,對于組合索引mysql會一直向右匹配直到遇到范圍查詢(>、 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調(diào)整。
6.其他情況
-使用函數(shù)
select* from tb1 where reverse(email) = 'egon';-類型不一致
如果列是字符串類型,傳入條件是必須用引號引起來,不然...
select* from tb1 where email = 999;#排序條件為索引,則select字段必須也是索引字段,否則無法命中
-order by
select namefroms1 order by email desc;
當(dāng)根據(jù)索引排序時候,select查詢的字段如果不是索引,則速度仍然很慢
select emailfroms1 order by email desc;
特別的:如果對主鍵排序,則還是速度很快:
select* fromtb1 order by nid desc;-組合索引最左前綴
如果組合索引為:(name,email)
nameand email --命中索引
name--命中索引
email--未命中索引- count(1)或count(列)代替count(*)在mysql中沒有差別了- create index xxxx on tb(title(19)) #text類型,必須制定長度
View Code
總結(jié)
以上是生活随笔為你收集整理的cpci检索为什么那么慢_索引原理与慢查询优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx 上传文件漏洞_nginx爆惊
- 下一篇: opcuaclient 文档_连接协议-