<MySQL>索引原理
-
索引介紹
- 需求:一般的應用系統,讀寫比例大概在10:1左右,急需優化讀操作,MySQL裡的讀操作表現形式就是查詢語句,優化查詢語句就是目的。
- 索引:相當於圖書的目錄,可以幫助使用者快速的找到需要的內容。
- 本質:通過不斷地縮小想要獲取資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查詢方式來鎖定資料。
-
索引方法
- B+樹索引:由平衡樹和二叉查詢樹結合產生的一種平衡查詢樹。
- 特點:所有的記錄節點都是按鍵值大小順序存放在同一層的葉節點中,葉節點間用指標相連,構成雙向迴圈連結串列,非葉節點(根節點、枝節點)只存放鍵值,不存放實際資料。
- 例子
- 查詢過程:
- 小知識:系統從磁碟讀取資料到記憶體時是以磁碟塊(block)為基本單位的,位於同一磁碟塊中的資料會被一次性讀取出來,而不是按需讀取。InnoDB 儲存引擎使用頁作為資料讀取單位,頁是其磁碟管理的最小單位,預設 page 大小是 16kB。
- 目標:查詢數字30
- 1.首先會把磁碟塊1由磁碟載入到記憶體,此時發生一次IO,在記憶體中用二分查詢確定30在28和65之間,鎖定磁碟塊1的P2指標,記憶體時間非常短(相比磁碟的IO)可以忽略不計。
- 2.通過磁碟塊1的P2指標的磁碟地址把磁碟塊由磁碟載入到記憶體,發生第二次IO,30在28和35之間,鎖定當前磁碟塊的P1指標。
- 3.通過指標載入磁碟塊到記憶體,發生第三次IO,同時記憶體中做二分查詢找到30,結束查詢,總計三次IO。
- 真實的情況是,3層的b+樹可以表示上百萬的資料,如果上百萬的資料查詢只需要三次IO,效能提高將是巨大的,如果沒有索引,每個資料項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。
- HASH 索引
- hash就是特殊形式的鍵值對,允許多個key對應相同的value,但不允許一個key對應多個value。
- 索引建立方式
- 為某一列或幾列建立hash索引,就會利用這一列或幾列的值通過一定的演算法計算出一個hash值,對應一行或幾行資料.
- hash索引可以一次定位,不需要像樹形索引那樣逐層查詢,因此具有極高的效率.
- B+樹索引:由平衡樹和二叉查詢樹結合產生的一種平衡查詢樹。
-
索引型別
- 普通索引
- 功能:加速查詢
- 建立表,表中新增索引
-
#建立表同時新增name欄位為普通索引 create table tb1( id int not null auto_increment primary key, name varchar(100) not null, index idx_name(name) );
-
- 單獨建立索引
-
create index idx_name on tb1(name);
-
- 刪除索引
-
drop index idx_name on tb1;
-
- 檢視索引
-
show index from tb1;
-
- 唯一索引
- 功能:加速查詢 和 唯一約束(不可含null)
- 表中建立唯一索引
-
create table tb2( id int not null auto_increment primary key, name varchar(50) not null, age int not null, unique index idx_age (age) )
-
- 語句建立唯一索引
-
create unique index idx_age on tb2(age);
-
- 主鍵索引:一個表中最多隻能有一個主鍵索引
- 功能:加速查詢 和 唯一約束(不可含null)
- 表中建立主鍵
-
#方式一: create table tb3( id int not null auto_increment primary key, name varchar(50) not null, age int default 0 ); #方式二: create table tb3( id int not null auto_increment, name varchar(50) not null, age int default 0 , primary key(id) );
-
- 單獨建立
-
alter table tb3 add primary key(id);
-
- 刪除主鍵
-
#方式一 alter table tb3 drop primary key; #方式二: #如果當前主鍵為自增主鍵,則不能直接刪除.需要先修改自增屬性,再刪除 alter table tb3 modify id int ,drop primary key;
-
- 組合索引:組合索引是將n個列組合成一個索引
- 應用場景::頻繁的同時使用n列來進行查詢,如:where n1 = 'alex' and n2 = 666。
- 表中建立組合索引
-
create table tb4( id int not null , name varchar(50) not null, age int not null, index idx_name_age (name,age) )
-
- 單獨建立
-
create index idx_name_age on tb4(name,age);
-
- 索引應用場景
-
舉個例子來說,比如你在為某商場做一個會員卡的系統。 這個系統有一個會員表 有下列欄位: 會員編號 INT 會員姓名 VARCHAR(10) 會員身份證號碼 VARCHAR(18) 會員電話 VARCHAR(10) 會員住址 VARCHAR(50) 會員備註資訊 TEXT 那麼這個 會員編號,作為主鍵,使用 PRIMARY 會員姓名 如果要建索引的話,那麼就是普通的 INDEX 會員身份證號碼 如果要建索引的話,那麼可以選擇 UNIQUE (唯一的,不允許重複)
-
- 普通索引
-
聚合索引和輔助索引
-
資料庫中的B+樹索引可以分為聚集索引和輔助索引.
-
聚集索引:表中資料按主鍵B+樹存放,葉子節點直接存放整條資料,每張表只能有一個聚集索引。
-
當你定義一個主鍵時,InnnodDB儲存引擎則把它當做聚集索引。
-
如果你沒有定義一個主鍵,則InnoDB定位到第一個唯一索引,且該索引的所有列值均飛空的,則將其當做聚集索引。
-
如果表沒有主鍵或合適的唯一索引INNODB會產生一個隱藏的行ID值6位元組的行ID聚集索引。
-
輔助索引:(也稱非聚集索引)是指葉節點不包含行的全部資料,葉節點除了包含鍵值之外,還包含一個書籤連線,通過該書籤再去找相應的行資料。
-
-
何時使用聚集索引或非聚集索引
-
-
測試索引
-
建立表
-
CREATE TABLE userInfo( id int NOT NULL, name VARCHAR(16) DEFAULT NULL, age int, sex char(1) not null, email varchar(64) default null )ENGINE=MYISAM DEFAULT CHARSET=utf8;
-
-
建立儲存過程,插入資料
-
delimiter$$ CREATE PROCEDURE insert_user_info(IN num INT) BEGIN DECLARE val INT DEFAULT 0; DECLARE n INT DEFAULT 1; -- 迴圈進行資料插入 WHILE n <= num DO set val = rand()*50; INSERT INTO userInfo(id,name,age,sex,email)values(n,concat('alex',val),rand()*50,if(val%2=0,'女','男'),concat('alex',n,'@qq.com')); set n=n+1; end while; END $$ delimiter;
-
-
呼叫儲存過程,插入500萬條資料
-
call insert_user_info(5000000);
-
-
修改引擎為INNODB
-
ALTER TABLE userinfo ENGINE=INNODB;
-
-
測試索引
-
沒索引查詢速度
-
SELECT * FROM userinfo WHERE id = 4567890;
-
注意:無索引情況,mysql根本就不知道id等於4567890的記錄在哪裡,只能把資料表從頭到尾掃描一遍,此時有多少個磁碟塊就需要進行多少IO操作,所以查詢速度很慢.
-
- 在表中已經存在大量資料的前提下,為某個欄位段建立索引,建立速度會很慢
-
CREATE INDEX idx_id on userinfo(id);
-
- 在索引建立完畢後,以該欄位為查詢條件時,查詢速度提升明顯
-
select * from userinfo where id = 4567890;
-
- 注意
-
1.mysql先去索引表裡根據b+樹的搜尋原理很快搜索到id為4567890的資料,IO大大降低,因而速度明顯提升
2.我們可以去mysql的data目錄下找到該表,可以看到新增索引後該表佔用的硬碟空間多了
3.如果使用沒有新增索引的欄位進行條件查詢,速度依舊會很慢(如圖:)
-
-
-
-
正確使用索引
-
資料庫表中新增索引後確實會讓查詢速度起飛,但前提必須是正確的使用索引來查詢,如果以錯誤的方式使用,則即使建立索引也會不奏效。即使建立索引,索引也不會生效:
-
#1. 範圍查詢(>、>=、<、<=、!= 、between...and) #1. = 等號 select count(*) from userinfo where id = 1000 -- 執行索引,索引效率高 #2. > >= < <= between...and 區間查詢 select count(*) from userinfo where id <100; -- 執行索引,區間範圍越小,索引效率越高 select count(*) from userinfo where id >100; -- 執行索引,區間範圍越大,索引效率越低 select count(*) from userinfo where id between 10 and 500000; -- 執行索引,區間範圍越大,索引效率越低 #3. != 不等於 select count(*) from userinfo where id != 1000; -- 索引範圍大,索引效率低 #2.like '%xx%' #為 name 欄位新增索引 create index idx_name on userinfo(name); select count(*) from userinfo where name like '%xxxx%'; -- 全模糊查詢,索引效率低 select count(*) from userinfo where name like '%xxxx'; -- 以什麼結尾模糊查詢,索引效率低 #例外: 當like使用以什麼開頭會索引使用率高 select * from userinfo where name like 'xxxx%'; #3. or select count(*) from userinfo where id = 12334 or email ='xxxx'; -- email不是索引欄位,索引此查詢全表掃描 #例外:當or條件中有未建立索引的列才失效,以下會走索引 select count(*) from userinfo where id = 12334 or name = 'alex3'; -- id 和 name 都為索引欄位時, or條件也會執行索引 #4.使用函式 select count(*) from userinfo where reverse(name) = '5xela'; -- name索引欄位,使用函式時,索引失效 #例外:索引欄位對應的值可以使用函式,我們可以改為一下形式 select count(*) from userinfo where name = reverse('5xela'); #5.型別不一致 #如果列是字串型別,傳入條件是必須用引號引起來,不然... select count(*) from userinfo where name = 454; #型別一致 select count(*) from userinfo where name = '454'; #6.order by #排序條件為索引,則select欄位必須也是索引欄位,否則無法命中 select email from userinfo ORDER BY name DESC; -- 無法命中索引 select name from userinfo ORDER BY name DESC; -- 命中索引 #特別的:如果對主鍵排序,則還是速度很快: select id from userinfo order by id desc;
-
-
注意事項
-
1. 避免使用select * 2. 其他資料庫中使用count(1)或count(列) 代替 count(*),而mysql資料庫中count(*)經過優化後,效率與前兩種基本一樣. 3. 建立表時儘量時 char 代替 varchar 4. 表的欄位順序固定長度的欄位優先 5. 組合索引代替多個單列索引(經常使用多個條件查詢時) 6. 使用連線(JOIN)來代替子查詢(Sub-Queries) 7. 不要有超過4個以上的表連線(JOIN) 8. 優先執行那些能夠大量減少結果的連線。 9. 連表時注意條件型別需一致 10.索引雜湊值不適合建索引,例:性別不適合
-
-
大資料分頁優化
-
大資料分頁
-
select * from userinfo limit 3000000,10;
-
-
優化方案:
-
一. 簡單粗暴,就是不允許檢視這麼靠後的資料,比如百度就是這樣的
-
最多翻到72頁就不讓你翻了,這種方式就是從業務上解決;
-
二.在查詢下一頁時把上一頁的行id作為引數傳遞給客戶端程式,然後sql就改成了
-
select * from userinfo where id>3000000 limit 10;
-
這條語句執行也是在毫秒級完成的,id>300w其實就是讓mysql直接跳到這裡了,不用依次在掃描全面所有的行。
-
如果你的table的主鍵id是自增的,並且中間沒有刪除和斷點,那麼還有一種方式,比如100頁的10條資料
-
select * from userinfo where id>100*10 limit 10;
-
-
-
三.最後第三種方法:延遲關聯
-
我們在來分析一下這條語句為什麼慢,慢在哪裡。
-
select * from userinfo limit 3000000,10;
-
-
玄機就處在這個 * 裡面,這個表除了id主鍵肯定還有其他欄位 比如 name age 之類的,因為select * 所以mysql在沿著id主鍵走的時候要回行拿資料,走一下拿一下資料;
-
如果把語句改成 :
-
select id from userinfo limit 3000000,10;
-
-
你會發現時間縮短了一半;然後我們在拿id分別去取10條資料就行了;
-
語句就改成這樣了:
-
select table.* from userinfo inner join ( select id from userinfo limit 3000000,10 ) as tmp on tmp.id=userinfo.id;
-
-
-
這三種方法最先考慮第一種 其次第二種,第三種是別無選擇
-
-