寫操作執行過程
如果這條sql是寫操作(insert、update、delete),那么大致的過程如下,其中引擎層是屬於 InnoDB 存儲引擎的,因為InnoDB 是默認的存儲引擎,也是主流的,所以這里只說明 InnoDB 的引擎層過程。由於寫操作較查詢操作更為復雜,所以先看一下寫操作的執行圖。方便后面解析。
組件介紹
Server層
1、連接器
1)負責與客戶端的通信,是半雙工模式,這就意味着某一固定時刻只能由客戶端向服務器請求或者服務器向客戶端發送數據,而不能同時進行。
2)驗證用戶名和密碼是否正確(數據庫mysql的user表中進行驗證),如果錯誤返回錯誤通知(deAcess nied for user 'root'@'localhost'(using password:YES)),如果正確,則會去 mysql 的權限表(mysql中的 user、db、columns_priv、Host 表,分別存儲的是全局級別、數據庫級別、表級別、列級別、配合 db 的數據庫級別)查詢當前用戶的權限。
2、緩存(Cache)
也稱為查詢緩存,存儲的數據是以鍵值對的形式進行存儲,如果開啟了緩存,那么在一條查詢sql語句進來時會先判斷緩存中是否包含當前的sql語句鍵值對,如果存在直接將其對應的結果返回,如果不存在再執行后面一系列操作。如果沒有開啟則直接跳過。
相關操作:
查看緩存配置:show variables like 'have_query_cache';
查看是否開啟:show variables like 'query_cache_type';
查看緩存占用大小:show variables like 'query_cache_size';
查看緩存狀態信息:show status like 'Qcache%';
相關參數的含義:
緩存失效場景:
1、查詢語句不一致。前后兩條查詢SQL必須完全一致。
2、查詢語句中含有一些不確定的值時,則不會緩存。比如 now()、current_date()、curdate()、curtime()、rand()、uuid()等。
3、不使用任何表查詢。如 select 'A';
4、查詢 mysql、information_schema 或 performance_schema 數據庫中的表時,不會走查詢緩存。
5、在存儲的函數,觸發器或事件的主體內執行的查詢。
6、如果表更改,則使用該表的所有高速緩存查詢都變為無效並從緩存中刪除,這包括使用 MERGE 映射到已更改表的表的查詢。一個表可以被許多類型的語句改變,如 insert、update、delete、truncate rable、alter table、drop table、drop database。
通過上面的失效場景可以看出緩存是很容易失效的,所以如果不是查詢次數遠大於修改次數的話,使用緩存不僅不能提升查詢效率還會拉低效率(每次讀取后需要向緩存中保存一份,而緩存又容易被清除)。所以在 MYSQL5.6默認是關閉緩存的,並且在 8.0 直接被移除了。當然,如果場景需要用到,還是可以使用的。
開啟
在配置文件(linux下是安裝目錄的cnf文件,windows是安裝目錄下的ini文件)中,增加配置: query_cache_type = 1
關於 query_type_type 參數的說明:
指定 SQL_NO_CACHE:select SQL_NO_CACHE * from student where age >20; SQL_CACHE 同理。
3、分析器
對客戶端傳來的 sql 進行分析,這將包括預處理與解析過程,並進行關鍵詞的提取、解析,並組成一個解析樹。具體的解析詞包括但不局限於 select/update/delete/or/in/where/group by/having/count/limit 等,如果分析到語法錯誤,會直接拋給客戶端異常:ERROR:You have an error in your SQL syntax.
比如:select * from user where userId =1234;
在分析器中就通過語義規則器將select from where這些關鍵詞提取和匹配出來,mysql會自動判斷關鍵詞和非關鍵詞,將用戶的匹配字段和自定義語句識別出來。這個階段也會做一些校驗:比如校驗當前數據庫是否存在user表,同時假如User表中不存在userId這個字段同樣會報錯:unknown column in field list.
4、優化器
進入優化器說明sql語句是符合標准語義規則並且可以執行。優化器會根據執行計划選擇最優的選擇,匹配合適的索引,選擇最佳的方案。比如一個典型的例子是這樣的:
表T,對A、B、C列建立聯合索引(A,B,C),在進行查詢的時候,當sql查詢條件是:select xx where B=x and A=x and C=x.很多人會以為是用不到索引的,但其實會用到,雖然索引必須符合最左原則才能使用,但是本質上,優化器會自動將這條sql優化為:where A=x and B=x and C=X,這種優化會為了底層能夠匹配到索引,同時在這個階段是自動按照執行計划進行預處理,mysql會計算各個執行方法的最佳時間,最終確定一條執行的sql交給最后的執行器。
優化器會根據掃描行數、是否使用臨時表、是否排序等來判斷是否使用某個索引,其中掃描行數的計算可以通過統計信息來估算得出,而統計信息可以看作是索引唯一數的數量,可以使用部分采樣來估算,具體就是選擇 N 個數據頁,統計這些頁上數據的不同值,得到一個平均值,然后乘以這個索引的頁面數,就得到了。但是因為索引數據會變化,所以索引的統計信息也會變化。當變更的數據行數超過 1/M 的時候,就會重新計算一次統計信息。
關於統計信息可以選擇是否持久化:
通過 innodb_stats_persistent :設置為 on 的時候,表示統計信息會持久化存儲。這時,默認的 N 是 20,M 是 10。設置為 off 的時候,表示統計信息只存儲在內存中。這時,默認的 N 是 8,M 是 16。
沒有使用最優索引如何優化:
1、雖然會自動更新統計信息,但是但是不能保證統計信息是最新值,這就可能導致優化器選擇了不同的索引導致執行變慢,所以可以通過 " analyze table 表名" 來重新計算索引的統計信息。
2、在表名后面添加 " force index(索引名) " 語句來強制使用索引
3、將 sql 進行修改成優化器可以選最優索引的實現方式。
4、新建一個最優索引或者刪除優化器誤用的索引。
5、執行器
執行器會調用對應的存儲引擎執行 sql。主流的是MyISAM 和 Innodb。
存儲引擎(InnoDB)層
1、undo log 與 MVCC
undo log是 Innodb 引擎專屬的日志,是記錄每行數據事務執行前的數據。主要作用是用於實現MVCC版本控制,保證事務隔離級別的讀已提交和讀未提交級別。而 MVCC 相關的可以參考 MySQL中的事務原理和鎖機制。
2、redo log 與 Buffer Pool
InnoDB 內部維護了一個緩沖池,用於減少對磁盤數據的直接IO操作,並配合 redo log、內部的 change buffer 來實現異步的落盤,保證程序的高效執行。redo log 大小固定,采用循環寫
write pos 表示當前正在記錄的位置,會向后記錄, checkpoint 表示數據落盤的邊界,也就是 checkpoint 與 write pos中間是已記錄的,當 write pos寫完 id_logfile_3后,會回到id_logfile_0循環寫,而追上 checkpomnit 后則需要先等數據進行落盤,等待 checkponit向后面移動一段距離再寫。redo log存儲的內容是對數據頁的修改邏輯。
關於 Buffer Pool詳情可查看博客 InnoDB 中的緩沖池(Buffer Pool)。
3、bin log(Server 層)
redo log 因為大小固定,所以不能存儲過多的數據,它只能用於未更新的數據落盤,而數據操作的備份恢復、以及主從復制是靠 bin log(如果數據庫誤刪需要還原,那么需要某個時間點的數據備份以及bin log)。5.7默認記錄的是操作語句涉及的每一行修改前后的行記錄。
在更新到數據頁緩存或者 Change Buffer 后,首先進行 redo log 的編寫,編寫完成后將 redo log 設為 prepare 狀態,隨后再進行 binlog 的編寫,等到 binlog 也編寫完成后再將 redo log 設置為 commit 狀態。這是為了防止數據庫宕機導致 binlog 沒有將修改記錄寫入,后面數據恢復、主從復制時數據不一致。在斷電重啟后先檢查 redo log 記錄的事務操作是否為 commit 狀態:
1、如果是 commit 狀態說明沒有數據丟失,判斷下一個。
2、如果是 prepare 狀態,檢查 binlog 記錄的對應事務操作(redo log 與 binlog 記錄的事務操作有一個共同字段 XID,redo log 就是通過這個字段找到 binlog 中對應的事務的)是否完整(這點在前面 binlog 三種格式分析過,每種格式記錄的事務結尾都有特定的標識),如果完整就將 redo log 設為 commit 狀態,然后結束;不完整就回滾 redo log 的事務,結束。
三種格式:
1、Row(5.7默認)。記錄操作語句對具體行的操作以及操作前的整行信息。缺點是占空間大。優點是能保證數據安全,不會發生遺漏。
2、Statement。記錄修改的 sql。缺點是在 mysql 集群時可能會導致操作不一致從而使得數據不一致(比如在操作中加入了Now()函數,主從數據庫操作的時間不同結果也不同)。優點是占空間小,執行快。
3、Mixed。會針對於操作的 sql 選擇使用Row 還是 Statement。缺點是還是可能發生主從不一致的情況。
三個日志的比較(undo、redo、bin)
1、undo log是用於事務的回滾、保證事務隔離級別讀已提交、可重復讀實現的。redo log是用於對暫不更新到磁盤上的操作進行記錄,使得其可以延遲落盤,保證程序的效率。bin log是對數據操作進行備份恢復(並不能依靠 bin log 直接完成數據恢復)。
2、undo log 與 redo log 是存儲引擎層的日志,只能在 InnoDB 下使用;而bin log 是 Server 層的日志,可以在任何引擎下使用。
3、redo log 大小有限,超過后會循環寫;另外兩個大小不會。
4、undo log 記錄的是行記錄變化前的數據;redo log 記錄的是 sql 的數據頁修改邏輯以及 change buffer 的變更;bin log記錄操作語句對具體行的操作以及操作前的整行信息(5.7默認)或者sql語句。
5、單獨的 binlog 沒有 crash-safe 能力,也就是在異常斷電后,之前已經提交但未更新的事務操作到磁盤的操作會丟失,也就是主從復制的一致性無法保障,而 redo log 有 crash-safe 能力,通過與 redo log 的配合實現 "三步提交",就可以讓主從庫的數據也能保證一致性。
6、redo log 是物理日志,它記錄的是數據頁修改邏輯以及 change buffer 的變更,只能在當前存儲引擎下使用,而 binlog 是邏輯日志,它記錄的是操作語句涉及的每一行修改前后的值,在任何存儲引擎下都可以使用。
MySQL 是 WAL(Write-Ahead Logging)機制,也就是寫操作會先存入日志,然后再寫入磁盤,這樣可以避開高峰,提高數據庫的可用性。
執行過程
寫操作
通過上面的分析,可以很容易地了解開始的更新執行圖。這里就不過多闡述了。
讀操作
查詢的過程和更新比較相似,但是有些不同,主要是來源於他們在查找篩選時的不同,更新因為在查找后會進行更新操作,所以查詢這一行為至始至終都在緩沖池中(使用到索引且緩沖池中包含數據對應的數據頁)。而查詢則更復雜一些。
Where 條件的提取
在 MySQL 5.6開始,引入了一種索引優化策略——索引下推,其本質優化的就是 Where 條件的提取。Where 提取過程是怎樣的?用一個例子來說明,首先進行建表,插入記錄。
create table tbl_test (a int primary key, b int, c int, d int, e varchar(50)); create index idx_bcd on tbl_test(b, c, d); insert into tbl_test values (4,3,1,1,'a'); insert into tbl_test values (1,1,1,2,'d'); insert into tbl_test values (8,8,7,8,'h'); insert into tbl_test values (2,2,1,2,'g'); insert into tbl_test values (5,2,2,5,'e'); insert into tbl_test values (3,3,2,1,'c'); insert into tbl_test values (7,4,0,5,'b'); insert into tbl_test values (6,5,2,4,'f');
那么執行 select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a'; 在提取時,會將 Where 條件拆分為 Index Key(First Key & Last Key)、Index Filter 與 Table Filter。
1、Index Key
用於確定 SQL 查詢在索引中的連續范圍(起始點 + 終止點)的查詢條件,被稱之為Index Key;由於一個范圍,至少包含一個起始條件與一個終止條件,因此 Index Key 也被拆分為 Index First Key 和 Index Last Key,分別用於定位索引查找的起始點以終止點
Index First Key
用於確定索引查詢范圍的起始點;提取規則:從索引的第一個鍵值開始,檢查其在 where 條件中是否存在,若存在並且條件是 =、>=,則將對應的條件加入Index First Key之中,繼續讀取索引的下一個鍵值,使用同樣的提取規則;若存在並且條件是 >,則將對應的條件加入 Index First Key 中,同時終止 Index First Key 的提取;若不存在,同樣終止 Index First Key 的提取
針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,提取出來的 Index First Key 為 b >= 2, c > 0 ,由於 c 的條件為 >,提取結束
Index Last Key
用於確定索引查詢范圍的終止點,與 Index First Key 正好相反;提取規則:從索引的第一個鍵值開始,檢查其在 where 條件中是否存在,若存在並且條件是 =、<=,則將對應條件加入到 Index Last Key 中,繼續提取索引的下一個鍵值,使用同樣的提取規則;若存在並且條件是 < ,則將條件加入到 Index Last Key 中,同時終止提取;若不存在,同樣終止Index Last Key的提取
針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,提取出來的 Index Last Key為 b < 7 ,由於是 < 符號,提取結束
2、Index Filter
在完成 Index Key 的提取之后,我們根據 where 條件固定了索引的查詢范圍,那么是不是在范圍內的每一個索引項都滿足 WHERE 條件了 ? 很明顯 4,0,5 , 2,1,2 均屬於范圍中,但是又均不滿足SQL 的查詢條件
所以 Index Filter 用於索引范圍確定后,確定 SQL 中還有哪些條件可以使用索引來過濾;提取規則:從索引列的第一列開始,檢查其在 where 條件中是否存在,若存在並且 where 條件僅為 =,則跳過第一列繼續檢查索引下一列,下一索引列采取與索引第一列同樣的提取規則;若 where 條件為 >=、>、<、<= 其中的幾種,則跳過索引第一列,將其余 where 條件中索引相關列全部加入到 Index Filter 之中;若索引第一列的 where 條件包含 =、>=、>、<、<= 之外的條件,則將此條件以及其余 where 條件中索引相關列全部加入到 Index Filter 之中;若第一列不包含查詢條件,則將所有索引相關條件均加入到 Index Filter之中
針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,提取出來的 Index Filter 為 c > 0 and d != 2 ,因為索引第一列只包含 >=、< 兩個條件,因此第一列跳過,將余下的 c、d 兩列加入到 Index Filter 中,提取結束
3、Table Filter
這個就比較簡單了,where 中不能被索引過濾的條件都歸為此中;提取規則:所有不屬於索引列的查詢條件,均歸為 Table Filter 之中
針對 SQL:select * from tbl_test where b >= 2 and b < 7 and c > 0 and d != 2 and e != 'a',應用這個提取規則,那么 Table Filter 就為 e != 'a'
在5.6 之前,是不分 Table Filter 與 Index Filter 的,這兩個條件都直接分配到 Server 層進行篩選。篩選過程是先根據 Index Key 的條件先在引擎層進行初步篩選,然后得到對應的主鍵值進行回表查詢得到初篩的行記錄,傳入 Server 層進行后續的篩選,在 Server 層的篩選因為沒有用到索引所以會進行全表掃描。而索引下推的優化就是將 Index Filter 的條件下推到引擎層,在使用 Index First Key 與 Index Last Key 進行篩選時,就帶上 Index Filter 的條件再次篩選,以此來過濾掉不符合條件的記錄對應的主鍵值,減少回表的次數,同時發給 Server 層的記錄也會更少,全表掃描篩選的效率也會變高。下面是未使用索引下推和使用索引下推的示意圖。
索引下推:開啟:set optimizer_switch='index_condition_pushdown=on';
查看 :show variables like 'optimizer_switch';
從上面的分析來看,查詢的流程圖大致可以用下面這張圖來概括
這里要注意的是如果在一開始沒有用到索引,會依次將磁盤上的數據頁讀取到緩沖池中進行查詢。
SQL執行順序
最后需要注意的是 SQL 語句關鍵詞的解析執行順序:
參考博客