本文口味:番茄炒蛋,預計閱讀:10分鍾。
博客又停更了兩個月,在這期間,對人生和世界多了許多思考。在人生的不同階段,會對生活和世界有着不一樣的認知,而認知的改變也會直接反應在行為模式之中。
對於生活的思考心得也會在之后的時間里,慢慢分享給大家,一方面是對自己心路歷程的記錄和總結,另一方面也希望能給遇到同樣問題或疑惑的朋友以幫助。目前生活已經慢慢調整到我想要的樣子,博客寫作也該繼續起航了。
一、說明
Mysql是最常用的關系型數據庫,而索引則是Mysql調優中最關心的部分,設計一個好的索引並寫出合適的sql,就能將查詢速度大大提升。從本篇開始,將會對Mysql中的索引進行深入淺出的介紹,從索引的簡介、類別、使用姿勢到索引的原理,最后到索引實戰。希望通過本系列的文章,能讓你對mysql中的索引有一個更深入的認識。
以下是本文大綱:
二、什么是索引
索引是存儲引擎用於快速查找記錄的一種數據結構。
emm,用人話說,如果把Mysql比作一本書的話,索引就是書的目錄,根據目錄便能很快找到需要的信息所在的頁面,如果沒有目錄的話,想要查找想要的信息就只能一頁一頁翻了。
比如下面這樣一條簡單的sql:
SELECT id,name,course,grade FROM t_grade WHERE name = 'Frank';
如果沒有添加索引的話,只能從最小記錄開始依次遍歷mysql中的記錄,然后對比每條記錄是否符合搜索條件。如果表中的數據量不大(十萬級別以下),耗時其實也還好,畢竟目前來說,CPU效率已經很高了。但這樣其實是對CPU的一種浪費,就好比開着跑車在泥濘的鄉村小路上駕駛,完全無法發揮它應有的性能。而索引便是這樣一條康庄大道,有了索引,才能充分發揮mysql引擎的性能,讓你的sql跑車風馳電掣。
三、索引的優缺點
對於大部分事物而言,通常存在其對立面的,有好的一面,就會有壞的一面,就像質量好的東西通常價格高,便宜的東西通常質量差,索引也是如此。
使用索引的優點顯而易見:
- 可以大大加快數據檢索效率。
- 可以加速表與表之間的連接。
- 可以通過唯一索引的創建,保證數據的唯一性。
- 可以顯著減少分組與排序的時間。
總而言之,用一個字來總結,就是快。
使用索引的缺點也是需要考慮的:
- 索引的創建和維護需要時間成本。表中的數據量越大,插入或刪除數據時,調整索引所需要的時間就越長。
- 索引需要單獨存儲,占用磁盤空間,如果設置了大量的索引,占用的空間甚至比記錄本身更大。
- 在對數據進行增、刪、改時,需要同時更新索引中的數據,因此會影響增刪改的速度。
所以使用索引並不是百利而無一害,使用不當甚至可能造成刪庫跑路的慘劇【手動滑稽】。但當你了解它的原理,掌握了索引的真諦,它就會成為你的神兵利器,讓你在mysql開發中所向披靡。
四、索引的分類以及創建姿勢
索引可分為普通索引、唯一索引、主鍵索引、組合索引、全文索引。看起來好像很多很復雜,但其實並非如此,且聽我慢慢道來。
普通索引
,名字中就透露出它普通的氣質,也就是最常見的索引。
如何創建一個普通索引呢?其實很簡單,如果是在DDL中創建索引,可以這樣使用:
CREATE TABLE `t_grade` (
id BIGINT(20) COMMENT '主鍵id',
name VARCHAR(30) COMMENT '姓名',
course INT COMMENT '課程,1-語文,2-數學,3-英語,4-物理',
grade DECIMAL(5,2) COMMENT '成績',
KEY idx_name(`name`)
)ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
這樣就為"name"列創建了一個名為"idx_name"的普通索引。通用的創建方式為:
KEY 索引名 (`列名`)
如果是為一張已經創建好的表添加一個普通索引,那么可以這樣:
ALTER TABLE `t_grade` ADD KEY idx_name(`name`);
你可能會說,“不是用index關鍵字來創建索引的嗎”,別急別急,其實它們的效果是一樣的。
主鍵索引
,一看就是很關鍵的角色,沒錯,每張表都會有且只有一個主鍵索引,即使沒有顯式的創建主鍵索引的話,也會自動創建一個隱藏的主鍵索引。
這么重要的索引,用的關鍵字肯定也得不一樣才行,創建主鍵索引的關鍵字是PRIMARY KEY
,在DDL中添加主鍵索引的姿勢為:
CREATE TABLE `t_grade` (
id BIGINT(20) COMMENT '主鍵id',
name VARCHAR(30) COMMENT '姓名',
course INT COMMENT '課程,1-語文,2-數學,3-英語,4-物理',
grade DECIMAL(5,2) COMMENT '成績',
PRIMARY KEY (`id`)
)ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
因為主鍵只能有一個,所以不需要添加主鍵名。通用的添加方式為:
PRIMARY KEY (`列名`)
如果是為已創建好的表添加主鍵索引,那么可以這樣:
ALTER TABLE `t_grade` ADD PRIMARY KEY (`id`);
唯一索引
,顧名思義,就是“唯一”的索引,被添加到索引中的列的值必須是唯一的,如果向數據表中插入一條已存在的唯一索引字段記錄,就會報錯。
定義唯一索引的關鍵字為 UNIQUE KEY
。在DDL中添加唯一索引的姿勢為:
CREATE TABLE `t_grade` (
id BIGINT(20) COMMENT '主鍵id',
name VARCHAR(30) COMMENT '姓名',
course INT COMMENT '課程,1-語文,2-數學,3-英語,4-物理',
grade DECIMAL(5,2) COMMENT '成績',
UNIQUE KEY uk_name (`name`)
)ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
唯一索引的通用添加方式為:
UNIQUE KEY 索引名 (`列名`)
為已創建好的表添加唯一索引:
ALTER TABLE `t_grade` ADD UNIQUE KEY uk_name (`name`);
組合索引
,又叫聯合索引,便是將兩個或者多個字段組合在一起的索引,好像跟沒說一樣= =
看一個栗子就知道了。
CREATE TABLE `t_grade` (
id BIGINT(20) COMMENT '主鍵id',
name VARCHAR(30) COMMENT '姓名',
course INT COMMENT '課程,1-語文,2-數學,3-英語,4-物理',
grade DECIMAL(5,2) COMMENT '成績',
KEY idx_name_corse (`name`,`course`)
)ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
同樣是使用key
關鍵字,在索引名后添加多個字段名即可。這里有一點需要注意的是,字段排列是有順序的。舉例說明,下面這兩個索引是不一樣的:
ALTER TABLE `t_grade` ADD KEY idx_name_course (`name`,`course`);
ALTER TABLE `t_grade` ADD KEY idx_name_course (`course`,`name`);
索引的匹配遵循“左綴匹配原則”,舉個栗子說明,如果創建的組合索引是
ALTER TABLE `t_grade` ADD KEY idx_name_course (`name`,`course`);
那么下面語句將能命中這個組合索引。
SELECT * FROM `t_grade` WHERE name = 'Frank';
而下面這個語句將無法命中索引:
SELECT * FROM `t_grade` WHERE course = 1;
因為在組合索引中,索引中的記錄是先按照前一個字段排序,然后再根據后一個字段排序的,所以如果直接使用組合索引中的第二個字段查詢時,查詢索引對索引記錄進行遍歷,遍歷完成之后還需要回溯到聚簇索引中獲取完整記錄,這樣反而更耗時間,所以sql優化器會選擇直接對記錄進行遍歷。
如果你還不清楚索引的結構以及聚簇索引是什么,不要着急,后面的文章里會有詳細的介紹。
聯合唯一索引
,便是將多個字段組合起來形成一個唯一鍵,舉個栗子:
先刪除所有索引,然后添加兩條記錄:
INSERT INTO `t_grade` (`id`, `name`, `course`, `grade`) VALUES(1, 'Frank', 1, 100);
INSERT INTO `t_grade` (`id`, `name`, `course`, `grade`) VALUES(2, 'Frank', 1, 95);
這樣就能插入兩條記錄了。
然后刪掉這兩條記錄,創建一個聯合唯一索引:
ALTER TABLE `t_grade` ADD UNIQUE KEY idx_name_course (`name`,`course`);
然后再來執行一下上面的sql:
這時候,就會得到一個錯誤提示,因為將字段name
和course
創建了聯合唯一索引,所以這兩個字段的組合值必須是唯一的,如果要插入的記錄的這兩個字段組合值已經存在,那么就會拋出異常。
最后一個是比較復雜的索引:全文索引
,由於其復雜性,這里只簡單的介紹它的創建姿勢。
CREATE TABLE `t_article`(
id BIGINT COMMENT '文章id',
title VARCHAR(200) COMMENT '文章標題',
content TEXT COMMENT '文章內容',
FULLTEXT (title, content)
)ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
或者給現有表添加全文索引:
ALTER TABLE `t_article` ADD FULLTEXT KEY fidx_title_content (title,content) WITH PARSER ngram;
想要使用全文索引查詢,則需要使用MATCH關鍵字。
SELECT * FROM `t_article` WHERE MATCH(title, content) AGAINST('查詢字符串');
當然,如果想要使用全文索引,需要確認mysql的版本號在5.7以上,否則無法在innodb引擎上使用全文索引的中文檢索插件ngram。
五、索引使用前后對比
為了更直觀的看出索引的優缺點,我們可以來對數據表添加索引前后執行相同sql的耗時來看出對比,這里僅進行簡單的比較,沒有使用性能測試。
先來創建一個數據表:
CREATE TABLE `t_grade` (
id BIGINT(20) COMMENT '主鍵id',
name VARCHAR(30) COMMENT '姓名',
course INT COMMENT '課程,0-化學,1-語文,2-數學,3-英語,4-物理',
grade DECIMAL(5,2) COMMENT '成績'
)ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
然后插入一百萬條數據:
public void batchInsert(){
long timeMillis = System.currentTimeMillis();
System.out.println("開始插入數據");
for (int i = 1; i < 1000000; i++) {
GradeDO gradeDO = new GradeDO((long) i, randomName(), random.nextInt(5), BigDecimal.valueOf(random.nextDouble() * 100));
gradeMapper.insert(gradeDO);
}
System.out.println("插入一百萬條記錄耗時:" + ( System.currentTimeMillis() - timeMillis) / 1000.0 );
}
輸出如下:
開始插入數據
插入一百萬條記錄耗時:1507.102
現在是沒有索引的狀態,開始進行插入測試:
public void batchInsert(){
long timeMillis = System.currentTimeMillis();
System.out.println("開始插入數據");
for (int i = 1000000; i < 1010000; i++) {
GradeDO gradeDO = new GradeDO((long) i, randomName(), random.nextInt(5), BigDecimal.valueOf(random.nextDouble() * 100));
gradeMapper.insert(gradeDO);
}
System.out.println("插入一萬條記錄耗時:" + ( System.currentTimeMillis() - timeMillis) / 1000.0 );
}
輸出如下:
開始插入數據
插入一萬條記錄耗時:15.681
然后進行查詢測試。
@Test
void testQuery() {
long timeMillis = System.currentTimeMillis();
System.out.println("開始查詢");
for (int i = 0; i < 100; i++) {
Integer id = random.nextInt(1000000);
GradeDO gradeDO = gradeMapper.selectById(id);
}
System.out.println("一百次查詢耗時:" + ( System.currentTimeMillis() - timeMillis) / 1000.0 );
}
輸出如下:
開始查詢
一百次查詢耗時:51.658
接下來,為id列創建一個主鍵,並為name字段創建一個普通索引。
再插入一萬條記錄:
開始插入數據
插入一萬條記錄耗時:17.465
然后進行查詢測試。
開始查詢
一百次查詢耗時:0.191
可以看出,在有單個索引的情況下,創建記錄耗時略長於無索引的情況,當字段數量和索引數量增加時,這種差距將會增大。查詢效率可以清晰的看出,這里添加了索引之后,大大的縮減了查詢的耗時,當然,這里主要是聚簇索引的功勞。
六、總結
索引是mysql中十分重要的一個特性,使用好它就能讓你的sql如虎添翼。簡單來說,索引一方面可以大大提升查詢性能,另一方面也會占用時間和空間成本,因此索引的選擇也是一門學問。索引有很多種類型,不同類型的索引有着不同的特性,因此只有了解了它們各自的特性才能正確使用它們。
關於索引的簡介就先介紹到這里了,后面會對索引的原理進行進一步深入的介紹,讓你不僅知道怎么使用索引,而且還能知道為什么要這樣使用索引。
如果本文對你有幫助,不要吝嗇你的點贊哦。也歡迎關注我的公眾號進行留言交流。