【宇哥帶你玩轉MySQL】索引篇(一)索引揭祕,看他是如何讓你的查詢效能指數提升的
場景復現,一個索引提高600倍查詢速度?
首先準備一張books表
create table books( id int not null primary key auto_increment, name varchar(255) not null, author varchar(255) not null, created_at datetime not null default current_timestamp, updated_at datetime not null default current_timestamp on update current_timestamp )engine=InnoDB;
然後插入100w條資料
drop procedure prepare_data; delimiter // create procedure prepare_data() begin declare i int; set i = 0; while i < 1000000 do insert into books(name, author) value (concat('name', i), concat('author', i)); set i = i + 1; end while; end // delimiter ; call prepare_data();
那麼問題來了,現在我們要在這100w本書中找到name為name9000000的書,來看看大概需要多久。
set profiling = 1; select * from books where name = 'name900000'; show profiles; set profiling = 0;
(圖一)
大概在400ms左右,我不是很滿意這個查詢的速度,那麼如何提升查詢速度呢?建個索引吧!
create index idx_books_name on books(name);
建立索引後我們再看看查詢的速度
set profiling = 1; select * from books where name = 'name900000'; show profiles; set profiling = 0;
(圖二)
可以發現,只需要6ms,索引為我們帶來600倍的速度提升,那麼為什麼索引可以帶來這麼大的查詢速度提升呢?
索引揭祕
想象一下, 現在我們有100w條資料,如何快速的通過name找到符合條件的資料
如果這100w條資料是按照name有序排列的,那麼我們就可以使用二分搜尋,這樣每次可以排除一半資料。那麼100w資料最多隻需要查詢~= 20次就可以找到
執行過程型別下圖
(圖三)
這裡可以發現一個問題,在比較過程中,我們只用到了name欄位,但是卻需要把name和其他欄位一起載入到記憶體,這樣顯然會浪費很多記憶體,所以我們可以修改結構為下圖
(圖四)
我們把原來表中的name和id欄位進行一份複製形成了一個新的表,這樣的話,當我們根據name來查詢資料時,只需要把name和id兩個資料載入到記憶體就行了,當找到資料後再根據id找到對應行的其他資料。
其實這個冗餘表就是我們常說的索引,索引表會把我們指定的列的資料進行拷貝形成一個新的表,這個表中的資料是有序排列的,如果有多列,則是按宣告的前後關係依次比較。
例如,有一個商品表items,其中有名稱、價格、建立日期等欄位
create table items ( id int not null primary key auto_increment, title varchar(255) not null, price decimal(12,2) not null, created_at datetime not null, updated_at datetime not null ) engine = innodb;
(圖五)
由於使用者喜歡按價格和建立時間查詢商品,我們可以建立一個idx_items_price_created_at(price, created_at)的索引,那麼他的資料結構就是這樣的:先按price排序,再按created_at排序,如圖六
(圖六)
通過圖六的資料結構我們可以學習到索引使用的一個原則和一個優化
一個原則:最左匹配原則:如果要觸發索引使用,需要按索引欄位的宣告順序來新增條件過濾
以items表中的idx_items_price_created_at索引使用舉例:
# sql1:price + created_at條件,可以使用索引 select * from items where price = "20" and created_at = '2020-01-04'; # sql2:created_at + price條件,可以使用索引,注意雖然此處查詢條件順序和索引順序不一樣,但其實mysql在執行sql前,會先對sql進行語法分析,最終的結果是和sql1一樣的。但是我不推薦這種寫法,因為對於看程式碼的人來說沒有sql1直觀。 select * from items where created_at = "2020-01-04" and price = "20"; # sql3:price 可以使用索引,因為索引表即使只考慮price欄位,順序也是有序的 select * from items where price = "20"; # sql4:crated_at 不可以使用索引,因為索引中如果只考慮craeted_at欄位,順序不能保證有序 select * from items where created_at = "2020-01-04";
一個優化:覆蓋索引:如果要查詢的欄位全在索引上,那麼不需要回表
以items表中的idx_items_price_created_at索引使用舉例:
# sql1:由於需要所有的欄位,該查詢在根據idx_items_price_created_at找到id後,還需要根據id再找items表中該條記錄的其他欄位的值 select * from items where price = "20" and created_at = '2020-01-04'; # sql2: 由於需要的欄位在索引上都有,該查詢只需要在idx_items_price_created_at索引表找到記錄直接返回即可 select price, created_at, id where price = "20" and created_at = '2020-01-04';
小結
通過本章學習,我們瞭解到索引其實就是一個有序排列的表,我們通過有序排列的優勢來加快查詢。也正是由於索引是有序排列的,如果想有效使用索引,我們就需要要遵循最左匹配原則。我們還了解到覆蓋索引,如果查詢的欄位全在索引上,可以減少一次回表查詢,利用該特性在大批量查詢時可以大幅度優化效能。
本章所講的內容全是以資料全在記憶體中為前提的,但是真實場景中資料都是在硬碟中儲存,如果一個表中的資料可能有好幾G,我們不可能把所有的資料都載入到記憶體然後進行二分搜尋,所以下一章會我們講一講索引和硬碟的關係。