MySQL·引擎特性·基於InnoDB的物理複製
最近有幸前去美國參加Percona Live 2016會議並分享了我們最近在MySQL複製上所做的工作,也就是基於InnoDB的物理複製。會後很多小夥伴私信我說分享的PPT太核心了,不太容易理解。因此本文主要針對分享的內容進行展開描述,希望能對大家有所幫助。
背景知識
在開始之前,你需要對InnoDB的事務系統有個基本的認識。如果您不瞭解,可以參考我之前的幾篇關於InnoDB的文章,包括InnoDB的事務子系統,事務鎖,redo log,undo log,以及崩潰恢復邏輯。在這裡我們簡單的概述一下幾個基本的概念:
事務ID:一個自增的序列號,每次開啟一個讀寫事務(或者事務從只讀轉換成讀寫模式)時分配並遞增,每更新256次後持久化到Ibdata的事務系統頁中。每個讀寫事務都必須保證擁有的ID是唯一的。
Read View: 用於一致性讀的snapshot,InnoDB裡稱為檢視;在需要一致性讀時開啟一個檢視,記錄當時的事務狀態快照,包括當時活躍的事務ID以及事務ID的上下水位值,以此用於判斷資料的可見性。
Redo Log:用於記錄對物理檔案的修改,所有對InnoDB物理檔案的修改都需要通過Redo保護起來,這樣才能從崩潰中恢復。
Mini Transaction(mtr):是InnoDB中修改物理塊的最小原子操作單位,同時也負責生產本地的redo日誌,並在提交mtr時將redo日誌拷貝到全域性log buffer中。
LSN: 一個一直在遞增的日誌序列號,在InnoDB中代表了從例項安裝到當前已經產生的日誌總量。可以通過LSN計算出其在日誌檔案中的位置。每個block在寫盤時,其最近一次修改的LSN也會記入其中,這樣在崩潰恢復時,無需Apply該LSN之前的日誌。
Undo Log: 用於儲存記錄被修改之前的舊版本,如果被多次修改,則會產生一個版本鏈。保留舊版本的目的是用於可重複讀。通過結合Undo和檢視控制來實現InnoDB的MVCC。
Binary Log: 構建在儲存引擎之上的統一的日誌格式;有兩種儲存方式,一種是記錄執行的SQL,另外一種是記錄修改的行記錄。Binlog本質上是一種邏輯日誌,因此能夠適用所有的儲存引擎,並進行資料複製。
原生複製的優缺點
MySQL的每條讀寫事務都需要維持兩份日誌,一份是redo log,一份是binary log。MySQL使用兩階段提交協議,只有當redo 和binlog都寫入磁碟時,事務才算真正的持久化了。如果只寫入redo,未寫入binlog,這樣的事務在崩潰恢復時需要回滾掉。MySQL通過XID來關聯InnoDB的事務和binlog。
MySQL的原生事務日誌複製有一些顯著的優點:
首先,相比InnoDB的redo log而言,Binary Log更加可讀,有成熟的配套工具來進行解析;由於記錄了行級別的更改。我們可以通過解析binlog,轉換成DML語句來將資料變更同步到異構資料庫。另外一種典型的做法是使用Binlog來失效構建在前端的cache。事實上,基於Binlog的資料流服務在阿里內部使用的非常廣泛,也是最重要的基礎設施之一。
其次由於Binary log是一種統一的日誌格式,你可以在主備上使用不同的儲存引擎,例如當你需要測試某種新的儲存引擎時,你可以搭建一個備庫,將所有表alter到新引擎,然後開啟資料複製進行觀察。
此外基於Binary Log你還可以構建起非常複雜的複製拓撲結構,尤其是在引入了GTID之後,這種優勢尤為明顯: 如果設計妥當,你可以實現相當複雜的複製結構。甚至可以做到多點寫入。總體使用起來非常靈活。
然而,也正是這種日誌架構可能會帶來一些問題:首先MySQL需要記錄兩份日誌:redo及binlog,只有當兩份日誌都fsync到磁碟,我們才能認為事務是持久化的,而眾所周知,fsync是一種開銷非常昂貴的操作。更多的日誌寫入還增加了磁碟IO壓力。這兩點都會影響到響應時間和吞吐量。
Binlog複製還會帶來複制延遲的問題。我們知道只有主庫事務提交後,日誌才會寫入到binlog檔案並傳遞到備庫,這意味著備庫至少延遲一個事務的執行時間。另外有些操作例如DDL,大事務等等,由於在備庫需要繼續保持事務完整性,這些執行時間很長的操作會長時間佔用某個worker執行緒,而協調執行緒會碰到複製同步點,導致後續的任務無法分發到其他空閒的worker執行緒。
MySQL是原生複製是MySQL生態的一個非常重要的組成部分。官方也在積極的改進其特性,例如MySQL5.7在這一塊就有非常顯著的改進。
Why Phsyical Replication
既然原生複製這麼成熟,優點這麼多,為什麼我們還要考慮基於物理日誌的複製呢?
首先最重要的原因就是效能!當我們事先了物理複製後,就可以關閉binlog和gtid,大大減少了資料寫盤量。這種情況下,最多隻需要一次fsync既可以將事務持久化到磁碟。例項整體的吞吐量和響應時間都得到了非常大的提升。
另外,通過物理複製,我們能獲得更加理想的物理複製效能。事務在執行過程中產生的redo log只要寫到檔案中,就會被傳送到備庫。這意味著我們可以同時在主備庫上執行事務,而無需等待主庫上執行完成。我們可以基於(space_id, page_no)來進行併發apply,同一個page上的變更也可以做到合併寫操作,相比傳統複製,具有更好的併發性。最重要的是,基於物理變更的複製,可以最大程度保證主備的資料總是一致的。
當然物理複製不是銀彈,當啟用該特性後,我們將只能支援InnoDB儲存引擎;我們也很難去設計多點寫複製拓撲。物理複製無法取代原生複製,而是應對特定的場景,例如需求高併發DML效能的場景。
因此在正式開始前,我們設定了這些前提:1.主庫上不應該有任何限制; 2.備庫上只允許執行查詢操作,不允許通過使用者介面對資料產生任何的變更。
下文預設MySQL已包含如下特性:
- 沒有隻讀事務連結串列,並且不為只讀事務分配事務ID
- 使用全域性事務ID陣列來構建read view快照
- 所有MySQL庫下的系統表都使用InnoDB儲存引擎
High Level Architecture
複製架構
這裡複製的基礎架構和原生複製類似,但程式碼是完全獨立的。如下圖所示:
首先,我們在備庫上配置好連線後,執行START INNODB SLAVE,備庫上會開啟一個io執行緒,同時InnoDB層啟動一個Log Apply協調執行緒以及多個worker執行緒。
IO執行緒建立和主庫的連線,併傳送一個dump請求,請求的內容包括:
master_uuid: 最近備庫上日誌最初產生所在的例項的server_uuid
start_lsn: 開始複製的點
在主庫上,一個log_dump執行緒被建立,先檢查dump請求是否是合法的,如果合法,就去從本地的ib_logfile中讀取日誌,併傳送到備庫。
備庫IO執行緒在接受到日誌後,將其拷貝到InnoDB的Log Buffer中,然後呼叫log_write_up_to將其寫入到本地的ib_logfile檔案中。
Log Apply協調執行緒被喚醒,從檔案中讀取日誌進行解析,並根據fold(space id ,page no)% (n_workers + 1)進行分發,系統表空間的變更存放到sys hash中,使用者表空間的變更儲存到user hash中。協調執行緒在解析&&分發完畢後,也會參與到日誌apply中。
當Apply日誌時,我們總是先應用系統表空間,再是使用者表空間。原因是我們需要保證undo日誌先應用,否則外部查詢檢索使用者表的btree,試圖通過回滾段指標查詢undo page,可能對應的Undo還沒構成。
日誌檔案管理
要實現上述架構,第一個要解決的問題是需要重新整理InnoDB的日誌檔案。 因為原生邏輯中,InnoDB採用迴圈寫檔案的方式,例如當我們設定innodb_log_files_in_group為4時,會建立4個ib logfile檔案。當第四個檔案寫滿時,會回到第一個檔案迴圈寫入。但是在物理複製架構下,我們需要保留老的日誌檔案,這些檔案既可以防止例如網路出現問題,日誌未曾及時傳送到備庫,也可以用於備份目的。
我們像binlog那樣,噹噹前日誌檔案寫滿時,則切換到下一個日誌檔案,檔案的序號總是向前遞增的。然而這裡需要解決的一個問題是:切換檔案需要儘量減小對效能的影響,我們引入了獨立的後臺執行緒,並允許已被清理的日誌檔案重用。
和binlog類似,我們也需要清理已經沒用的日誌檔案,既需要提供介面,由使用者手動清理,也可以開啟後臺執行緒自動判斷並進行清理,但兩種方案都需要滿足條件:
- 不允許超過當前checkpoint所在的檔案
- 如果有正在連線的備庫,則不允許清理尚未傳送到備庫的日誌
檔案架構如下圖所示:
這裡我們增加了一個新的檔案ib_checkpoint,原因是原生邏輯中,checkpoint資訊是儲存在ib_logfile0中的,而在新的架構下,該檔案可能被刪除掉,我們需要單獨對checkpoint資訊進行儲存,包含checkpoint no, checkpoint lsn, 以及該Lsn所在的日誌檔案號及檔案內偏移量。
後臺清理執行緒被稱為log purge thread,當該執行緒被喚醒被執行清理操作時,將目標日誌檔案rename到以purged作為字首,並放到一個回收池中,如果池子滿了,則直接刪除掉。
為了避免日誌切換到新檔案時造成的效能抖動,後臺log file allocate執行緒總是預先將下一個檔案準備好,也就是說,當前正在寫第N個檔案,後臺執行緒會被喚醒,並建立好第N+1個檔案。這樣對前臺執行緒的影響僅僅是關閉並開啟新檔案控制程式碼。
log file allocate執行緒在準備下一個檔案時,先嚐試從回收池中獲取檔案,並進行必要的判斷(確保下一個檔案開始的LSN轉換成block no後不和檔案內的內容重疊),如果可以使用,則直接取出來並rename為下一個檔名。如果回收池無可用檔案,則建立檔案,並extend到指定的大小。通過這種方式,我們儘量保證了效能的平緩。
例項角色
和原生複製不同,對於備庫,我們總是不允許做任何的資料變更,這種行為不受是否重啟,是否奔潰而影響,只受failover影響。一臺備庫無論重啟多少次總是為備庫。
日誌最初產生的伺服器我們稱為日誌源例項。日誌可能通過複雜的複製拓撲傳遞到多級級聯例項上。但所有的這些備庫都應具有相同的源例項資訊。我們需要通過這個資訊來判斷一個dump請求是否是合法的,例如作為備庫,所有dump的日誌都應產自同一個日誌源例項,除非在複製拓撲中發生了failover。
我們為例項定義了三種狀態:master, slave,以及upgradable-slave;其中第三種是一種中間狀態,只在failover時產生。
這些狀態資訊被持久化到本地檔案innodb_repl.info檔案中。同時也單獨儲存了日誌源例項的server_uuid。
我們以下圖為例:
server 1的uuid為1,和檔案中記錄的uuid相同,因此認為該例項為master;
server 2的uuid為2,和檔案中記錄的uuid不同,因為該例項為slave;
server 3的uuid為3,但檔案中記錄的值為0,表明最近剛發生過一次failover(server 1 和server 2發生過一次切換),但還沒來得及獲取到切換日誌,因此該例項角色為upgradable-slave
innodb_repl.info檔案維持了所有的複製和failover狀態資訊,很顯然,如果我們想從已有的拓撲中restore出一個新的例項,對應的innodb_repl.info檔案也要拷貝出來。
後臺執行緒
有些後臺執行緒可能對資料產生變更,因此在備庫上我們需要禁止這些執行緒:
- 不允許開啟Purge執行緒
- master執行緒不允許去做ibuf merge之類的工作,只負責定期做lazy checkpoint
- dict_stats執行緒只負責更新表的記憶體統計資訊,不可以觸發統計資訊的物理儲存。
此外備庫的page cleaner執行緒的刷髒演算法也需要重新調整以儘量平緩,不要影響到日誌apply。
MySQL Server層資料複製
檔案操作複製
為了實現Server-Engine的架構,MySQL在Server層另外冗餘了一些後設資料資訊,以在儲存引擎之上建立統一的標準。這些後設資料檔案包括FRM,PAR,DB.OPT,TRG,TRN以及代表資料庫的目錄。對這些檔案和目錄的操作都沒有寫到redo中。
為了能夠實現檔案層的操作,我們需要將檔案變更操作寫到日誌中,主要擴充套件了三種新的日誌型別:
MLOG_METAFILE_CREATE: [FIL_NAME | CONTENT]
MLOG_METAFILE_RENAME: [ORIGINAL_NAME | TARGET_NAME]
MLOG_METAFILE_DELETE: [FIL_NAME]
這裡包含了三種操作,檔案的建立,重新命名及刪除。注意這裡沒有修改檔案操作,原因是Server層總是通過建立新檔案,刪除舊檔案的方式來進行後設資料更新。
DDL複製
當MySQL在執行DDL修改後設資料時,是不允許訪問表空間的,否則可能導致各種異常錯誤。MySQL使用排他的MDL鎖來阻塞使用者訪問。我們需要在備庫保持相同的行為。這就需要識別修改後設資料的起點和結束點。我們引入兩類日誌來進行標識。
Name | Write On Master | Apply On Slave |
---|---|---|
MLOG_METACHANGE_BEGIN | 在獲取MDL鎖,修改後設資料之前寫入 | 獲取表上的顯式排他MDL鎖,同時失效該表的所有table cache物件 |
MLOG_METACHANGE_END | 在釋放MDL鎖之前寫入 | 釋放表上的MDL鎖 |
舉個簡單的例子:
執行: CREATE TABLE t1 (a INT PRIMARY KEY, b INT);
從Server層產生的日誌包括:
* MLOG_METACHANGE_START
* MLOG_METAFILE_CREATE (test/t1.frm)
* MLOG_METACHANGE_END
執行: ALTER TABLE t1 ADD KEY (b);
從Server層產生的日誌包括:
* Prepare Phase
MLOG_METACHANGE_START
MLOG_METAFILE_CREATE (test/#sql-3c36_1.frm)
MLOG_METACHANGE_END
* In-place build…slow part of DDL
* Commit Phase
MLOG_METACHANGE_START
MLOG_METAFILE_RENAME(./test/#sql-3c36_1.frm to ./test/t1.frm)
MLOG_METACHANGE_END
然而後設資料修改開始點和結束點所代表的兩個日誌並不是原子的,這意味著主庫上在修改後設資料的過程中如果crash了,就會丟失後面的結束標記。備庫可能一直持有這個表上的MDL鎖無法釋放。為了解決這個問題,我們在主庫每次崩潰恢復後,都寫一條特殊的日誌,通知所有連線的備庫釋放其持有的所有MDL排他鎖。
另外一個問題存在於備庫,舉個例子,執行MLOG_METACHANGE_START後,做一次checkpoint,在接受到MLOG_METACHANGE_END之前crash。當備庫例項從崩潰中恢復時,需要能夠繼續保持MDL鎖,避免使用者訪問。
為了能夠恢復MDL,首先我們需要控制checkpoint的LSN,保證不超過所有未完成後設資料變更的最老的開始點;其次,在重啟時蒐集未完成後設資料變更的表名,並在崩潰恢復完成後依次把MDL 排他鎖加上。
Cache失效
在Server層還維護了一些Cache結構,然而資料的更新是體現在物理層的,備庫在應用完redo後,需要感知到哪些Cache是需要進行更新的,目前來看主要有以下幾種情況:
- 許可權操作,備庫上需要進行ACL Reload,才能讓新的許可權生效
- 儲存過程操作,例如增刪儲存過程,在備庫需要遞增一個版本號,以告訴使用者執行緒重新載入cache
- 表級統計資訊,主庫上通過更新的行的數量來觸發表統計資訊更新;但在備庫上,所有的變更都是基於塊級別的,並不能感知到變化了多少行。因此每次主庫更新統計資訊時同時寫一條日誌到redo中,通知備庫進行記憶體統計資訊更新
備庫MVCC
檢視控制
備庫一致性讀的最基本要求是使用者執行緒不應該看到主庫上尚未執行完成的事務所產生的變更。換句話說,當備庫上開啟一個read view時,在該時間點,如果有尚未提交的事務變更,這些變更應該是不可見的。
基於此,我們需要知道一個事務的開始點和結束點。我們增加了兩種日誌來進行標示:
MLOG_TRX_START: 在主庫上為一個讀寫事務分配事務ID後,同時生成一條日誌,日誌中記錄了該ID的值;由於是持有trx_sys->mutex鎖生成的日誌記錄,因此保證寫入redo的事務ID是有序的。
MLOG_TRX_COMMIT: 在事務提交階段,標記undo狀態為提交後,寫入該型別日誌,記錄對應事務的事務ID
在備庫上,我們通過這兩類日誌來重現事務場景,具體的我們採用一種延遲構建的方式:只有在完成apply一批日誌後才對全域性事務狀態進行更新:
- 在apply一批日誌時,選擇其中最大的MLOG_TRX_START+1來更新trx_sys->max_trx_id
- 所有未提交的事務ID被加入到全域性事務陣列中。
如下圖所示:
在初始狀態下,最大未分配事務id(trx_sys->max_trx_id)為11,活躍事務ID陣列為空;
在執行第一批日誌期間,所有使用者請求構建的檢視都具有一樣的結構。即low_limit_id = up_limit_id = 11,本地trx_ids為空;
在執行完第一批日誌後,max_trx_id被被更新成12 + 1,未完成的事務ID 12加入到全域性活躍事務ID陣列中。
依次類推。該方案是複製效率和資料可見性的一個權衡。
注意如果主庫崩潰,那麼可能存在事務存在開始點,但丟失結束點的情況,因此主庫在崩潰恢復後寫入一條特殊的日誌,以告訴所有的備庫去通過遍歷undo slot重新初始化全域性事務狀態。
Purge控制
既然要維持MVCC特性,那麼作為一致性讀的重要組成部分的Undo log,就需要對其進行控制,那些仍然可能被讀檢視引用的Undo不應該被清理掉。這裡我們提供了兩種方式來供使用者選擇:
方案一:控制備庫上的Purge
當主庫每次Purge時,都將當前Purge的最老快照寫入redo;備庫在拿到這個快照後,會去判斷其和當期例項上活躍的最老檢視是否有可見性上的重疊,並等待直到這些檢視關閉;我們也提供了一個超時選項,當等待時間過長時,就直接更新本地Purge檢視,使用者執行緒將獲得一個錯誤碼DB_MISSING_HISTORY
這種方案的缺點很明顯:當備庫讀負載很重,或者存在大查詢時,備庫可能產生複製延遲。
方案二:控制主庫上的Purge,備庫定期向其連線的例項傳送反饋,反饋的內容為當前可安全Purge的最小ID。如下圖所示:
這種方案的缺點是,犧牲了主庫的Purge效率,在整個複製拓撲上,只要有長時間未關閉的檢視,都有可能引起主庫上的Undo膨脹。
B-TREE結構變更復制
當發生B-TREE的結構變更時,例如Page合併或分裂,我們需要禁止使用者執行緒對btree進行檢索。
解決方案很簡單:當主庫上的mtr在commit時,如果是持有索引的排他鎖,並且一個mtr中的變更超過一個page時,則將涉及的索引id寫到日誌中;備庫在解析到該日誌時,會產生一個同步點:完成已經解析的日誌;獲取索引X鎖;完成日誌組Apply;釋放索引X鎖。
複製Change Buffer
備庫change buffer合併
Change buffer是InnoDB的一種特殊的快取結構,其本質上是一棵存在於ibdata的btree。當修改使用者表空間的二級索引頁時,如果對應的page不在記憶體中,該操作將可能被記錄到change buffer中,從而減少了二級索引的隨機IO,並達到了合併更新的效果。
隨後當對應的page被讀入記憶體時,會進行一次merge操作;後臺Master執行緒也會定期發起Merge。關於change buffer本文不做深入,感興趣的可以閱讀我之前的這篇月報
然而在備庫,我們需要保證對資料不做任何的變更,只讀操作不應該對物理資料產生任何的影響。為了實現這一點,我們採用瞭如下方式來解決這個問題:
- 當將Page讀入記憶體,如果發現其需要進行ibuf merge,則為其分配一個shadow page,將未修改的資料頁儲存到其中
- 將change buffer記錄合併到資料頁上,同時關閉該Mtr的redo log,這樣修改後的Page就不會放到flush list上了。
- change buffer bitmap頁和change buffer btree上的頁都不允許產生任何的修改。
- 當資料頁從buffer pool驅逐或者被log apply執行緒請求時,shadow page會被釋放掉
另外一個問題是,主備庫的記憶體狀態可能是不一樣的,例如一個Page在主庫上未讀入記憶體,因此為其快取到change buffer。但備庫上這個page已經存在於buffer pool了。為了保證資料一致性,在備庫上我們需要將新的change buffer記錄合併到這個page上。
具體的,當在備庫解析到新的change buffer entry時,如果對應的Page已經在記憶體中了,就對其打個標籤。隨後使用者執行緒如果訪問到這個page,就從shadow page中恢復出未修改的Page(如果有shadow page),再進行一次change buffer合併操作。
複製change buffer合併
由於一次change buffer merge涉及到ibuf bitmap page,二級索引頁,change buffer btree三類,其存在嚴格的先後關係,而在備庫上,我們是並行進行日誌apply的。為了保證在合併的過程中,使用者執行緒不能訪問到正在被修改的資料頁。我們增加了新的日誌型別:
MLOG_IBUF_MERGE_START : 在主庫上進行ibuf merge之前寫入;當備庫解析到該日誌時,apply所有已解析的日誌,獲取對應的block,並加上排他鎖;如果有shadow page的話,則將未修改的資料恢復出來,並釋放shadow page。
MLOG_IBUF_MERGE_END: 在主庫上清除ibuf bitmap page上對應位後寫入;備庫解析到時apply所有已解析的日誌並釋放block鎖。
很顯然該方案構成了一個效能瓶頸點,可能會影響到複製效能。後續再研究下有沒有更完美的解決方案。
Failover
Planned Failover
當執行計劃中的切換時,我們需要執行嚴格的步驟,以確保在切換時所有的例項處於一致的狀態。具體的分為4步:
Step1: 主庫上執行降級操作,狀態從MASTER修改成UPGRADABLE-SLAVE;這裡會退出所有的讀寫事務,掛起或退出哪些可能修改資料的後臺執行緒;同時一條MLOG_DEMOTE日誌寫入到redo檔案中。
Step2: 所有連線的備庫在讀取到MLOG_DEMOTE日誌後,將自己的狀態修改為UPGRADALE-SLAVE;
Step3: 任意挑選一個複製拓撲中的例項,將其提升為主庫,同時初始化各種記憶體狀態值;並寫入一條型別為MLOG_PROMOTE的日誌;
Step4: 所有連線過來的備庫在解析到MLOG_PROMOTE日誌後,將自己的狀態從UPGRADABLE-SLAVE修改成SLAVE
Unplanned Failover
然而多數情況下,切換都是在意外情況下發生的,為了減少當機時間,我們需要選擇一個備庫快速接管使用者負載。這種場景下需要解決的問題是:老主庫在恢復訪問後,如何確保和新主庫的狀態一致。更具體的說,如果老主庫上還有一部分日誌還沒傳送到新主庫,這部分的不一致資料該怎麼恢復。
我們採用覆蓋寫的方法來解決這一問題:
- 首先禁止老主庫上所有的訪問,包括查詢;同時將老主庫降級成備庫
- 獲取新主庫切換時的LSN,然後在老主庫上從這個LSN開始遍歷redo日誌,蒐集所有影響到(space id, page no),如果發現有DDL操作,則認為恢復失敗,需要從外部第三方工具進行比較同步,或者重做例項
- 從新主庫上獲取到這些page並在本地進行覆蓋寫操作。
- 完成覆蓋寫後,將多出來的redo log從磁碟上truncate掉,同時更新checkpoint資訊
- 恢復複製,並開啟讀請求
測試及效能
我們測試了三個版本的效能:
- ALI_RDS_56_redo: 使用物理複製,並禁止binlog
- ALI_RDS_56: 目前RDS的MySQL版本
- MySQL5629: Upstream 5.6.29
測試環境
- Sysbench 0.5
- 50 tables, each with 200,000 records
- Buffer pool size: 16GB, 8 buffer pool instance, all data fit in memory
- innodb_thread_concurrency = 32
- Log file group is big enough, so no sharp checkpoint will happen
- Gtid disabled
- 2 threads per core; 6 cores per socket; 2 CPU sockets
Update_non_index (TPS)
Update_non_index (RT)
Update_non_index(TPS)
Update_non_index(RT)
相關文章
- MySQL 引擎特性:InnoDB Buffer PoolMySql
- MySQL innodb表使用表空間物理檔案複製表MySql
- MySQL 引擎特性:InnoDB 同步機制MySql
- MySQL 引擎特性:InnoDB IO 子系統MySql
- MySQL 引擎特性:InnoDB崩潰恢復MySql
- Mysql基於GTID的複製模式MySql模式
- 5-5配置Mysql複製 基於日誌點的複製MySql
- mysql 基於日誌的主從複製MySql
- three.js cannon.js物理引擎之製作擁有物理特性的汽車JS
- MySQL·引擎特性·InnoDB事務子系統介紹MySql
- 談談MySQL InnoDB儲存引擎事務的ACID特性MySql儲存引擎
- Mysql 基於GTID主從複製MySql
- MySQL 5.7基於GTID的主從複製MySql
- mysql5.6複製新特性MySql
- PhysicsJS:基於JavaScript的強大的物理引擎JSJavaScript
- MySQL基於binlog主從複製配置MySql
- mysql複製和記憶體引擎的表MySql記憶體
- mysql之 MySQL 主從基於position複製原理概述MySql
- mysql複製基礎MySql
- MySQL MyISAM引擎和InnoDB引擎對於單表大小限制的總結MySql
- 基於 Docker 的 MySQL 主從複製搭建(真正弄懂)DockerMySql
- MySQL 複製全解析 Part10 基於GTID的MySQL複製的一些限制MySql
- Mysql innodb引擎(二)鎖MySql
- MySQL InnoDB儲存引擎MySql儲存引擎
- 零基礎製作物理引擎--創造世界
- 零基礎製作物理引擎--創造力量
- mysql之 MySQL 主從基於 GTID 複製原理概述MySql
- MySQL 複製全解析 Part 9 一步步搭建基於GTID的MySQL複製MySql
- MySQL 5.7 基於GTID搭建主從複製MySql
- Mysql5.6主從複製-基於binlogMySql
- 【Mysql】mysql公開課之-mysql5.7複製特性MySql
- 主從複製、雙主複製及半同步複製、以及基於SSL的複製
- Mysql innodb引擎(三) 事務MySql
- MySQL 5.7 InnoDB引擎簡介MySql
- 【Mysql】InnoDB 引擎中的頁目錄MySql
- Mysql 5.7 基於組複製(MySQL Group Replication) - 運維小結MySql運維
- MySQL 主從複製搭建,基於日誌(binlog)MySql
- 基於 Docker 的 MySQL 主從複製搭建及原理(真正弄懂)DockerMySql