一條查詢 SQL 語句是如何執(zhí)行的?

我們的程序或者工具要操作數(shù)據(jù)庫,第一步要做什么事情?跟數(shù)據(jù)庫建立連接
通信協(xié)議
首先,MySQL 必須要運(yùn)行一個服務(wù),監(jiān)聽默認(rèn)的 3306 端口.在我們開發(fā)系統(tǒng)跟第三方對接的時候,必須要弄清楚的有兩件事
- 第一個就是通信協(xié)議,比如我們是用 HTTP 還是 WebService 還是 TCP?
- 第二個是消息格式,比如我們用 XML 格式,還是 JSON 格式,還是定長格式?報文頭長度多少,包含什么內(nèi)容,每個字段的詳細(xì)含義.比如我們之前跟銀聯(lián)對接,銀聯(lián)的銀行卡聯(lián)網(wǎng)規(guī)范,約定了一種比較復(fù)雜的通訊協(xié)議叫做:四進(jìn)四出單工異步長連接(為了保證穩(wěn)定性和性能)

通信協(xié)議
MySQL 是支持多種通信協(xié)議的,可以使用同步/異步的方式,支持長連接/短連接,這里我們拆分來看,第一個是通信類型
通信類型:同步或者異步
同步通信的特點(diǎn):
- 同步通信依賴于被調(diào)用方,受限于被調(diào)用方的性能.也就是說,應(yīng)用操作數(shù)據(jù)庫,線程會阻塞,等待數(shù)據(jù)庫的返回
- 一般只能做到一對一,很難做到一對多的通信
異步跟同步相反:
- 異步可以避免應(yīng)用阻塞等待,但是不能節(jié)省 SQL 執(zhí)行的時間
- 如果異步存在并發(fā),每一個 SQL 的執(zhí)行都要單獨(dú)建立一個連接,避免數(shù)據(jù)混亂.但是這樣會給服務(wù)端帶來巨大的壓力(一個連接就會創(chuàng)建一個線程,線程間切換會占用大量 CPU 資源).另外異步通信還帶來了編碼的復(fù)雜度,所以一般不建議使用.如果要異步,必須使用連接池,排隊從連接池獲取連接而不是創(chuàng)建新連接
一般來說我們連接數(shù)據(jù)庫都是同步連接
連接方式:長連接或者短連接
MySQL 既支持短連接,也支持長連接.短連接就是操作完畢以后,馬上 close 掉.長連接可以保持打開,減少服務(wù)端創(chuàng)建和釋放連接的消耗,后面的程序訪問的時候還可以使用這個連接.一般我們會在連接池中使用長連接
保持長連接會消耗內(nèi)存.長時間不活動的連接,MySQL 服務(wù)器會斷開
show global variables like 'wait_timeout'; -- 非交互式超時時間,如 JDBC 程序
show global variables like 'interactive_timeout'; -- 交互式超時時間,如數(shù)據(jù)庫工具
默認(rèn)都是28800秒,8小時
我們怎么查看 MySQL 當(dāng)前有多少個連接?
可以用 showstatus 命令:
show global status like 'Thread%';
Threads_cached:緩存中的線程連接數(shù)
Threads_connected:當(dāng)前打開的連接數(shù)
Threads_created:為處理連接創(chuàng)建的線程數(shù)
Threads_running:非睡眠狀態(tài)的連接數(shù),通常指并發(fā)連接數(shù)
每產(chǎn)生一個連接或者一個會話,在服務(wù)端就會創(chuàng)建一個線程來處理.反過來,如果要?dú)⑺罆?就是 Kill 線程.有了連接數(shù),怎么知道當(dāng)前連接的狀態(tài)?也可以使用 SHOWPROCESSLIST;(root 用戶)查看 SQL 的執(zhí)行狀態(tài)
https://dev.mysql.com/doc/refman/5.7/en/show-processlist.html

一些常見的狀態(tài):https://dev.mysql.com/doc/refman/5.7/en/thread-commands.html
| 狀態(tài) | 含義 | |
|---|---|---|
| Sleep | 線程正在等待客戶端,以向它發(fā)送一個新語句 | |
| Query | 線程正在執(zhí)行查詢或往客戶端發(fā)送數(shù)據(jù) | Locked 該查詢被其它查詢鎖定 |
| Copyingtotmptableondisk | 臨時結(jié)果集合大于 tmp_table_size.線程把臨時表從存儲器內(nèi)部格式改變?yōu)榇疟P模式,以節(jié)約存儲器 | |
| Sendingdata | 線程正在為 SELECT 語句處理行,同時正在向客戶端發(fā)送數(shù)據(jù) | |
| Sortingforgroup | 線程正在進(jìn)行分類,以滿足 GROUPBY 要求 | |
| Sortingfororder | 線程正在進(jìn)行分類,以滿足 ORDERBY 要求 |
MySQL 服務(wù)允許的最大連接數(shù)是多少呢?
在 5.7 版本中默認(rèn)是 151 個,最大可以設(shè)置成 16384(2^14)
show variables like 'max_connections';

show 的參數(shù)說明:
- 級別:會話 session 級別(默認(rèn));全局 global 級別
- 動態(tài)修改:set,重啟后失效;永久生效,修改配置文件/etc/my.cnf
set global max_connections = 1000;
通信協(xié)議
MySQL 支持哪些通信協(xié)議呢?
第一種是 UnixSocket.比如我們在 Linux 服務(wù)器上,如果沒有指定-h 參數(shù),它就用 socket 方式登錄(省略了-S/var/lib/mysql/mysql.sock)

它不用通過網(wǎng)絡(luò)協(xié)議,也可以連接到 MySQL 的服務(wù)器,它需要用到服務(wù)器上的一個物理文件(/var/lib/mysql/mysql.sock)
select @@socket;
如果指定-h 參數(shù),就會用第二種方式,TCP/IP 協(xié)議
mysql -h192.168.8.211 -uroot -p123456
我們的編程語言的連接模塊都是用 TCP 協(xié)議連接到 MySQL 服務(wù)器的,比如 mysql-connector-java-x.x.xx.jar

另外還有命名管道(NamedPipes)和內(nèi)存共享(ShareMemory)的方式,這兩種通信方式只能在 Windows 上面使用,一般用得比較少
通信方式
第二個是通信方式

