目錄
MySQL 索引
1. 什麼是索引
- 將數據進行排序整理的過程就稱爲索引。根據索引去查,可以提高效率。索引的應用場景:
- 新開發的系統:一般是在系統設計階段, 可能會在需求文檔中說明哪些字段需要創建索引。
- 系統後期維護:如果客戶反饋某些數據搜索的速度比較慢,可以考慮客戶搜索的列適不適合做索引,如果可以就建立索引。
2. MySQL 索引分類
- 主鍵索引(主鍵約束)
有兩個功能:主鍵約束 + 提高查詢效率 - 唯一索引(唯一約束)
有兩個功能:唯一約束 + 提高查詢效率 - 普通索引
僅提高查詢效率的功能 - 組合(聯合)索引
多個字段組成索引 - 全文索引
在關係型數據庫中用的不多,一般用在 solr、es 中 - hash 索引
根據 key-value 效率非常高,但範圍查詢能力很差
3. MySQL 索引語法
a. 創建索引
i. 直接創建
-- 創建普通索引
create index 索引名 on 表名(字段);
-- 創建唯一索引
create unique index 索引名 on 表名(字段);
-- 創建普通組合索引
create index 索引名 on 表名(字段1,字段2);
-- 創建唯一組合索引
create unique index 索引名 on 表名(字段1,字段2);
- 代碼示例:
-- 創建學生表
CREATE TABLE student(
id INT,
`name` VARCHAR(32),
telephone VARCHAR(11)
);
-- name字段適合設置普通索引
CREATE INDEX name_idx ON student(`name`);
-- telephone適合設置unique索引
CREATE UNIQUE INDEX telephone_uni_idx ON student(telephone);
ii. 修改表時指定(不需要指定名字)
-- 添加一個主鍵,這意味着索引值必須是唯一的,且不能爲NULL
alter table 表名 add primary key(字段); --默認索引名:primary
-- 添加唯一索引(除了NULL外,NULL可能會出現多次)
alter table 表名 add unique(字段); -- 默認索引名:字段名
-- 添加普通索引,索引值可以出現多次。
alter table 表名 add index(字段); -- 默認索引名:字段名
-- 添加聯合普通索引
alter table 表名 add index(字段1,字段2);
-- 添加聯合唯一索引
alter table 表名 add unique(字段1,字段2);
- 代碼示例:
-- 指定id爲主鍵索引
ALTER TABLE student ADD PRIMARY KEY(id);
-- 指定name爲普通索引
ALTER TABLE student ADD INDEX(`name`);
-- 指定telephone爲唯一索引
ALTER TABLE student ADD UNIQUE(telephone);
-- 指定name和telephone爲聯合唯一索引
ALTER TABLE student ADD UNIQUE(`name`,telephone);
iii. 創建表時指定(推薦)
- 之前的都屬於亡羊補牢的做法,推薦在創建表時指定。
-- 創建教師表
CREATE TABLE teacher(
id INT PRIMARY KEY AUTO_INCREMENT, -- 主鍵索引
`name` VARCHAR(32),
telephone VARCHAR(11) UNIQUE, -- 唯一索引
sex VARCHAR(5),
birthday DATE,
INDEX(`name`) -- 普通索引
);
b. 刪除索引
-- 直接刪除
drop index 索引名 on 表名;
-- 修改表時刪除 【掌握】
alter table 表名 drop index 索引名;
- 代碼示例:
-- 刪除name普通索引
DROP INDEX name_idx ON student;
-- 刪除telephone唯一索引
ALTER TABLE student DROP INDEX telephone_uni_idx;
4. 千萬表記錄索引效果演示
- 用到了 MySQL 的性能優化與慢查詢日誌 中的千萬條記錄的數據庫。
a. 先測試沒有索引情況下的查詢
-- 1.指定id查詢
select * from user where id = 8888888;
-- 2.指定username精準查詢
select * from user where username = 'jack1234567';
-- 3.指定email模糊查詢
select * from user where email like 'jack1234567%';
b. 給這三個字段添加索引
-- 指定id爲主鍵索引
ALTER TABLE USER ADD PRIMARY KEY(id);
-- 指定username爲普通索引
ALTER TABLE USER ADD INDEX(username);
-- 指定email爲唯一索引
ALTER TABLE USER ADD UNIQUE(email);
c. 再測試有索引情況下的查詢
-- 1.指定id查詢
select * from user where id = 8888888;
-- 2.指定username精準查詢
select * from user where username = 'jack1234567';
-- 3.指定email模糊查詢
select * from user where email like 'jack1234567%';
> 如何查詢聯合索引?
- 聯合索引是由多個字段組成的索引,一般不會使用。一般是使用所有字段查詢,如果是查詢單個字段會出現以下情況:
- 查詢時使用聯合索引的一個字段,如果這個字段是聯合索引中所有字段的第一個,那就會用到索引,否則就無法使用到索引。例如,創建聯合索引
create index indexname on 表名 (字段 A,字段 B,字段 C,字段 D);
,當僅使用字段 A 查詢時,索引 indexname 就會被使用到;如果僅使用字段 B 或字段 C 或字段 D 查詢,則索引 indexname 不會被用到。 這個規則在 Oracle 和 MySQL 數據庫中均成立。
5. 索引的優缺點
a. 優點
- 減少磁盤 IO,提高查詢效率。
b. 缺點
- 索引佔用磁盤空間。
- 在進行增刪改時,索引的維護會增加成本,可能會降低服務器性能。
6. 索引創建原則
- 字段內容可識別度不能低於 70%
- 經常使用 where 條件搜索的字段
- 經常使用表連接的字段(內連接、外連接)
- 經常排序的字段 order by
- 注意:索引本身會佔用磁盤空間,不是所有的字段都適合增加索引。
7. 常見索引失效情況
- 使用 like 模糊匹配,% 通配符在最左側使用時,默認進行全表掃描;% 通配符在最右側使用時,索引不會失效。
- 儘量避免使用 or,如果條件有一個沒有索引,那麼會進行全表掃描;使用 or 關鍵字時,如果兩邊的條件都是索引,索引不會失效。
- 在索引列上進行計算,不知道哪個 id + 1 = 88,注意不是表達式計算。
- 使用 !=、 not in、is not null 時,取反時會全表掃描。
-- 1.使用 like 模糊匹配,% 通配符在最左側使用時
select * from user where email like '%jack1234567%';
-- 2.儘量避免使用 or,如果條件有一個沒有索引,那麼會進行全表掃描
select * from user where id = 88 or sex = 'male';
-- 3.在索引列上進行計算,不知道哪個 id + 1 = 88
select * from user where id + 1 = 88;
-- 注意不是表達式計算:id = 88 - 1
-- 4.使用 !=、 not in、is not null 時,取反時會全表掃描
select * from user where username != 'jack12';
- 範圍查詢不會讓索引失效,下文會解釋。
8. 索引的數據結構
a. 概述
- 索引是幫助 MySQL 高效獲取排好序的數據結構。爲什麼使用索引後查詢效率提高很多呢?
- 在沒有索引的情況下執行一條 SQL 語句,表進行全局遍歷,磁盤尋址(注意邏輯上相鄰的記錄在磁盤上也並不是一定物理相鄰的)。
select * from user where col1=6;
- 爲了加快的查找效率,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉樹查找快速獲取到相應數據。
select * from user where col2=89;
b. 索引的數據結構
- 二叉樹:左邊的子節點比父節點小,右邊的子節點比父節點大。
如果是數據自增順序插入,二叉樹退變爲鏈表。
- 紅黑樹:平衡二叉樹(左旋、右旋)。
查詢記錄需要 ≈ 23 次(即樹的深度、高度)IO。
內存中效率非常高,但是在磁盤中讀取要考慮到 IO 次數,所以紅黑樹不適合數據庫使用。
- BTree:多路平衡搜索樹。
BTree 增加了樹的寬度,一個節點可以存儲多個元素(一個元素由索引(bigint = 8 字節,即 8 B)、指針(6 字節,即 6 B)、數據(data = 1 KB)組成)。而一個節點推薦爲 16 KB(因爲內存讀取硬盤的內容一次讀取一頁(1 page = 4 KB)),因此一個節點能存儲 15 條記錄。
查詢記錄需要 次(即樹的深度、高度)IO。
- B+Tree:優化 BTree,非葉子節點只存儲 索引+指針、葉子節點存儲 索引 + 數據(根據不同的存儲引擎,可能是數據,也可能是數據的指針地址)
索引 8 字節,指針 6 字節,一個節點 16 KB,所以一個非葉子節點能存儲 個元素,因此第 2 次 IO 大概能 存儲 條記錄;數據 1 KB,一個節點 16 KB,所以一個葉子節點能存儲 16 條記錄,因此第 3 次 IO 大概能 存儲 條記錄。
控制 IO 數爲 3 次。
所有數據都在葉子節點上,並且已排好序,支持範圍查詢。所有葉子節點就是一個雙向鏈表,詳見下文。MySQL 默認將所有記錄的根節點放到內存中,所以拿出數據一般只會經過 2 次 IO。
- Hash:通過散列算法查找,每一條記錄對應一個地址,只經歷一次 IO
不支持範圍查詢。
MySQL 5.7 之前是有 Hash 索引的,5.7 之後即使指定了 Hash 索引,底層仍默認使用 B+Tree 實現。
> 數據結構學習網站
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
c. MySQL 中的 B+Tree
-- 查看MySQL索引節點大小
show global status like 'innodb_page_size';
- MySQL 中的 B+Tree 索引結構示意圖:
每次讀取一個磁盤塊(即節點,16 KB),每一個索引都有一個指針。
9. 數據庫的存儲引擎
- 上文只是講到邏輯數據結構,保存到物理磁盤的方式即是數據庫的存儲引擎。
- MySQL 存儲引擎的不同,那麼索引文件保存的方式也有所不同,常見的有二種存儲引擎 MyISAM 和 InnoDB。
- 注意:每張表都對應一個存儲引擎,而不是整個數據庫對應一個存儲引擎。
- 查詢表的數據庫引擎類型:
a. MyISAM(非聚集索引)
- MyISAM 是 MySQL 5.5 版本之前默認存儲引擎。
- 特點:不支持事務,不支持外鍵約束。
CREATE DATABASE indexdemo;
USE indexdemo;
-- 創建 myisam存儲引擎表
CREATE TABLE tab_myisam(
id INT,
`name` VARCHAR(32)
)ENGINE=MYISAM;
- 每一個數據庫對應一個文件夾。
- 基於當前數據庫的文件夾內多出 3 個文件:
xxx.frm
表結構文件
xxx.MYD
表數據文件
xxx.MYI
表索引文件
- MyISAM 非聚集索引,葉子節點沒有數據,只存儲索引和數據的指針地址。
- 如果表中存儲的數據都是大字段時(1 個記錄不是 1 KB,而是 10 KB),即使又多了一次 IO 找數據,MyISAM 查詢速度也比 InnoDB 更快。但是 MyISAM 不支持事務,而且大字段數據一般放到非關係型數據庫(如 Mongo 等),所以 MyISAM 已經被 InnoDB 取代了。
b. InnoDB(聚集索引)
- InnoDB 是 MySQL 5.5 版本之後默認存儲引擎。
- 特點:支持事務,支持外鍵約束。
-- 創建 innodb存儲引擎表
CREATE TABLE tab_innodb(
id INT,
`name` VARCHAR(32)
)ENGINE = INNODB;
- 基於當前數據庫的文件夾內只多出 2 個文件:
xxx.frm
表結構文件
xxx.ibd
表數據和索引文件
- InnoDB 存儲引擎必須要設置主鍵(整型 int/Integer),且自增類型。
i. InnoDB 爲什麼要設置主鍵?
- InnoDB 的索引與數據是在一起的,如果沒有指定索引,MySQL 會根據行號自動生成 rowID 索引,浪費磁盤空間;因爲也沒有用到 rowID 索引,所以也沒有提高查詢效率。
ii. InnoDB 爲什麼要設置主鍵爲整型?
- 如果數據不是整型,在加入新數據時,數據裂變的位置可能在中間,導致整個後續的數據收到影響。
- 查詢索引的時候,整型的比較速度更快。