MySQL實戰45講



 

 源碼安裝完MySQL之后,使用Debug模式啟動
mysqld --debug --console &后,
mysql> create database wxb;
Query OK, 1 row affected (0.01 sec)

mysql> use wxb;
Database changed
mysql> create table t(a int);
Query OK, 0 rows affected (0.01 sec)

mysql> select * from t where k=1;
ERROR 1054 (42S22): Unknown column 'k' in 'where clause'

T@4: | | | | | | | | | error: error: 1054 message: 'Unknown column 'k' in 'where clause''

Complete optimizer trace:

 

入手比較晚,說下個人對為什么是分析器的看法。
連接器:門衛,想進請出示准入憑證(工牌、邀請證明一類)。“你好,你是普通員工,只能進入辦公大廳,不能到高管區域”此為權限查詢。
分析器:“您需要在公司里面找一張頭發是黑色的桌子?桌子沒有頭發啊!臣妾做不到”
優化器:“要我在A B兩個辦公室找張三和李四啊?那我應該先去B辦公室找李四,然后請李四幫我去A辦公室找張三,因為B辦公室比較近且李四知道張三具體工位在哪”
執行器:“好了,找人的計划方案定了,開始行動吧,走你!糟糕,剛門衛大哥說了,我沒有權限進B辦公室”

連接完成后,如果你沒有后續的動作,這個連接就處於空閑狀態,你可以在 show processlist 命令中看到它。
文本中這個圖是 show processlist 的結果,其中的 Command 列顯示為“Sleep”的這一行,就表示現在系統里面有一個空閑連接。

客戶端如果太長時間沒動靜,連接器就會自動將它斷開。這個時間是由參數 wait_timeout 控制的,默認值是 8 小時。
如果在連接被斷開之后,客戶端再次發送請求的話,就會收到一個錯誤提醒: Lost connection to MySQL server during query。
這時候如果你要繼續,就需要重連,然后再執行請求了。

數據庫里面,長連接是指連接成功后,如果客戶端持續有請求,則一直使用同一個連接。
短連接則是指每次執行完很少的幾次查詢就斷開連接,下次查詢再重新建立一個。

建立連接的過程通常是比較復雜的,所以我建議你在使用中要盡量減少建立連接的動作,也就是盡量使用長連接。
但是全部使用長連接后,你可能會發現,有些時候 MySQL 占用內存漲得特別快,這是因為 MySQL 在執行過程中臨時使用的內存是管理在連接對象里面的。這些資源會在連接斷開的時候才釋放。
所以如果長連接累積下來,可能導致內存占用太大,被系統強行殺掉(OOM),從現象看就是 MySQL 異常重啟了。
怎么解決這個問題呢?你可以考慮以下兩種方案。
定期斷開長連接。使用一段時間,或者程序里面判斷執行過一個占用內存的大查詢后,斷開連接,之后要查詢再重連。
如果你用的是 MySQL 5.7 或更新版本,可以在每次執行一個比較大的操作后,通過執行 mysql_reset_connection 來重新初始化連接資源。這個過程不需要重連和重新做權限驗證,但是會將連接恢復到剛剛創建完時的狀態。

MySQL 整體來看,其實就有兩塊:
一塊是 Server 層,它主要做的是 MySQL 功能層面的事情;
還有一塊是引擎層,負責存儲相關的具體事宜。



MySQL 里面最重要的兩個日志,即物理日志 redo log邏輯日志 binlog
redo log 用於保證 crash-safe 能力。

現在由於redo是屬於InnoDB引擎,所以必須要有binlog,因為你可以使用別的引擎
binlog還不能去掉。

一個原因是,redolog只有InnoDB有,別的引擎沒有。
另一個原因是,redolog是循環寫的,不持久保存,binlog的“歸檔”這個功能,redolog是不具備的。


Redo log不是記錄數據頁“更新之后的狀態”,而是記錄這個頁 “做了什么改動”。
Binlog有兩種模式,statement 格式的話是記sql語句, row格式會記錄行的內容,記兩條,更新前和更新后都有。

redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)。
我想你肯定會問,為什么會有兩份日志呢?
因為最開始 MySQL 里並沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,binlog 日志只能用於歸檔。
而 InnoDB 是另一個公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統——也就是 redo log 來實現 crash-safe 能力。
這兩種日志有以下三點不同。
redo log 是 InnoDB 引擎特有的;
binlog 是 MySQL 的 Server 層實現的,所有引擎都可以使用。

