Springboot整合第三方jar快速實現微信、支付寶等支付場景
MySql進階
Mysql
一、MySql邏輯架構
1. MySql邏輯架構介紹
和其它資料庫相比,MySQL有點與眾不同,它的架構可以在多種不同場景中應用併發揮良好作用。主要體現在儲存引擎的架構上,外掛式的儲存引擎架構將查詢處理和其它的系統任務以及資料的儲存提取相分離。這種架構可以根據業務的需求和實際需要選擇合適的儲存引擎。
-
連線層
最上層是一些客戶端和連線服務,包含本地sock通訊和大多數基於客戶端/服務端工具實現的類似於tcp/ip的通訊。主要完成一些類似於連線處理、授權認證、及相關的安全方案。在該層上引入了執行緒池的概念,為通過認證安全接入的客戶端提供執行緒。同樣在該層上可以實現基於SSL的安全連結。伺服器也會為安全接入的每個客戶端驗證它所具有的操作許可權。如,navicat -
服務層
第二層架構主要完成大多數的核心服務功能,如SQL介面,並完成快取的查詢,SQL的分析和優化及部分內建函式的執行。所有跨儲存引擎的功能也在這一層實現,如過程、函式等。在該層,伺服器會解析查詢並建立相應的內部解析樹,並對其完成相應的優化如確定查詢表的順序,是否利用索引等,最後生成相應的執行操作。如果是select語句,伺服器還會查詢內部的快取。如果快取空間足夠大,這樣在解決大量讀操作的環境中能夠很好的提升系統的效能。 -
引擎層
儲存引擎層,儲存引擎真正的負責了MySQL中資料的儲存和提取,伺服器通過API與儲存引擎進行通訊。不同的儲存引擎具有的功能不同,這樣我們可以根據自己的實際需要進行選取。後面介紹MyISAM和InnoDB -
儲存層
資料儲存層,主要是將資料儲存在運行於裸裝置的檔案系統之上,並完成與儲存引擎的互動。
2.資料庫引擎
通過show engines可以查詢資料庫支援的所有引擎, 我們常用的引擎有MyISAM和InnoDB。下面我們主要討論這兩個引擎。
查詢結果中
Engine引數指儲存引擎名稱:
Support引數說明MySQI是否支援該型別引擎;
Comment引數表示對該引擎的評論:
Transaction引數表示是否支援事務處理:
XA引數表示是否分散式交易處理的XA規範:
Savepoints引數表示是否支援儲存點,以方便事務的回滾操作
那麼怎麼看mysql當前預設的儲存引擎:
mysql> show variables like ‘%storage_engine%’;
上面顯示,預設儲存引擎是InnoDB,當前儲存引擎也是InnoDB。
那麼這兩個儲存引擎有什麼區別呢?
現在最常用的儲存引擎是InnoDB,它從MySQL 5.5.5版本開始成為了預設儲存引擎。
黑色背景為重點
擴充套件:
那麼阿里用什麼呢,在幾年前,阿里的確也用Mysql。
Percona 為 MySQL 資料庫伺服器進行了改進,在功能和效能上較 MySQL 有著很顯著的提升。該版本提升了在高負載情況下的 InnoDB 的效能、為 DBA 提供一些非常有用的效能診斷工具;另外有更多的引數和命令來控制伺服器行為。
該公司新建了一款儲存引擎叫xtradb完全可以替代innodb,並且在效能和併發上做得更好,
阿里巴巴大部分mysql資料庫其實使用的percona的原型加以修改。
AliSql+AliRedis
3.Sql執行順序
笛卡爾積大家都學過,複習一下,兩張表,T1 5條記錄,T2 8條記錄,執行select * from T1,T2,一共有多少條,很簡單5*8=40條。
這麼多條資料,肯定可以把資料都查出來,但是資料太亂了,因此需要where過濾,那麼有沒有一條公式能讓我們寫出標準的sql語句呢。
上圖就是寫Sql語句的規範,按照這個規範可以寫出好的sql語句。
不知道大家有沒有思考過一個問題,我們寫sql程式碼的時候,先select … 然後在from…,那麼計算機執行這條語句的時候也是這樣的順序嗎?
其實機讀的時候的順序是從from開始,因為計算機需要知道你要操作哪個表。
整理的魚刺圖如下。
二、事務
1. 事務概覽
事務由單獨單元的一個或多個SQL語句組成,在這個單元中,每個MySQL語句是相互依賴的。而整個單獨單元作為一個不可分割的整體,如果單元中某條SQL語句一旦執行失敗或產生錯誤,整個單元將會回滾。所有受到影響的資料將返回到事物開始以前的狀態;如果單元中的所有SQL語句均執行成功,則事物被順利執行。
三大關鍵點:
- 在MySQL中只有使用了Innodb 資料庫引擎的資料庫或表才支援事務。
- 事務處理可以用來維護資料庫的完整性,保證成批的SQL語句要麼全部執行,要麼全部不執行。
- 事務用來管理insert,update,delete語句,也即寫操作才用到事務
事務控制
在MySQL命令列的預設設定下,事務都是自動提交的既後面自動加一條COMMIT,即執行SQL語句後就會馬上執行COMMIT操作。因此要顯式地開啟一一個事務務須使用命令BEGIN或START,TRANSACTION或者執行命令SET AUTOCOMMIT=O,用來禁止使用當前會話的自動提交。
MYSQL事務處理主要有兩種方法:
- 用BEGIN, ROLLBACK, COMMIT來實現
(1)BEGIN或START TRANSACTION開始一個事務
(2)ROLLBACK事務回滾
(3)COMMIT事務確認 - 直接用SET來改變MySQL的自動提交模式:
(1)SET AUTOCOMMIT=O禁止自動提交
(2)SET AUTOCOMMIT=1開啟自動提交
2. 資料一致性
髒讀”、“不可重複讀”和“幻讀”,其實都是資料庫讀一致性問題,必須由資料庫提供定的事務隔離機制來解決。
資料庫的事務隔離越嚴格,併發副作用越小,但付出的代價也就越大,因為事務隔離實質上就是使事務在一定程度上“序列化”進行,這顯然與“併發”是矛盾的。
同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重複讀”和“幻讀”並不敏感,可能更關心資料併發訪問的能力。
Mysql支援4種事務隔離級別。
Mysql預設命事務隔離級別為: REPEATABLE READ
檢視當前的隔離級別兩種方法:
每啟動一個mysql程式,就會獲得一一個單獨的資料庫連線.每個資料庫連線都有一
個全域性變數@@tx_isolation,表示當前的事務隔離級別。
- 檢視當前的隔離級別: SELECT @ @tx isolation;
- 檢視當前資料庫的事務隔離級別: show variables like ‘tx_isolation’;
SELECT @ @tx isolation;
show variables like ‘tx_isolation’;
3. 程式碼演示資料一致性
SET SESSION TRANSACTION ISOL ATION LEVEL READ UNCOMMITTED; --未提交讀
SET SESSION TRANSACTION ISOL ATION LEVEL READ COMMITTED; --已提交讀
SET SESSION TRANSACTION ISOL ATION LEVEL REPEATABLE READ; --可重複讀
SET SESSION TRANSACTION ISOL ATION LEVEL SERIALIZABLE; --可序列號
未提交讀- READ UNCOMMITED
首先我們開啟兩個Sql視窗,分別將事務級別設定成READ UNCOMMITED
接著我們在左視窗插入一條資料(注意,沒有COMMIT),由於READ UNCOMMITED的特性,右視窗也可以得到資料,所以右視窗拿著資料去辦事了,在右視窗得到資料之後,左視窗rollback(此時仍未提交),rollback之後,左視窗的資料已經修改,但是右視窗卻已經拿著之前錯誤的資料辦事去了。
舉個例子,小明抄班長考試題,班長寫一個,小明抄一個,直到小明把所有的題都抄完了,交卷了,這時候班長髮現好多題都做錯了,要逐個修改答案。最後的結果就是,班長100分,小明掛科。
對於這種讀到錯誤的資料,叫做髒讀。
由於左邊視窗回滾了,所以剛剛插入的資料也沒有了,此時右邊視窗在select,會發現和上次查詢的結果不一樣,這就叫做不可重複讀。
舉個例子,小明抄班長考試題,小明問班長第一題選什麼,班長說A,過了一會班長修改了答案,小明此時閒的無聊問班長,為什麼第一題選A啊,班長說,我選的B啊,沒選A,此時小明蒙了,不知道這道題到底選A還是選B。
對於這種同一事務,多次查詢同一資源但是結果卻不同的現象,叫做不可重複讀。
以提交讀- READ COMMITED
首先我們開啟兩個Sql視窗,分別將事務級別設定成READ COMMITED
我們可以看到,左視窗插入了一條資料,但是沒提交。
右視窗查詢tx表,什麼也沒查到。
區別於 READ UNCOMMITED, 我們發現READ COMMITED沒有有髒讀現象,所以READ COMMITED避免了髒讀。
當左視窗COMMIT提交之後,右視窗就可以查到資料了,既事務提交之後,其他會話才能訪問此次事務的資料。
但是我們也發現,右視窗在同一事務對同一資源查詢卻查詢到了不同的結果,因此存在不可重讀的現象。
可重複讀- READ COMMITED
首先我們開啟兩個Sql視窗,分別將事務級別設定成REPEATABLE READ
右視窗首先查詢資料,發現表中存在一個數據,‘1’, 左視窗在一個事務中新增一條資料,‘2’,此時提交事務,右視窗再次查詢資料,發現還是1。
在同一事務中,查詢同一資源多次,得到的結果是一樣的,因此REPEATABLE READ可以避免不可重複讀。只有當前事務提交之後,再次查詢,才能查到更新後的資料。
但也正是這種現象,導致了左視窗在事務提交之前明明插入了一條資料,但是右視窗卻查不到,這就彷彿產生了幻覺,這種現象叫做幻讀。
髒讀也可以避免,就不演示了。
可序列化-SERIALIZABLE;
首先我們開啟兩個Sql視窗,分別將事務級別設定成SERIALIZABLE。
哎,世界上最難過的事情就是解決了舊的問題又會產生新的問題。
左視窗插入了一條資料,‘3’,此時還沒有提交事務,這時右視窗查詢,發現處於阻塞等待狀態。這時候大家可能猜到為什麼了。那就是冰糖胡蘆,吃了第一個才能吃第二個。
當左視窗COMMIT之後,右窗口才查出資料。很明顯,這樣的機制使得上述三種麻煩都不會產生,但是對於併發效率是極其低的。
三、join
1. join概覽
Join分為左連線,右連線,內連線,A表獨有,B表獨有,全連線,AB各有。
有點糊塗?沒關係,我們用例子來介紹Join。
參考Sql語句,所用引擎都為INNODB。
CREATE TABLE `tbl_dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`locAdd` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `tbl_emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `tbl_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO tbl_dept(deptName,locAdd) VALUES('RD',11);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('HR',12);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('MK',13);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('MIS',14);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('FD',15);
INSERT INTO tbl_emp(NAME,deptId) VALUES('z3',1);
INSERT INTO tbl_emp(NAME,deptId) VALUES('z4',1);
INSERT INTO tbl_emp(NAME,deptId) VALUES('z5',1);
INSERT INTO tbl_emp(NAME,deptId) VALUES('w5',2);
INSERT INTO tbl_emp(NAME,deptId) VALUES('w6',2);
INSERT INTO tbl_emp(NAME,deptId) VALUES('s7',3);
INSERT INTO tbl_emp(NAME,deptId) VALUES('s8',4);
INSERT INTO tbl_emp(NAME,deptId) VALUES('s9',51);
部門表,id,部門名,樓層
員工表,id, name, 部門id
分別對這些表進行插入操作。
查詢部門表
查詢員工表
簡單解釋下插入的表
在部門表中,一共有4個部門,ID為5的部門沒有被引用,因為他是一個特殊的部門,就像老闆的部門是8888一樣。
在員工表中,ID1-7為公司正式員工,ID為8的員工為試用員工,故分配臨時部門。
在演示之前,我們先對兩個表進行笛卡爾積查詢,發現
查詢部分
我們發現40條記錄,結果正常。
現在我們開始演示
2.內連線
問題:求出公司所有正式員工的資訊。
提示:部門表沒有被正式員工引用的部門和實習員工都不應該顯示出來,故求A∩B。
如圖所示
3.左連線
問題:展示出公司所有員工資訊。
提示:員工表除了正式員工外,試用員工也應該查詢出來,如果沒有對應部門,那部門資訊應該留空。故應該取員工表獨有部分,和A∩B,故就是求A表所有資訊。
如圖所示:
4.右連線
與左連線相反,不做解釋。
5.A表獨有
問題:求出沒有被分配正式部門的員工
提示:就是試用員工,他只被分配了一個臨時部門,實際上部門表並沒有他引用的部門。
如圖所示:
6.B表獨有
與A表獨有相反,不做解釋。
7.全連線
問題:求出AB兩表所有資訊。
提示:特殊部門和試用員工也要查出來。
如圖所示
咦?怎麼報錯了,看起來和圖上的語法一樣啊。是不是哪裡寫錯了?其實不是,按照SQL99的規範,Mysql不支援FULL OUTET的關鍵字,那是不是就沒辦法了?
解決方案如圖所示
我們發現實際上,全連線就是A的獨有+B的獨有,但是我們也知道,A和B連個獨有疊加在一起,相交的部分會重複所以要對它們去重。
參考上圖,第一行是左連線,也就是A的獨有,第三行是右連線是B的獨有,union是合併加去重。
8.AB各有
問題:找出實習員工以及特殊部門。
提示:就是找出AB獨有去掉AB共有,程式碼實現原理和全連線相反。
如圖所示
四 、PL/SQL - 函式和儲存過程
1. 函式和儲存過程簡介
不知道大家的資料庫裡面有沒有千萬條資料。如果你想在你的資料庫裡插入千萬條資料怎麼作?一條一條插?還是用JAVA寫個迴圈?
那日常中我們非常常用的一個需求,比如更新A表之後,B、C兩個表的也要隨著A表更新而更新那怎麼做?
例如同一插入操作,我希望在1、3、5 周插入A表,2、4、6 周插入B表,我又該怎麼做。
有人可能會問了,你說這些東西,涉及到了邏輯操作,我們日常學到SQL都是增刪改查啊,這些問題可能要藉助程式語言實現了。
其實完全不用,Mysql支援PL/SQL,簡單的說就是可以讓你的SQL做簡單的邏輯操作,以此代替java程式碼,是不是很酷。
既然已經說過了,SQL做簡單的邏輯操作,那是不是它也可以向其他語言一樣,可以有函式呢,別說還真有,Msql支援函式和儲存過程。
和java的方法一樣,PL/SQL的函式也支援引數,返回值等。
函式是有返回值的邏輯集合體。如now()/max()/min()… 這些都是系統自帶的。
由於Mysql對自己的過度保護,我們自定義的函式( sql封裝體 ) Mysql是不認可的,我們需要將給Mysql信任白名單告知。
那麼儲存過程是什麼呢?儲存過程其實就是沒有返回值的函式。
下面通過例子一起學習函式和儲存過程的用法。
那麼我們依然用老辦法,實踐是檢驗真理的最好方式。上程式碼。
# 新建庫
create database bigData;
use bigData;
#1 建表dept
CREATE TABLE dept(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
dname VARCHAR(20) NOT NULL DEFAULT "",
loc VARCHAR(13) NOT NULL DEFAULT ""
) ENGINE=INNODB DEFAULT CHARSET=GBK ;
#2 建表emp
CREATE TABLE emp
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*編號*/
ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/
job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/
mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上級編號*/
hiredate DATE NOT NULL,/*入職時間*/
sal DECIMAL(7,2) NOT NULL,/*薪水*/
comm DECIMAL(7,2) NOT NULL,/*紅利*/
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部門編號*/
)ENGINE=INNODB DEFAULT CHARSET=GBK ;
簡單的建兩張表。
本次的目標是,隨機往表裡插入1000W資料
接下來我們就寫函式吧!
等等…
好像新增一個白名單。
設定引數log_bin_trust_function_creators
為什麼要設定這個引數?
當開啟二進位制日誌後(可以執行show variables like 'log_bin’檢視是否開啟),
如果變數log_bin_trust_function_creators為OFF,那麼建立或修改儲存函式就會報
“ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL,
or READS SQL DATA in its declaration and binary logging is enabled
(you *might* want to use the less safe log_bin_trust_function_creators variable)”
這樣的錯誤。
【解決方法】
show variables like ‘log_bin_trust_function_creators’;
set global log_bin_trust_function_creators=1;
這樣添加了引數以後,如果mysqld重啟,上述引數又會消失,永久方法:
windows下my.ini[mysqld]加上log_bin_trust_function_creators=1
linux下 /etc/my.cnf下my.cnf[mysqld]加上log_bin_trust_function_creators=1
那麼我們按照操作一步一步來。
我們的二進位制日誌已經開啟。如圖
查詢log_bin_trust_function_creators,發現是關閉的,不可以建立函式。
接下來我們把他開啟。
再次查詢發現已經是NO了。
我們PL/SQL函式的語法和JAVA有很多相近之處,我們寫一個隨機生成字串的函式。
2. 使用自定義函式隨機生成字串
#換行符的分割標識,原來是以;結尾,由於函式中也要用到;所以用$$代替。
DELIMITER $$
#建立函式 rand_string(n INT), 引數為INT型的變數n,返回值型別為VARCHAR長度為255
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
#函式的開始
BEGIN
#定義varchar型變數,chars_str ,初始化為26個字母,用來後期隨機從中挑選字元。
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
#定義一個varchar型的return_str,預設為空字串,用來返回結果。
DECLARE return_str VARCHAR(255) DEFAULT '';
#定義一個變數i,預設為0
DECLARE i INT DEFAULT 0;
#迴圈,條件為i<n,n為想返回多少位隨機數,每次迴圈生成一個隨機數
WHILE i < n DO
#CONCAT是將後面的引數追加到return_str這個引數中
#SUBSTRING和java一樣,就是切割字串,chars_str為源串,
#1+RAND*52為生成52個隨機數
#FLOOR(1+RAND*52),由於1+RAND*52生成的書是浮點數,故用FLOOR來取整
#floor函式在官方的解釋是,返回小於等於該值的最大整數。
#這句的作用就是將隨機生成的一個字元追加到return_str
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
#i++
SET i = i + 1;
#迴圈體結束
END WHILE;
#返回隨機字串
RETURN return_str;
END $$
#假如要刪除
#drop function rand_string;
執行結果如下
我們在寫一個隨機生成數字
3. 使用自定義函式隨機生成數字
#用於隨機產生部門編號
DELIMITER $$
CREATE FUNCTION rand_num( )
RETURNS INT(5)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(100+RAND()*10);
RETURN i;
END $$
#假如要刪除
#drop function rand_num;
執行結果
函式是有返回值的,因此我們建立了具有返回值的隨機字串和隨機數字,我們可以用這些隨機資料作為我們即將插入需要動態變化的資源欄位。那麼批量插入欄位是不用返回值的,因此我們下一步採用儲存過程插入資料。
4. 使用儲存過程插入員工表
我們建立一個插入員工表 的儲存過程,insert_emp();
DELIMITER $$
#建立儲存過成,START為員工開始編號,max_num為插入多少條資料
CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把autocommit設定成0 關閉自動提交
SET autocommit = 0;
REPEAT #儲存過程中的迴圈
SET i = i + 1; #遊標
#插入員工
#員工號對應Start+i如100,101,102
#員工姓名為我們自己寫的隨機生成的6位字串
#部門編號為隨機生成的3位數
INSERT INTO emp (empno, ename ,job ,mgr ,hiredate ,sal ,comm ,deptno ) VALUES ((START+i) ,rand_string(6),'SALESMAN',0001,CURDATE(),2000,400,rand_num());
#迴圈終止條件
UNTIL i = max_num
#迴圈體結束
END REPEAT;
#手動提交
COMMIT;
END $$
#刪除
# DELIMITER ;
# drop PROCEDURE insert_emp;
5. 使用儲存過程插入部門表
然後在定義一個部門插入儲存過程
#執行儲存過程,往dept表新增隨機資料
DELIMITER $$
CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept (deptno ,dname,loc ) VALUES ((START+i) ,rand_string(10),rand_string(8));
UNTIL i = max_num
END REPEAT;
COMMIT;
END $$
#刪除
# DELIMITER ;
# drop PROCEDURE insert_dept;
做一個簡單的測試:
兩個插入儲存過程都寫完了,自動插入的感覺太爽了,那是不是可以直接插入千萬條資料呢,如果不想讓你的電腦卡死,我建議你分批插入。
我插入了500000條資料用了41.78秒。。。。
這是查詢的結果,從員工編號可以確定我們插入了500000條資料
五 、MySql索引結構
1.基礎回顧
我們在大學的時候都學過計算機組成原理,在這門課中,我們學習了硬碟的結構以及原理。
一個硬碟結構圖如下圖所示。
在寫入資料的時候,距離盤面 3 奈米的磁頭會利用電磁鐵,改變磁碟上磁性材料的極性來記錄資料,兩種極性分別對應 0 或 1 。
而讀取資料時,旁邊的讀取器可以識別磁性材料的不同極性,再還原成 0 或 1 。
一片磁碟分為若干個磁軌,每個磁軌又分為各個扇區。扇區是磁碟儲存的最小資料塊,大小一般是 512 位元組。
因此,磁頭要想讀取某個檔案,必須在電機驅動下,先找到對應的磁軌,再等磁碟轉到對應扇區才行,一般會有十幾毫秒的延遲,這就讓機械硬碟在讀取分散於磁碟各處的資料時,速度將大幅降低。
而在磁軌定址的過程是很耗費時間的,就像我們查詢新華字典一樣,如果不用目錄查詢一個字的話,一頁一頁的翻需要很長時間,如果用事先約定好的規則(如偏旁部首)查詢可以很快很輕鬆的查詢到我們要查的檔案。
Mysql也一樣,索引的目的在於提高查詢效率,可以類比字典,
如果要查“mysql”這個單詞,我們肯定需要定位到m字母,然後從上往下找到y字母,再找到剩下的sql。每查詢一個字母都是O(n)的時間複雜度。
如果沒有索引,那麼你可能需要a----z,如果我想找到Java開頭的單詞呢?或者Oracle開頭的單詞呢?
是不是覺得如果沒有索引,這個事情根本無法完成?
所以使用索引好處多多。
- 索引能極大的減少儲存引擎需要掃描的資料量
- 索引可以把隨機IO變成順序IO
- 索引可以幫助我們在進行分組、排序等操作時,避免使用臨時表
2. 索引檢索原理
本章用到了很多資料結構,如果資料結構不好的同學,建議自學資料結構。
那麼索引的檢索原理是什麼呢?
你可以簡單理解為“排好序的快速查詢B樹資料結構”
B+樹中的B代表平衡(balance)而不是二叉(binary)
在討論B+樹前,我們先討論一下二叉樹。
二叉樹是利用了折半查詢的方式,
在資料之外,資料庫系統還維護著滿足特定查詢演算法的資料結構,這些資料結構以某種方式引用(指向)資料。
這樣就可以在這些資料結構上實現高階查詢演算法。這種資料結構,就是索引。下圖就是一種可能的索引方式示例:
左邊是資料表,一共有兩列七條記錄,最左邊的是資料記錄的實體地址
為了加快Col2的查詢,可以維護一個右邊所示的二叉查詢樹,每個節點分別包含索引鍵值和一個指向對應資料記錄實體地址的指標,這樣就可以運用二叉查詢在一定的複雜度內獲取到相應資料,從而快速的檢索出符合條件的記錄。
那麼前面我們說過了,Mysql的索引是用B+樹(加強版多路平衡查詢樹)作為基礎資料結構的,那麼為什麼要採用B+樹呢,別的資料結構不好嗎?
我們知道很多很多可以用來快速查詢的資料結構與演算法,比如陣列,Hash,二分查詢,又如二叉搜尋樹,再例如AVL平衡樹,B樹(Balance Tree多路平衡查詢樹)等。
下面我們就聊聊索引演算法的發展史。
3. 索引資料結構的發展史
開頭放一個時間複雜度圖,忘記的同學複習一下。
其實對於查詢,我們可以大致分為三類
-
陣列
作為一個人盡皆知的資料結構,陣列的單點插入時間複雜度為O(N),單點查詢的時間複雜度為O(1)。 -
Hash
不得不承認Hash是一個很優秀的演算法,單點插入時間複雜度為O(1),單點查詢的時間複雜度為O(1),那麼為什麼Mysql不採用Hash作為索引演算法呢? -
樹
二叉樹的演算法都是O(Log n),在這可以發現,單按照效率而言,Hash是優於樹的,比如在總資料量為8的序列中查詢,樹需要log2 8 = 3
需要3次可以查到,而Hash只需要1次,但是不要忽略一個致命問題,那就是區間查詢和排序,對數樹來說,區間查詢的時間複雜度依然是O(log n),而Hash卻已經變成了O(n),兩害相衡取其輕,綜合考慮,選擇樹作為索引的演算法。
既然奠定演算法,我們複習下樹的知識。
二叉樹
二叉樹的特點:
1、一個節點只能有兩個子節點,也就是一個節點度不能超過2
2、左子節點 小於 本節點;右子節點大於等於 本節點,比我大的向右,比我小的向左
對該二叉樹的節點進行查詢發現:
深度為1的節點的查詢次數為1,
深度為2的節點的查詢次數為2,
深度為N的節點的查詢次數為N,
結論:因此其平均查詢次數為 (1+2+2+3+3+3) / 6 = 2.3次
二叉樹看起來不錯嘛,為什麼要用B+?
極端情況?左右傾錯誤。
第1種情況
如果id的值是持續遞增的話,建立出的樹會是什麼樣的結構?
第2種情況
很可怕吧,極端的情況下,時間複雜度會退化成恐怖的O(n)。
那麼我們能不能在建樹的時候,把樹 “正” 過來呢,不要這麼偏激。
平衡二叉樹
什麼是平衡二叉樹
平衡二叉樹的特點:
1、一個節點只能有兩個子節點,也就是一個節點度不能超過2
2、左子節點 小於 本節點;右子節點大於等於 本節點,比我大的向右,比我小的向左
從演算法的數學邏輯來講,平衡二叉樹的查詢速度和比較次數都是較小的,說明磁碟IO的次數也很少,那為什麼我們選擇BTREE?
理想豐滿,現實骨感。我們不得不考慮一個最坑爹的問題。我們以二叉樹作為索引結構,舉個例子:
假設樹高是4,查詢的值是10,我們的流程如下:
初始載入樹
第1次查詢:
第2次查詢:
第3次查詢:
第4次查詢:
從上一步的查詢過程中發現的規律?
磁碟的IO次數是由什麼決定的?
樹高,也即磁碟的IO次數最壞的情況下就等於樹的高度。
平衡二叉樹產生的問題:
樹高度問題導致磁碟IO過多
還是不行,哎
我們需要將"瘦高"的身形變成"矮胖",通過降低樹的高度達到減少IO的次數
那麼有沒有什麼辦法把樹壓扁呢,如果三叉呢?
B樹
我們發現B樹不是二叉樹,因為它可能會有三個叉,所以又叫二三樹。後面會詳細介紹B樹。
我們發現,B樹比平衡二叉樹樹高要低。
別急,在瞭解B樹的檢索原理前,我們先了解一下磁碟頁/塊
那如果一個數據庫中的內容很多,需要將所有的索引都載入進來嗎?
-
資料庫索引是儲存在磁碟上的,如果資料很大,必然導致索引的大小也會很大,超過幾個G(好比新華字典字數多必然導致目錄厚)
-
當我們利用索引查詢時候,是不可能將全部幾個G的索引都載入進記憶體的,我們能做的只能是:逐一載入每一個磁碟頁,因為磁碟頁對應著索引樹的節點。
樹只是邏輯結構,物理上資料還是儲存在磁碟中,每個節點儲存在磁碟頁中。
磁碟頁/塊
首先我們用SQL查詢一下頁的資訊
SHOW GLOBAL STATUS LIKE 'Innodb_page_size';
Innodb_page_size
INNODB page size (DEFAULT 16KB).
Many VALUES are counted IN pages; the page size enables them TO be easily converted TO bytes
說明每個頁的大小是16kb。
底層原理
系統從磁碟讀取資料到記憶體時是以磁碟塊(block) 為基本單位的,位於同一個磁碟塊中的資料會被一次性讀取出來,而不是需要什麼取什麼(防止重複讀取浪費時間)。
InnoDB儲存引擎中有頁(Page)的概念,頁是其磁碟管理的最小單位。
系統一個磁碟塊的儲存空間往往沒有這麼大,因此InnoDB每次申請磁碟空間時都會是若干地址連續磁碟塊來達到頁的大小16KB。(爭取塞滿整個頁)
InnoDB在把磁碟資料讀入到磁碟時會以頁為基本單位,在查詢資料時如果一個頁中的每條資料都能有助於定位資料記錄的位置,這將會減少磁碟I/O次數,提高查詢效率。
B樹檢索原理
每個節點佔用一個盤塊的磁碟空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指標,指標儲存的是子節點所在磁碟塊的地址。
模擬查詢關鍵字29的過程:
根據根節點找到磁碟塊1,讀入記憶體。【磁碟I/O操作第1次】
比較關鍵字29在區間(17,35),找到磁碟塊1的指標P2。
根據P2指標找到磁碟塊3,讀入記憶體。【磁碟I/O操作第2次】
比較關鍵字29在區間(26,30),找到磁碟塊3的指標P2。
根據P2指標找到磁碟塊8,讀入記憶體。【磁碟I/O操作第3次】
在磁碟塊8中的關鍵字列表中找到關鍵字29。
分析上面過程,發現需要3次磁碟I/O操作,和3次記憶體查詢操作。由於記憶體中的關鍵字是一個有序表結構,可以利用二分法查詢提高效率。而3磁碟I/O操作是影響整個BTree查詢效率的決定因素。BTree相對於AVLTree縮減了節點個數,使每次磁碟I/O取到記憶體的資料都發揮了作用,從而提高了查詢效率。
結論:B樹比平衡二叉樹減少了一次IO操作
其實b樹就已經很好了,但是喪心病狂的工程師們還不滿意,繼續優化。
B+樹
B+樹和B樹的最明顯的區別就是所有資料都拿到了葉子節點,葉子節點採用連結串列資料結構。
圖中可以看出所有data資訊都移動葉子節點中,而且子節點和子節點之間會有個指標指向,這個也是B+樹的核心點,這樣可以大大提升範圍查詢效率,也方便遍歷整個樹
- 非葉子節點不再儲存資料,資料只儲存在同一層的葉子節點上;
- 葉子之間,增加了連結串列,獲取所有節點,不再需要中序遍歷;
B+樹的檢索原理
B樹(注意是B樹) 的結構圖中可以看到每個節點中不僅包含資料的key值,還有data值。而每一個頁的儲存空間是有限的,如果data資料較大時將會導致每個節點(即一個頁)能儲存的key的數量很小,當儲存的資料量很大時同樣會導致B樹的深度較大,增大查詢時的磁碟I/O次數進而影響查詢效率。
但在B+樹中,所有資料記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只儲存key值資訊,這樣可以大大加大每個節點儲存的key值數量,降低B+樹的高度。
- InnoDB儲存引擎的最小儲存單元是頁,頁可以用於存放資料也可以用於存放鍵值+指標, 在B+樹中葉子節點存放資料,非葉子節點存放鍵值+指標。
- 索引組織表通過非葉子節點的二分查詢法以及指標確定資料在哪個頁中,首先找到根頁進而在去資料頁中查詢到需要的資料
B+樹演算法: 通過繼承了B樹的特徵,B+樹相比B樹,新增葉子節點與非葉子節點關係。
- 葉子節點中包含了鍵值和資料,
- 非葉子節點中只是包含鍵值和子節點引用,不包含資料。
- 通過非葉子節點查詢葉子節點獲取對應的資料,所有相鄰的葉子節點包含非葉子節點使用連結串列進行結合,葉子節點是順序排序並且相鄰節點有順序引用的關係 。
結論:從B樹到B+樹
B+樹是在B樹基礎上的一種優化使其更適合實現外儲存索引結構,InnoDB儲存引擎就是用B+樹實現其索引結構。
B+樹相對於B樹有幾點不同?
-
非葉子節點只儲存鍵值資訊。
-
所有葉子節點之間都有一個鏈指標。
-
資料記錄都存放在葉子節點中。
這章的最後,我們看一下上述結構演算法複雜度
大O表示法
六、索引優化
1. 索引基本型別與語法規則
如果我們想買一本書,肯定去搜索書名而不是去搜索ISBN碼,那麼一個商品庫裡有那麼多的樹,當我們 WHERE book = ’ XX '的時候,想想是不是很恐怖,所以我們要在對應欄位上建立索引。
索引大致上分為以下幾類。
1.單值索引
即一個索引只包含單個列,一個表可以有多個單列索引
如:
create index idx_book_ bName on t_book (bookName) //單值索引
select * from t_book where bookName=' core java ‘
2.唯一索引
索引列的值必須唯一,但允許有空值。
如:
create index unique idx_book_ bName on t_book (bookName) //單值索引
select * from t_book where bookName=' core java ‘
主鍵就是唯一索引
3.複合索引
即一個索引包含多個列
create index idx_ book_ bNameathpci on t_book (bookName, author, price)//複合索引
select * from t_book where bookName=' core java' and author=' z3’;
有人想問,那我多建立幾個單值索引不就好了嘛?你可以這麼用,但是那不是閒的嘛。。。。不推薦
索引語法
1.建立
- 方法1:
使用cerate [] 裡的內容為可選,選中就是唯一索引
CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length));
- 方法二
和下面的ALTER區分
ALTER TABLE 表名 ADD [UNIQUE ] INDEX [indexName] ON (columnname(length))
2.刪除
DROP INDEX [indexName] ON mytable;
3.檢視
SHOW INDEX FROM table_name\G
4.使用ALTER命令
方法1:
該語句新增一個主鍵,這意味著索引值必須是唯一的,且不能為NULL。
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list)
方法2:
這條語句建立索引的值必須是唯一的(除了NULL外,NULL可能會出現多次)。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list):
方法3:
新增普通索引,索引值可出現多次。
ALTER TABLE tbl_name ADD INDEX index_name (column_list)
操作好像挺簡單,但是
索引建立了就可以了嗎?索引建立了查表就會塊嗎,效率就會高嗎,索引建立的好不好?是否被引用?如果索引建立了,不被用那不就是垃圾嗎。下面繼續
2. 索引和EXPLAIN
前面我們瞭解了我們手寫SQL語句和機讀SQL語句順序是不同的,所以說Mysql是會自動優化我們寫的SQL語句。
因此我們可以,使用EXPLAIN關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。分析你的查詢語句或是表結構的效能瓶頸。
借用官網的文件,大致瞭解下
http://dev.mysql.com/doc/refman/5.5/en/explain-output.html
explain可以分析我們的查詢語句或是表結構的效能瓶頸,具體有什麼用呢?大致分為以下幾點
- 表的讀取順序
- 資料讀取操作的操作型別
- 哪些索引可以使用
- 哪些索引被實際使用
- 表之間的引用
- 每張表有多少行被優化器查詢
這是官網的原話,有沒有覺得哪個字都是中國字,但是就是讀不懂,不要急,慢慢來。
explain語法很簡單如下圖
sql寫的好不好我們要眼見為實,資料說話。
但是分析出來的資料都是什麼意思呢?
其實我們在說這些之前,要在瞭解以下一條SQL的執行流程。如圖所示
圖很容易懂,就不解釋了。
本次是以5.5的版本進行講解,5.8之後略有不同,但是大致一樣。
執行計劃包含的資訊如圖如圖所示(就是分析出的欄位)
3. 分析欄位解釋
id(重點)
這可不是資料表中的id
select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序,一般情況下id越多表越複雜。
id欄位的內容又分為以下三種情況
1.id相同,執行順序由上至下
我們發現id都是1,table的順序是t1,t3,t2,表明三個表是順著載入,但是有人一定會有疑惑,我們form的順序明明是t1,t2,t3啊,怎麼到了執行順序中就變成了,t1,t3,t2了呢,我們前面說過,手寫和機讀的順序是不同的。
2.id不同,如果是子查詢,id的序號會遞增,id值越大優先順序越高,越先被執行
我們都知道,括號的優先順序是很高的,被括號修飾的語句肯定是最先執行的,我們在分析表中的id,發現是1,2,3,再看對應的table欄位,發現是t2,t1,t3,正好對應id越大表越先被執行。
所以 如果是子查詢,id的序號會遞增,id值越大優先順序越高,越先被執行。
因此在下一條開始前,先記一句口訣,id相同順著走,id不同大的先走。
3.id相同不同,同時存在
我們依然還是分析下語句,還是先執行括號裡的語句,括號裡面查詢到資訊生成了一個臨時表s1,然後和t2合作共同查詢出了t3.id。
在分析下,查詢出的表id為2的欄位是最大的,發現對應的表是t3,t3是在括號裡面的最先用到的表,然後再看第一行和第二行,發現id都是1,因此他們對應的表在按照從上到下順序執行。
細心的朋友一定發現了,第一行對應的表名是derived2,這是什麼意思呢?其實這是臨時表的意思,derived後面的2對應的是id為2的表t3,意思是這個臨時表是通過t3表誕生的。既s1表。
select_type
select_type查詢的型別,主要是用於區別普通查詢、聯合查詢、子查詢等的複雜查詢。
select_type全部欄位如下:
天哪,怎麼有這麼多欄位,想想腦袋都煩!