- 單工:在兩臺計算機(jī)通信的時候,數(shù)據(jù)的傳輸是單向的.生活中的類比:遙控器
- 半雙工:在兩臺計算機(jī)之間,數(shù)據(jù)傳輸是雙向的,你可以給我發(fā)送,我也可以給你發(fā)送,但是在這個通訊連接里面,同一時間只能有一臺服務(wù)器在發(fā)送數(shù)據(jù),也就是你要給我發(fā)的話,也必須等我發(fā)給你完了之后才能給我發(fā).生活中的類比:對講機(jī)
- 全雙工:數(shù)據(jù)的傳輸是雙向的,并且可以同時傳輸.生活中的類比:打電話
MySQL 使用了半雙工的通信方式?
要么是客戶端向服務(wù)端發(fā)送數(shù)據(jù),要么是服務(wù)端向客戶端發(fā)送數(shù)據(jù),這兩個動作不能同時發(fā)生.所以客戶端發(fā)送 SQL 語句給服務(wù)端的時候,(在一次連接里面)數(shù)據(jù)是不能分成小塊發(fā)送的,不管你的 SQL 語句有多大,都是一次性發(fā)送
比如我們用 MyBatis 動態(tài) SQL 生成了一個批量插入的語句,插入 10 萬條數(shù)據(jù),values 后面跟了一長串的內(nèi)容,或者 where 條件 in 里面的值太多,會出現(xiàn)問題
這個時候我們必須要調(diào)整 MySQL 服務(wù)器配置 max_allowed_packet 參數(shù)的值(默認(rèn)是 4M),把它調(diào)大,否則就會報錯

另一方面,對于服務(wù)端來說,也是一次性發(fā)送所有的數(shù)據(jù),不能因?yàn)槟阋呀?jīng)取到了想要的數(shù)據(jù)就中斷操作,這個時候會對網(wǎng)絡(luò)和內(nèi)存產(chǎn)生大量消耗
所以,我們一定要在程序里面避免不帶 limit 的這種操作,比如一次把所有滿足條件的數(shù)據(jù)全部查出來,一定要先 count 一下.如果數(shù)據(jù)量的話,可以分批查詢
執(zhí)行一條查詢語句,客戶端跟服務(wù)端建立連接之后呢?下一步要做什么?
查詢緩存
MySQL 內(nèi)部自帶了一個緩存模塊
緩存的作用我們應(yīng)該很清楚了,把數(shù)據(jù)以 KV 的形式放到內(nèi)存里面,可以加快數(shù)據(jù)的讀取速度,也可以減少服務(wù)器處理的時間.但是 MySQL 的緩存我們好像比較陌生,從來沒有去配置過,也不知道它什么時候生效?
比如 user_innodb 有 500 萬行數(shù)據(jù),沒有索引.我們在沒有索引的字段上執(zhí)行同樣的查詢,大家覺得第二次會快嗎?
select * from user_innodb where name='青山';
緩存沒有生效,為什么?MySQL 的緩存默認(rèn)是關(guān)閉的
show variables like 'query_cache%';
默認(rèn)關(guān)閉的意思就是不推薦使用,為什么 MySQL 不推薦使用它自帶的緩存呢?
主要是因?yàn)?MySQL 自帶的緩存的應(yīng)用場景有限,第一個是它要求 SQL 語句必須一模一樣,中間多一個空格,字母大小寫不同都被認(rèn)為是不同的的 SQL
第二個是表里面任何一條數(shù)據(jù)發(fā)生變化的時候,這張表所有緩存都會失效,所以對于有大量數(shù)據(jù)更新的應(yīng)用,也不適合
所以緩存這一塊,我們還是交給 ORM 框架(比如 MyBatis 默認(rèn)開啟了一級緩存),或者獨(dú)立的緩存服務(wù),比如 Redis 來處理更合適.在 MySQL8.0 中,查詢緩存已經(jīng)被移除了
1.3.語法解析和預(yù)處理(Parser&Preprocessor)
我們沒有使用緩存的話,就會跳過緩存的模塊,下一步我們要做什么呢?
OK,這里我會有一個疑問,為什么我的一條 SQL 語句能夠被識別呢?假如我隨便執(zhí)行一個字符串 penyuyan,服務(wù)器報了一個 1064 的錯:
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'penyuyan' at line 1
它是怎么知道我輸入的內(nèi)容是錯誤的?
這個就是 MySQL 的 Parser 解析器和 Preprocessor 預(yù)處理模塊.這一步主要做的事情是對語句基于 SQL 語法進(jìn)行詞法和語法分析和語義的解析
詞法解析
詞法分析就是把一個完整的 SQL 語句打碎成一個個的單詞
比如一個簡單的 SQL 語句:
select name from user where id = 1;
它會打碎成8個符號,每個符號是什么類型,從哪里開始到哪里結(jié)束
語法解析
第二步就是語法分析,語法分析會對 SQL 做一些語法檢查,比如單引號有沒有閉合,然后根據(jù) MySQL 定義的語法規(guī)則,根據(jù) SQL 語句生成一個數(shù)據(jù)結(jié)構(gòu).這個數(shù)據(jù)結(jié)構(gòu)我們把它叫做解析樹(select_lex)

任何數(shù)據(jù)庫的中間件,比如 Mycat,Sharding-JDBC(用到了 DruidParser),都必須要有詞法和語法分析功能,在市面上也有很多的開源的詞法解析的工具(比如 LEX,Yacc)
預(yù)處理器
問題:如果我寫了一個詞法和語法都正確的 SQL,但是表名或者字段不存在,會在哪里報錯?是在數(shù)據(jù)庫的執(zhí)行層還是解析器?比如:
select * from penyuyan;
解析器可以分析語法,但是它怎么知道數(shù)據(jù)庫里面有什么表,表里面有什么字段呢?
實(shí)際上還是在解析的時候報錯,解析 SQL 的環(huán)節(jié)里面有個預(yù)處理器.
它會檢查生成的解析樹,解決解析器無法解析的語義.比如,它會檢查表和列名是否存在,檢查名字和別名,保證沒有歧義.
預(yù)處理之后得到一個新的解析樹
查詢優(yōu)化(Query Optimizer)與查詢執(zhí)行計劃
什么是優(yōu)化器?
得到解析樹之后,是不是執(zhí)行 SQL 語句了呢
這里我們有一個問題,一條 SQL 語句是不是只有一種執(zhí)行方式?或者說數(shù)據(jù)庫最終執(zhí)行的 SQL 是不是就是我們發(fā)送的 SQL?
這個答案是否定的.一條 SQL 語句是可以有很多種執(zhí)行方式的,最終返回相同的結(jié)果,他們是等價的.但是如果有這么多種執(zhí)行方式,這些執(zhí)行方式怎么得到的?最終選擇哪一種去執(zhí)行?根據(jù)什么判斷標(biāo)準(zhǔn)去選擇?
這個就是 MySQL 的查詢優(yōu)化器的模塊(Optimizer).
查詢優(yōu)化器的目的就是根據(jù)解析樹生成不同的執(zhí)行計劃(ExecutionPlan),然后選擇一種最優(yōu)的執(zhí)行計劃,MySQL 里面使用的是基于開銷(cost)的優(yōu)化器,那種執(zhí)行計劃開銷最小,就用哪種.
可以使用這個命令查看查詢的開銷:
show status like 'Last_query_cost';
https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Last_query_cost
優(yōu)化器可以做什么?
MySQL 的優(yōu)化器能處理哪些優(yōu)化類型呢?
舉兩個簡單的例子:
- 當(dāng)我們對多張表進(jìn)行關(guān)聯(lián)查詢的時候,以哪個表的數(shù)據(jù)作為基準(zhǔn)表
- 有多個索引可以使用的時候,選擇哪個索引
實(shí)際上,對于每一種數(shù)據(jù)庫來說,優(yōu)化器的模塊都是必不可少的,他們通過復(fù)雜的算法實(shí)現(xiàn)盡可能優(yōu)化查詢效率的目標(biāo)
如果對于優(yōu)化器的細(xì)節(jié)感興趣,可以看看《數(shù)據(jù)庫查詢優(yōu)化器的藝術(shù)-原理解析與 SQL 性能優(yōu)化》

但是優(yōu)化器也不是萬能的,并不是再垃圾的 SQL 語句都能自動優(yōu)化,也不是每次都能選擇到最優(yōu)的執(zhí)行計劃,大家在編寫 SQL 語句的時候還是要注意
如果我們想知道優(yōu)化器是怎么工作的,它生成了幾種執(zhí)行計劃,每種執(zhí)行計劃的 cost 是多少,應(yīng)該怎么做
優(yōu)化器是怎么得到執(zhí)行計劃的?
https://dev.mysql.com/doc/internals/en/optimizer-tracing.html
首先我們要啟用優(yōu)化器的追蹤(默認(rèn)是關(guān)閉的):
SHOW VARIABLES LIKE 'optimizer_trace';
set optimizer_trace='enabled=on';
注意開啟這開關(guān)是會消耗性能的,因?yàn)樗褍?yōu)化分析的結(jié)果寫到表里面,所以不要輕易開啟,或者查看完之后關(guān)閉它(改成 off)
注意:參數(shù)分為 session 和 global 級別
接著我們執(zhí)行一個 SQL 語句,優(yōu)化器會生成執(zhí)行計劃:
select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;
這個時候優(yōu)化器分析的過程已經(jīng)記錄到系統(tǒng)表里面了,我們可以查詢:
select * from information_schema.optimizer_trace\G
它是一個 JSON 類型的數(shù)據(jù),主要分成三部分,準(zhǔn)備階段,優(yōu)化階段和執(zhí)行階段

expanded_query 是優(yōu)化后的 SQL 語句.considered_execution_plans 里面列出了所有的執(zhí)行計劃.分析完記得關(guān)掉它:
set optimizer_trace="enabled=off"; SHOW VARIABLES LIKE 'optimizer_trace';
優(yōu)化器得到的結(jié)果
優(yōu)化完之后,得到一個什么東西呢?
優(yōu)化器最終會把解析樹變成一個查詢執(zhí)行計劃,查詢執(zhí)行計劃是一個數(shù)據(jù)結(jié)構(gòu)
當(dāng)然,這個執(zhí)行計劃是不是一定是最優(yōu)的執(zhí)行計劃呢?不一定,因?yàn)?MySQL 也有可能覆蓋不到所有的執(zhí)行計劃
我們怎么查看 MySQL 的執(zhí)行計劃呢?比如多張表關(guān)聯(lián)查詢,先查詢哪張表?在執(zhí)行查詢的時候可能用到哪些索引,實(shí)際上用到了什么索引?
MySQL 提供了一個執(zhí)行計劃的工具.我們在 SQL 語句前面加上 EXPLAIN,就可以看到執(zhí)行計劃的信息
EXPLAIN select name from user where id=1;
注意 Explain 的結(jié)果也不一定最終執(zhí)行的方式
存儲引擎
得到執(zhí)行計劃以后,SQL 語句是不是終于可以執(zhí)行了?
問題又來了:
- 從邏輯的角度來說,我們的數(shù)據(jù)是放在哪里的,或者說放在一個什么結(jié)構(gòu)里面?
- 執(zhí)行計劃在哪里執(zhí)行?是誰去執(zhí)行?
存儲引擎基本介紹
我們先回答第一個問題:在關(guān)系型數(shù)據(jù)庫里面,數(shù)據(jù)是放在什么結(jié)構(gòu)里面的?
(放在表 Table 里面的)我們可以把這個表理解成 Excel 電子表格的形式.所以我們的表在存儲數(shù)據(jù)的同時,還要組織數(shù)據(jù)的存儲結(jié)構(gòu),這個存儲結(jié)構(gòu)就是由我們的存儲引擎決定的,所以我們也可以把存儲引擎叫做表類型
在 MySQL 里面,支持多種存儲引擎,他們是可以替換的,所以叫做插件式的存儲引擎.為什么要搞這么多存儲引擎呢?一種還不夠用嗎?
這個問題先留著
查看存儲引擎
比如我們數(shù)據(jù)庫里面已經(jīng)存在的表,我們怎么查看它們的存儲引擎呢?
show table status from `gupao`;

或者通過 DDL 建表語句來查看
在 MySQL 里面,我們創(chuàng)建的每一張表都可以指定它的存儲引擎,而不是一個數(shù)據(jù)庫只能使用一個存儲引擎.存儲引擎的使用是以表為單位的.而且,創(chuàng)建表之后還可以修改存儲引擎
我們說一張表使用的存儲引擎決定我們存儲數(shù)據(jù)的結(jié)構(gòu),那在服務(wù)器上它們是怎么存儲的呢?我們先要找到數(shù)據(jù)庫存放數(shù)據(jù)的路徑:
默認(rèn)情況下,每個數(shù)據(jù)庫有一個自己文件夾,以 gupao 數(shù)據(jù)庫為例
任何一個存儲引擎都有一個 frm 文件,這個是表結(jié)構(gòu)定義文件

不同的存儲引擎存放數(shù)據(jù)的方式不一樣,產(chǎn)生的文件也不一樣,innodb 是 1 個,memory 沒有,myisam 是兩個
這些存儲引擎的差別在哪呢?
存儲引擎比較
常見存儲引擎
MyISAM 和 InnoDB 是我們用得最多的兩個存儲引擎,在 MySQL5.5 版本之前,默認(rèn)的存儲引擎是 MyISAM,它是 MySQL 自帶的.我們創(chuàng)建表的時候不指定存儲引擎,它就會使用 MyISAM 作為存儲引擎
MyISAM 的前身是 ISAM(IndexedSequentialAccessMethod:利用索引,順序存取數(shù)據(jù)的方法)
5.5 版本之后默認(rèn)的存儲引擎改成了 InnoDB,它是第三方公司為 MySQL 開發(fā)的.為什么要改呢?最主要的原因還是 InnoDB 支持事務(wù),支持行級別的鎖,對于業(yè)務(wù)一致性要求高的場景來說更適合
這個里面又有 Oracle 和 MySQL 公司的一段恩怨情仇
InnoDB 本來是 InnobaseOy 公司開發(fā)的,它和 MySQLAB 公司合作開源了 InnoDB 的代碼.但是沒想到 MySQL 的競爭對手 Oracle 把 InnobaseOy 收購了
后來 08 年 Sun 公司(開發(fā) Java 語言的 Sun)收購了 MySQLAB,09 年 Sun 公司又被 Oracle 收購了,所以 MySQL,InnoDB 又是一家了.有人覺得 MySQL 越來越像 Oracle,其實(shí)也是這個原因

那么除了這兩個我們最熟悉的存儲引擎,數(shù)據(jù)庫還支持其他哪些常用的存儲引擎呢?
數(shù)據(jù)庫支持的存儲引擎
我們可以用這個命令查看數(shù)據(jù)庫對存儲引擎的支持情況:
show engines;
其中有存儲引擎的描述和對事務(wù),XA 協(xié)議和 Savepoints 的支持
XA 協(xié)議用來實(shí)現(xiàn)分布式事務(wù)(分為本地資源管理器,事務(wù)管理器)
Savepoints 用來實(shí)現(xiàn)子事務(wù)(嵌套事務(wù)).創(chuàng)建了一個 Savepoints 之后,事務(wù)就可以回滾到這個點(diǎn),不會影響到創(chuàng)建 Savepoints 之前的操作

這些數(shù)據(jù)庫支持的存儲引擎,分別有什么特性呢?
https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html
MyISAM(3 個文件)
These tables have a small footprint. Table-level locking limits the performance in read/write workloads, so it is often used in read-only or read-mostly workloads in Web and data warehousing configurations
應(yīng)用范圍比較小.表級鎖定限制了讀/寫的性能,因此在 Web 和數(shù)據(jù)倉庫配置中,它通常用于只讀或以讀為主的工作
特點(diǎn):
- 支持表級別的鎖(插入和更新會鎖表).不支持事務(wù)
- 擁有較高的插入(insert)和查詢(select)速度
- 存儲了表的行數(shù)(count 速度更快),(怎么快速向數(shù)據(jù)庫插入 100 萬條數(shù)據(jù)?我們有一種先用 MyISAM 插入數(shù)據(jù),然后修改存儲引擎為 InnoDB 的操作)
適合:只讀之類的數(shù)據(jù)分析的項目
InnoDB(2 個文件)
https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html
The default storage engine in MySQL 5.7. InnoDB is a transaction-safe (ACID compliant)storage engine for MySQL that has commit, rollback, and crash-recovery capabilities to protect user data. InnoDB row-level locking (without escalation to coarser granularity locks)and Oracle-style consistent nonlocking reads increase multi-user concurrency and performance. InnoDB stores user data in clustered indexes to reduce I/O for common queries based on primary keys. To maintain data integrity, InnoDB also supports FOREIGN KEY referential-integrity constraints
mysql5.7 中的默認(rèn)存儲引擎.InnoDB 是一個事務(wù)安全(與 ACID 兼容)的 MySQL 存儲引擎,它具有提交,回滾和崩潰恢復(fù)功能來保護(hù)用戶數(shù)據(jù).InnoDB 行級鎖(不升級為更粗粒度的鎖)和 Oracle 風(fēng)格的一致非鎖讀提高了多用戶并發(fā)性和性能.InnoDB 將用戶數(shù)據(jù)存儲在聚集索引中,以減少基于主鍵的常見查詢的 I/O.為了保持?jǐn)?shù)據(jù)完整性,InnoDB 還支持外鍵引用完整性約束
特點(diǎn):
- 支持事務(wù),支持外鍵,因此數(shù)據(jù)的完整性,一致性更高
- 支持行級別的鎖和表級別的鎖
- 支持讀寫并發(fā),寫不阻塞讀(MVCC)
- 特殊的索引存放方式,可以減少 IO,提升查詢效率
適合:經(jīng)常更新的表,存在并發(fā)讀寫或者有事務(wù)處理的業(yè)務(wù)系統(tǒng)
Memory(1 個文件)
Stores all data in RAM, for fast access in environments that require quick lookups of non-critical data. This engine was formerly known as the HEAP engine. Its use cases are decreasing; InnoDB with its buffer pool memory area provides a general-purpose and durable way to keep most or all data in memory, and NDBCLUSTER provides fast key-value lookups for huge distributed data sets
將所有數(shù)據(jù)存儲在 RAM 中,以便在需要快速查找非關(guān)鍵數(shù)據(jù)的環(huán)境中快速訪問.這個引擎以前被稱為堆引擎.其使用案例正在減少;InnoDB 及其緩沖池內(nèi)存區(qū)域提供了一種通用,持久的方法來將大部分或所有數(shù)據(jù)保存在內(nèi)存中,而 ndbcluster 為大型分布式數(shù)據(jù)集提供了快速的鍵值查找
特點(diǎn):
- 把數(shù)據(jù)放在內(nèi)存里面,讀寫的速度很快,但是數(shù)據(jù)庫重啟或者崩潰,數(shù)據(jù)會全部消失.只適合做臨時表
- 將表中的數(shù)據(jù)存儲到內(nèi)存中
CSV(3 個文件)
Its tables are really text files with comma-separated values. CSV tables let you import or dump data in CSV format, to exchange data with scripts and applications that read and write that same format. Because CSV tables are not indexed, you typically keep the data in InnoDB tables during normal operation, and only use CSV tables during the import or export stage
它的表實(shí)際上是帶有逗號分隔值的文本文件.csv 表允許以 csv 格式導(dǎo)入或轉(zhuǎn)儲數(shù)據(jù),以便與讀寫相同格式的腳本和應(yīng)用程序交換數(shù)據(jù).因?yàn)?csv 表沒有索引,所以通常在正常操作期間將數(shù)據(jù)保存在 innodb 表中,并且只在導(dǎo)入或?qū)С鲭A段使用 csv 表
特點(diǎn):
- 不允許空行,不支持索引.格式通用,可以直接編輯,適合在不同數(shù)據(jù)庫之間導(dǎo)入導(dǎo)出
Archive(2 個文件)
These compact, unindexed tables are intended for storing and retrieving large amounts of seldom-referenced historical, archived, or security audit information
這些緊湊的未索引的表用于存儲和檢索大量很少引用的歷史,存檔或安全審計信息
特點(diǎn):
- 不支持索引,不支持 updatedelete
這是 MySQL 里面常見的一些存儲引擎,我們看到了,不同的存儲引擎提供的特性都不一樣,它們有不同的存儲機(jī)制,索引方式,鎖定水平等功能
我們在不同的業(yè)務(wù)場景中對數(shù)據(jù)操作的要求不同,就可以選擇不同的存儲引擎來滿足我們的需求,這個就是 MySQL 支持這么多存儲引擎的原因
如何選擇存儲引擎?
如果對數(shù)據(jù)一致性要求比較高,需要事務(wù)支持,可以選擇 InnoDB
如果數(shù)據(jù)查詢多更新少,對查詢性能要求比較高,可以選擇 MyISAM
如果需要一個用于查詢的臨時表,可以選擇 Memory
如果所有的存儲引擎都不能滿足你的需求,并且技術(shù)能力足夠,可以根據(jù)官網(wǎng)內(nèi)部手冊用 C 語言開發(fā)一個存儲引擎:https://dev.mysql.com/doc/internals/en/custom-engine.html
執(zhí)行引擎(Query Execution Engine),返回結(jié)果
存儲引擎分析完了,它是我們存儲數(shù)據(jù)的形式,繼續(xù)第二個問題,是誰使用執(zhí)行計劃去操作存儲引擎呢?
這就是我們的執(zhí)行引擎,它利用存儲引擎提供的相應(yīng)的 API 來完成操作
為什么我們修改了表的存儲引擎,操作方式不需要做任何改變?因?yàn)椴煌δ艿拇鎯σ鎸?shí)現(xiàn)的 API 是相同的
最后把數(shù)據(jù)返回給客戶端,即使沒有結(jié)果也要返回
MySQL 體系結(jié)構(gòu)總結(jié)
基于上面分析的流程,我們一起來梳理一下 MySQL 的內(nèi)部模塊
模塊詳解

- Connector:用來支持各種語言和 SQL 的交互,比如 PHP,Python,Java 的 JDBC
- ManagementServeices&Utilities:系統(tǒng)管理和控制工具,包括備份恢復(fù),MySQL 復(fù)制,集群等等
- ConnectionPool:連接池,管理需要緩沖的資源,包括用戶密碼權(quán)限線程等
- SQLInterface:用來接收用戶的 SQL 命令,返回用戶需要的查詢結(jié)果
- Parser:用來解析 SQL 語句
- Optimizer:查詢優(yōu)化器
- CacheandBuffer:查詢緩存,除了行記錄的緩存之外,還有表緩存,Key 緩存,權(quán)限緩存等等
- PluggableStorageEngines:插件式存儲引擎,它提供 API 給服務(wù)層使用,跟具體的文件打交道
架構(gòu)分層
總體上,我們可以把 MySQL 分成三層,跟客戶端對接的連接層,真正執(zhí)行操作的服務(wù)層,和跟硬件打交道的存儲引擎層(參考 MyBatis:接口,核心,基礎(chǔ))

連接層
我們的客戶端要連接到 MySQL 服務(wù)器 3306 端口,必須要跟服務(wù)端建立連接,那么管理所有的連接,驗(yàn)證客戶端的身份和權(quán)限,這些功能就在連接層完成
服務(wù)層
連接層會把 SQL 語句交給服務(wù)層,這里面又包含一系列的流程:
比如查詢緩存的判斷,根據(jù) SQL 調(diào)用相應(yīng)的接口,對我們的 SQL 語句進(jìn)行詞法和語法的解析(比如關(guān)鍵字怎么識別,別名怎么識別,語法有沒有錯誤等等)
然后就是優(yōu)化器,MySQL 底層會根據(jù)一定的規(guī)則對我們的 SQL 語句進(jìn)行優(yōu)化,最后再交給執(zhí)行器去執(zhí)行
存儲引擎
存儲引擎就是我們的數(shù)據(jù)真正存放的地方,在 MySQL 里面支持不同的存儲引擎
再往下就是內(nèi)存或者磁盤
一條更新 SQL 是如何執(zhí)行的?
講完了查詢流程,我們是不是再講講更新流程,插入流程和刪除流程?
在數(shù)據(jù)庫里面,我們說的 update 操作其實(shí)包括了更新,插入和刪除.如果大家有看過 MyBatis 的源碼,應(yīng)該知道 Executor 里面也只有 doQuery()和 doUpdate()的方法,沒有 doDelete()和 doInsert()
更新流程和查詢流程有什么不同呢?
基本流程也是一致的,也就是說,它也要經(jīng)過解析器,優(yōu)化器的處理,最后交給執(zhí)行器
區(qū)別就在于拿到符合條件的數(shù)據(jù)之后的操作
緩沖池 Buffer Pool
首先,InnnoDB 的數(shù)據(jù)都是放在磁盤上的,InnoDB 操作數(shù)據(jù)有一個最小的邏輯單位,叫做頁(索引頁和數(shù)據(jù)頁).我們對于數(shù)據(jù)的操作,不是每次都直接操作磁盤,因?yàn)榇疟P的速度太慢了.InnoDB 使用了一種緩沖池的技術(shù),也就是把磁盤讀到的頁放到一塊內(nèi)存區(qū)域里面.這個內(nèi)存區(qū)域就叫 BufferPool
下一次讀取相同的頁,先判斷是不是在緩沖池里面,如果是,就直接讀取,不用再次訪問磁盤修改數(shù)據(jù)的時候,先修改緩沖池里面的頁.內(nèi)存的數(shù)據(jù)頁和磁盤數(shù)據(jù)不一致的時候,我們把它叫做臟頁.InnoDB 里面有專門的后臺線程把 BufferPool 的數(shù)據(jù)寫入到磁盤,每隔一段時間就一次性地把多個修改寫入磁盤,這個動作就叫做刷臟
BufferPool 是 InnoDB 里面非常重要的一個結(jié)構(gòu),它的內(nèi)部又分成幾塊區(qū)域.這里我們趁機(jī)到官網(wǎng)來認(rèn)識一下 InnoDB 的內(nèi)存結(jié)構(gòu)和磁盤結(jié)構(gòu)
InnoDB 內(nèi)存結(jié)構(gòu)和磁盤結(jié)構(gòu)

內(nèi)存結(jié)構(gòu)
BufferPool 主要分為 3 個部分:BufferPool,ChangeBuffer,AdaptiveHashIndex,另外還有一個(redo)logbuffer
Buffer Pool
BufferPool 緩存的是頁面信息,包括數(shù)據(jù)頁,索引頁
查看服務(wù)器狀態(tài),里面有很多跟 BufferPool 相關(guān)的信息:
SHOW STATUS LIKE '%innodb_buffer_pool%';
這些狀態(tài)都可以在官網(wǎng)查到詳細(xì)的含義,用搜索功能
https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html

BufferPool 默認(rèn)大小是 128M(134217728 字節(jié)),可以調(diào)整
查看參數(shù)(系統(tǒng)變量):
SHOW VARIABLES like '%innodb_buffer_pool%';
這些參數(shù)都可以在官網(wǎng)查到詳細(xì)的含義,用搜索功能
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html

內(nèi)存的緩沖池寫滿了怎么辦?(Redis 設(shè)置的內(nèi)存滿了怎么辦?)InnoDB 用 LRU 算法來管理緩沖池(鏈表實(shí)現(xiàn),不是傳統(tǒng)的 LRU,分成了 young 和 old),經(jīng)過淘汰的數(shù)據(jù)就是熱點(diǎn)數(shù)據(jù)
內(nèi)存緩沖區(qū)對于提升讀寫性能有很大的作用.思考一個問題:
當(dāng)需要更新一個數(shù)據(jù)頁時,如果數(shù)據(jù)頁在 BufferPool 中存在,那么就直接更新好了
否則的話就需要從磁盤加載到內(nèi)存,再對內(nèi)存的數(shù)據(jù)頁進(jìn)行操作.也就是說,如果沒有命中緩沖池,至少要產(chǎn)生一次磁盤 IO,有沒有優(yōu)化的方式呢?
Change Buffer 寫緩沖
如果這個數(shù)據(jù)頁不是唯一索引,不存在數(shù)據(jù)重復(fù)的情況,也就不需要從磁盤加載索引頁判斷數(shù)據(jù)是不是重復(fù)(唯一性檢查).這種情況下可以先把修改記錄在內(nèi)存的緩沖池中,從而提升更新語句(Insert,Delete,Update)的執(zhí)行速度
這一塊區(qū)域就是 ChangeBuffer.5.5 之前叫 InsertBuffer 插入緩沖,現(xiàn)在也能支持 delete 和 update
最后把 ChangeBuffer 記錄到數(shù)據(jù)頁的操作叫做 merge.什么時候發(fā)生 merge?有幾種情況:在訪問這個數(shù)據(jù)頁的時候,或者通過后臺線程,或者數(shù)據(jù)庫 shutdown,redolog 寫滿時觸發(fā)
如果數(shù)據(jù)庫大部分索引都是非唯一索引,并且業(yè)務(wù)是寫多讀少,不會在寫數(shù)據(jù)后立刻讀取,就可以使用 ChangeBuffer(寫緩沖).寫多讀少的業(yè)務(wù),調(diào)大這個值:
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';
代表 ChangeBuffer 占 BufferPool 的比例,默認(rèn) 25%
Adaptive Hash Index
索引應(yīng)該是放在磁盤的,為什么要專門把一種哈希的索引放到內(nèi)存?下次課再說
(redo)Log Buffer
思考一個問題:如果 BufferPool 里面的臟頁還沒有刷入磁盤時,數(shù)據(jù)庫宕機(jī)或者重啟,這些數(shù)據(jù)丟失.如果寫操作寫到一半,甚至可能會破壞數(shù)據(jù)文件導(dǎo)致數(shù)據(jù)庫不可用
為了避免這個問題,InnoDB 把所有對頁面的修改操作專門寫入一個日志文件,并且在數(shù)據(jù)庫啟動時從這個文件進(jìn)行恢復(fù)操作(實(shí)現(xiàn) crash-safe)——用它來實(shí)現(xiàn)事務(wù)的持久性

這個文件就是磁盤的 redolog(叫做重做日志),對應(yīng)于/var/lib/mysql/目錄下的 ib_logfile0 和 ib_logfile1,每個 48M 這種日志和磁盤配合的整個過程,其實(shí)就是 MySQL 里的 WAL 技術(shù)(Write-AheadLogging),它的關(guān)鍵點(diǎn)就是先寫日志,再寫磁盤
show variables like 'innodb_log%';
| 值 | 含義 |
|---|---|
| innodb_log_file_size | 指定每個文件的大小,默認(rèn) 48M |
| innodb_log_files_in_group | 指定文件的數(shù)量,默認(rèn)為 2 |
| innodb_log_group_home_dir | 指定文件所在路徑,相對或絕對,如果不指定,則為 datadir 路徑 |
問題:
同樣是寫磁盤,為什么不直接寫到 dbfile 里面去?為什么先寫日志再寫磁盤?
我們先來了解一下隨機(jī) I/O 和順序 I/O 的概念
磁盤的最小組成單元是扇區(qū),通常是512個字節(jié)
操作系統(tǒng)和內(nèi)存打交道,最小的單位是頁 Page
操作系統(tǒng)和磁盤打交道,讀寫磁盤,最小的單位是塊 Block

如果我們所需要的數(shù)據(jù)是隨機(jī)分散在不同頁的不同扇區(qū)中,那么找到相應(yīng)的數(shù)據(jù)需要等到磁臂旋轉(zhuǎn)到指定的頁,然后盤片尋找到對應(yīng)的扇區(qū),才能找到我們所需要的一塊數(shù)據(jù),一次進(jìn)行此過程直到找完所有數(shù)據(jù),這個就是隨機(jī) IO,讀取數(shù)據(jù)速度較慢
假設(shè)我們已經(jīng)找到了第一塊數(shù)據(jù),并且其他所需的數(shù)據(jù)就在這一塊數(shù)據(jù)后邊,那么就不需要重新尋址,可以依次拿到我們所需的數(shù)據(jù),這個就叫順序 IO
刷盤是隨機(jī) I/O,而記錄日志是順序 I/O,順序 I/O 效率更高.因此先把修改寫入日志,可以延遲刷盤時機(jī),進(jìn)而提升系統(tǒng)吞吐
當(dāng)然 redolog 也不是每一次都直接寫入磁盤,在 BufferPool 里面有一塊內(nèi)存區(qū)域(LogBuffer)專門用來保存即將要寫入日志文件的數(shù)據(jù),默認(rèn) 16M,它一樣可以節(jié)省\磁盤 IO
[圖片上傳失敗...(image-2b1eed-1617012475264)]
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
需要注意:redolog 的內(nèi)容主要是用于崩潰恢復(fù).磁盤的數(shù)據(jù)文件,數(shù)據(jù)來自 bufferpool.redolog 寫入磁盤,不是寫入數(shù)據(jù)文件
那么,LogBuffer 什么時候?qū)懭?logfile?
在我們寫入數(shù)據(jù)到磁盤的時候,操作系統(tǒng)本身是有緩存的.flush 就是把操作系統(tǒng)緩沖區(qū)寫入到磁盤
logbuffer 寫入磁盤的時機(jī),由一個參數(shù)控制,默認(rèn)是 1
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit
| 值 | 含義 |
|---|---|
| 0(延遲寫) | logbuffer 將每秒一次地寫入 logfile 中,并且 logfile 的 flush 操作同時進(jìn)行.該模式下,在事務(wù)提交的時候,不會主動觸發(fā)寫入磁盤的操作 |
| 1(默認(rèn),實(shí)時寫,實(shí)時刷) | 每次事務(wù)提交時 MySQL 都會把 logbuffer 的數(shù)據(jù)寫入 logfile,并且刷到磁盤中去 |
| 2(實(shí)時寫,延遲刷) | 每次事務(wù)提交時 MySQL 都會把 logbuffer 的數(shù)據(jù)寫入 logfile.但是 flush 操作并不會同時進(jìn)行.該模式下 MySQL 會每秒執(zhí)行一次 flush 操作 |

這是內(nèi)存結(jié)構(gòu)的第 4 塊內(nèi)容,redolog,它又分成內(nèi)存和磁盤兩部分.redolog 有什么特點(diǎn)?
- redolog 是 InnoDB 存儲引擎實(shí)現(xiàn)的,并不是所有存儲引擎都有
- 不是記錄數(shù)據(jù)頁更新之后的狀態(tài),而是記錄這個頁做了什么改動,屬于物理日志
- redolog 的大小是固定的,前面的內(nèi)容會被覆蓋

checkpoint 是當(dāng)前要覆蓋的位置.如果 writepos 跟 checkpoint 重疊,說明 redolog 已經(jīng)寫滿,這時候需要同步 redolog 到磁盤中
這是 MySQL 的內(nèi)存結(jié)構(gòu),總結(jié)一下,分為:Bufferpool,changebuffer,AdaptiveHashIndex,logbuffer
磁盤結(jié)構(gòu)里面主要是各種各樣的表空間,叫做 Tablespace
磁盤結(jié)構(gòu)
表空間可以看做是 InnoDB 存儲引擎邏輯結(jié)構(gòu)的最高層,所有的數(shù)據(jù)都存放在表空間中.InnoDB 的表空間分為 5 大類
系統(tǒng)表空間 system tablespace
在默認(rèn)情況下 InnoDB 存儲引擎有一個共享表空間(對應(yīng)文件/var/lib/mysql/ibdata1),也叫系統(tǒng)表空間
InnoDB 系統(tǒng)表空間包含 InnoDB 數(shù)據(jù)字典和雙寫緩沖區(qū),ChangeBuffer 和 UndoLogs),如果沒有指定 file-per-table,也包含用戶創(chuàng)建的表和索引數(shù)據(jù)
- undo 在后面介紹,因?yàn)橛歇?dú)立的表空間
- 數(shù)據(jù)字典:由內(nèi)部系統(tǒng)表組成,存儲表和索引的元數(shù)據(jù)(定義信息)
- 雙寫緩沖(InnoDB 的一大特性):InnoDB 的頁和操作系統(tǒng)的頁大小不一致,InnoDB 頁大小一般為 16K,操作系統(tǒng)頁大小為 4K,InnoDB 的頁寫入到磁盤時,一個頁需要分 4 次寫

如果存儲引擎正在寫入頁的數(shù)據(jù)到磁盤時發(fā)生了宕機(jī),可能出現(xiàn)頁只寫了一部分的情況,比如只寫了 4K,就宕機(jī)了,這種情況叫做部分寫失效(partialpagewrite),可能會導(dǎo)致數(shù)據(jù)丟失
show variables like 'innodb_doublewrite';
我們不是有 redolog 嗎?但是有個問題,如果這個頁本身已經(jīng)損壞了,用它來做崩潰恢復(fù)是沒有意義的.所以在對于應(yīng)用 redolog 之前,需要一個頁的副本.如果出現(xiàn)了寫入失效,就用頁的副本來還原這個頁,然后再應(yīng)用 redolog.這個頁的副本就是 doublewrite,InnoDB 的雙寫技術(shù).通過它實(shí)現(xiàn)了數(shù)據(jù)頁的可靠性.跟 redolog 一樣,doublewrite 由兩部分組成,一部分是內(nèi)存的 doublewrite,一個部分是磁盤上的 doublewrite.因?yàn)?doublewrite 是順序?qū)懭氲?不會帶來很大的開銷
在默認(rèn)情況下,所有的表共享一個系統(tǒng)表空間,這個文件會越來越大,而且它的空間不會收縮
獨(dú)占表空間 file-per-table tablespaces
我們可以讓每張表獨(dú)占一個表空間.這個開關(guān)通過 innodb_file_per_table 設(shè)置,默認(rèn)開啟
SHOW VARIABLES LIKE 'innodb_file_per_table';
開啟后,則每張表會開辟一個表空間,這個文件就是數(shù)據(jù)目錄下的 ibd 文件(例如/var/lib/mysql/gupao/user_innodb.ibd),存放表的索引和數(shù)據(jù)
但是其他類的數(shù)據(jù),如回滾(undo)信息,插入緩沖索引頁,系統(tǒng)事務(wù)信息,二次寫緩沖(Doublewritebuffer)等還是存放在原來的共享表空間內(nèi)
通用表空間 general tablespaces
通用表空間也是一種共享的表空間,跟 ibdata1 類似
可以創(chuàng)建一個通用的表空間,用來存儲不同數(shù)據(jù)庫的表,數(shù)據(jù)路徑和文件可以自定義.語法:
create tablespace ts2673 add datafile '/var/lib/mysql/ts2673.ibd' file_block_size=16K engine=innodb;
在創(chuàng)建表的時候可以指定表空間,用 ALTER 修改表空間可以轉(zhuǎn)移表空間
create table t2673(id integer)tablespace ts2673;
不同表空間的數(shù)據(jù)是可以移動的
刪除表空間需要先刪除里面的所有表:
drop table t2673;
drop tablespace ts2673;
臨時表空間 temporary tablespaces
存儲臨時表的數(shù)據(jù),包括用戶創(chuàng)建的臨時表,和磁盤的內(nèi)部臨時表.對應(yīng)數(shù)據(jù)目錄下的 ibtmp1 文件.當(dāng)數(shù)據(jù)服務(wù)器正常關(guān)閉時,該表空間被刪除,下次重新產(chǎn)生
Redo log
磁盤結(jié)構(gòu)里面的 redolog,在前面已經(jīng)介紹過了
undo log tablespace
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-logs.html
undolog(撤銷日志或回滾日志)記錄了事務(wù)發(fā)生之前的數(shù)據(jù)狀態(tài)(不包括 select).如果修改數(shù)據(jù)時出現(xiàn)異常,可以用 undolog 來實(shí)現(xiàn)回滾操作(保持原子性)
在執(zhí)行 undo 的時候,僅僅是將數(shù)據(jù)從邏輯上恢復(fù)至事務(wù)之前的狀態(tài),而不是從物理頁面上操作實(shí)現(xiàn)的,屬于邏輯格式的日志
redoLog 和 undoLog 與事務(wù)密切相關(guān),統(tǒng)稱為事務(wù)日志
undoLog 的數(shù)據(jù)默認(rèn)在系統(tǒng)表空間 ibdata1 文件中,因?yàn)楣蚕肀砜臻g不會自動收縮,也可以單獨(dú)創(chuàng)建一個 undo 表空間
show global variables like '%undo%';
有了這些日志之后,我們來總結(jié)一下一個更新操作的流程,這是一個簡化的過程.name 原值是 qingshan
update user set name = 'penyuyan' where id=1;
- 事務(wù)開始,從內(nèi)存或磁盤取到這條數(shù)據(jù),返回給 Server 的執(zhí)行器
- 執(zhí)行器修改這一行數(shù)據(jù)的值為 penyuyan;
- 記錄 name=qingshan 到 undolog;
- 記錄 name=penyuyan 到 redolog;
- 調(diào)用存儲引擎接口,在內(nèi)存(BufferPool)中修改 name=penyuyan;
- 事務(wù)提交
內(nèi)存和磁盤之間,工作著很多后臺線程
后臺線程
后臺線程的主要作用是負(fù)責(zé)刷新內(nèi)存池中的數(shù)據(jù)和把修改的數(shù)據(jù)頁刷新到磁盤.后臺線程分為:masterthread,IOthread,purgethread,pagecleanerthread
- masterthread 負(fù)責(zé)刷新緩存數(shù)據(jù)到磁盤并協(xié)調(diào)調(diào)度其它后臺進(jìn)程
- IOthread 分為 insertbuffer,log,read,write 進(jìn)程.分別用來處理 insertbuffer,重做日志,讀寫請求的 IO 回調(diào)
- purgethread 用來回收 undo 頁
- pagecleanerthread 用來刷新臟頁
除了 InnoDB 架構(gòu)中的日志文件,MySQL 的 Server 層也有一個日志文件,叫做 binlog,它可以被所有的存儲引擎使用
Binlog
https://dev.mysql.com/doc/refman/5.7/en/binary-log.html
binlog 以事件的形式記錄了所有的 DDL 和 DML 語句(因?yàn)樗涗浀氖遣僮鞫皇菙?shù)據(jù)值,屬于邏輯日志),可以用來做主從復(fù)制和數(shù)據(jù)恢復(fù)
跟 redolog 不一樣,它的文件內(nèi)容是可以追加的,沒有固定大小限制
在開啟了 binlog 功能的情況下,我們可以把 binlog 導(dǎo)出成 SQL 語句,把所有的操作重放一遍,來實(shí)現(xiàn)數(shù)據(jù)的恢復(fù)
binlog 的另一個功能就是用來實(shí)現(xiàn)主從復(fù)制,它的原理就是從服務(wù)器讀取主服務(wù)器的 binlog,然后執(zhí)行一遍
配置方式和主從復(fù)制的實(shí)現(xiàn)原理在 Mycat 第二節(jié)課中有講述
有了這兩個日志之后,我們來看一下一條更新語句是怎么執(zhí)行的:

例如一條語句:updateteachersetname='盆魚宴'whereid=1;
- 先查詢到這條數(shù)據(jù),如果有緩存,也會用到緩存
- 把 name 改成盆魚宴,然后調(diào)用引擎的 API 接口,寫入這一行數(shù)據(jù)到內(nèi)存,同時記錄 redolog.這時 redolog 進(jìn)入 prepare 狀態(tài),然后告訴執(zhí)行器,執(zhí)行完成了,可以隨時提交
- 執(zhí)行器收到通知后記錄 binlog,然后調(diào)用存儲引擎接口,設(shè)置 redolog 為 commit 狀態(tài)
- 更新完成
這張圖片的重點(diǎn)(沒必要背下來):
- 先記錄到內(nèi)存,再寫日志文件
- 記錄 redolog 分為兩個階段
- 存儲引擎和 Server 記錄不同的日志
- 先記錄 redo,再記錄 binlog