redo log 是物理日志,記錄的是“在某個數據頁上做了什么修改”;
binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
redo log 是循環寫的,空間固定會用完;
binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個,並不會覆蓋以前的日志。


 

 你可能注意到了,最后三步看上去有點“繞”,將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。



兩階段提交為什么必須有“兩階段提交”呢?
這是為了讓兩份日志之間的邏輯一致。
要說明這個問題,我們得從文章開頭的那個問題說起:怎樣讓數據庫恢復到半個月內任意一秒的狀態?
前面我們說過了,binlog 會記錄所有的邏輯操作,並且是采用“追加寫”的形式
如果你的 DBA 承諾說半個月內可以恢復,那么備份系統中一定會保存最近半個月的所有 binlog,同時系統會定期做整庫備份。這里的“定期”取決於系統的重要性,可以是一天一備,也可以是一周一備。

由於 redo log 和 binlog 是兩個獨立的邏輯,如果不用兩階段提交,要么就是先寫完 redo log 再寫 binlog,或者采用反過來的順序。我們看看這兩種方式會有什么問題。


“binlog沒有被用來做崩潰恢復”,
歷史上的原因是,這個是一開始就這么設計的,所以不能只依賴binlog。
操作上的原因是,binlog是可以關的,你如果有權限,可以set sql_log_bin=0關掉本線程的binlog日志。 所以只依賴binlog來恢復就靠不住。


1.首先客戶端通過tcp/ip發送一條sql語句到server層的SQL interface
2.SQL interface接到該請求后,先對該條語句進行解析,驗證權限是否匹配
3.驗證通過以后,分析器會對該語句分析,是否語法有錯誤等
4.接下來是優化器器生成相應的執行計划,選擇最優的執行計划
5.之后會是執行器根據執行計划執行這條語句。在這一步會去open table,如果該table上有MDL,則等待。
如果沒有,則加在該表上加短暫的MDL(S)
(如果opend_table太大,表明open_table_cache太小。需要不停的去打開frm文件)
6.進入到引擎層,首先會去innodb_buffer_pool里的data dictionary(元數據信息)得到表信息
7.通過元數據信息,去lock info里查出是否會有相關的鎖信息,並把這條update語句需要的
鎖信息寫入到lock info里(鎖這里還有待補充)
8.然后涉及到的老數據通過快照的方式存儲到innodb_buffer_pool里的undo page里,並且記錄undo log修改的redo
(如果data page里有就直接載入到undo page里,如果沒有,則需要去磁盤里取出相應page的數據,載入到undo page里)
9.在innodb_buffer_pool的data page做update操作。並把操作的物理數據頁修改記錄到redo log buffer里
由於update這個事務會涉及到多個頁面的修改,所以redo log buffer里會記錄多條頁面的修改信息。
因為group commit的原因,這次事務所產生的redo log buffer可能會跟隨其它事務一同flush並且sync到磁盤上
10.同時修改的信息,會按照event的格式,記錄到binlog_cache中。(這里注意binlog_cache_size是transaction級別的,不是session級別的參數,
一旦commit之后,dump線程會從binlog_cache里把event主動發送給slave的I/O線程)
11.之后把這條sql,需要在二級索引上做的修改,寫入到change buffer page,等到下次有其他sql需要讀取該二級索引時,再去與二級索引做merge
(隨機I/O變為順序I/O,但是由於現在的磁盤都是SSD,所以對於尋址來說,隨機I/O和順序I/O差距不大)
12.此時update語句已經完成,需要commit或者rollback。這里討論commit的情況,並且雙1
13.commit操作,由於存儲引擎層與server層之間采用的是內部XA(保證兩個事務的一致性,這里主要保證redo log和binlog的原子性),
所以提交分為prepare階段與commit階段
14.prepare階段,將事務的xid寫入,將binlog_cache里的進行flush以及sync操作(大事務的話這步非常耗時)
15.commit階段,由於之前該事務產生的redo log已經sync到磁盤了。所以這步只是在redo log里標記commit
16.當binlog和redo log都已經落盤以后,如果觸發了刷新臟頁的操作,先把該臟頁復制到doublewrite buffer里,把doublewrite buffer里的刷新到共享表空間,然后才是通過page cleaner線程把臟頁寫入到磁盤中
老師,你看我的步驟中有什么問題嘛?我感覺第6步那里有點問題,因為第5步已經去open table了,第6步還有沒有必要去buffer里查找元數據呢?這元數據是表示的系統的元數據嘛,還是所有表的?
作者回復: 其實在實現上5是調用了6的過程了的,所以是一回事。MySQL server 層和InnoDB層都保存了表結構,所以有書上描述時會拆開說。





 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM