者 | Amazing10
責編 | 屠敏
本文為業(yè)余碼農投稿,已獲授權
還記得剛上研究生的時候,導師常掛在嘴邊的一句話,“科研的基礎不過就是數(shù)據(jù)而已。”如今看來,無論是人文社科,還是自然科學,或許都可在一定程度上看作是數(shù)據(jù)的科學。
倘若剝開研究領域的外衣,將人的操作抽象出來,那么科研的過程大概就是根據(jù)數(shù)據(jù)流動探索其中的未知信息吧。當然科學研究的范疇涵蓋甚廣,也不是一兩句話能夠拎得清的。不過從這個角度上的闡述,也只是為了引出數(shù)據(jù)的重要性。
在當今社會,充斥著大量的數(shù)據(jù)。從眾多APP上的賬戶資料到銀行信用體系等個人檔案,都離不開對大量數(shù)據(jù)的組織、存儲和管理。而這,便是數(shù)據(jù)庫存在的目的和價值。
目前數(shù)據(jù)庫的類型主要分為兩種,一種是關系型數(shù)據(jù)庫,另一種是非關系型數(shù)據(jù)庫(NoSQL)。而我們今天的主角MySQL就是關系型數(shù)據(jù)庫中的一種。
本文結構
關系型數(shù)據(jù)庫,顧名思義,是指存儲的數(shù)據(jù)之間具有關系。這種所謂的關系通常用二維表格中的行列來表示,即一個二維表的邏輯結構能夠反映表中數(shù)據(jù)的存儲關系。
概念總是拗口難懂的。那么簡單來說,關系型數(shù)據(jù)庫的存儲就是按照表格進行的。數(shù)據(jù)的存儲實際上就是對一個或者多個表格的存儲。通過對這些表格進行分類、合并、連接或者選取等運算來實現(xiàn)對數(shù)據(jù)庫的管理。常見的關系型數(shù)據(jù)庫有MySQL、Oracle、DB2和SqlServer等。
非關系型數(shù)據(jù)庫(NoSQL)是相對于關系型數(shù)據(jù)庫的一種泛指,它的特點是去掉了關系型數(shù)據(jù)庫中的關系特性,從而可獲得更好的擴展性。NoSQL并沒有嚴格的存儲方式,但采用不同的存儲結構都是為了獲得更高的性能和更高的并發(fā)。
NoSQL根據(jù)存儲方式可分為四大類,鍵值存儲數(shù)據(jù)庫、列存儲數(shù)據(jù)庫、文檔型數(shù)據(jù)庫和圖形數(shù)據(jù)庫。這四種數(shù)據(jù)的存儲原理不盡相同,因而在應用場景上也有些許的差異。一般常用的有作為數(shù)據(jù)緩存的redis和分布式系統(tǒng)的HBase。目前常見的數(shù)據(jù)庫排名可見網站:
https://db-engines.com/en/ranking
關系型數(shù)據(jù)庫與非關系型數(shù)據(jù)庫本質上的區(qū)別就在于存儲的數(shù)據(jù)是否具有一定的邏輯關系,由此產生的兩類數(shù)據(jù)庫看的性能和優(yōu)劣勢上也有一定的區(qū)別。二者對比可見下圖。
關系型數(shù)據(jù)庫與NoSQL的優(yōu)缺點對比
在關系型數(shù)據(jù)庫中,MySQL可以說是其中的王者。它是目前最流行的數(shù)據(jù)庫之一,由瑞典 MySQL AB 公司開發(fā),目前屬于 Oracle 公司。MySQL數(shù)據(jù)庫具有以下幾個方面的優(yōu)勢:
體積小、速度快;
代碼開源,采用了 GPL 協(xié)議,可以修改源碼來開發(fā)自己的 MySQL 系統(tǒng);
支持大型的數(shù)據(jù)庫,可以處理擁有上千萬條記錄的大型數(shù)據(jù)庫;
使用標準的 SQL 數(shù)據(jù)語言形式,并采用優(yōu)化的 SQL 查詢算法,有效地提高查詢速度;
使用 C 和 C++ 編寫,并使用多種編譯器進行測試,保證源代碼的可移植性;
可運行在多個系統(tǒng)上,并且支持多種語言;
核心程序采用完全的多線程編程,可以靈活地為用戶提供服務,充分利用CPU資源。
MySQL的邏輯架構可分為四層,包括連接層、服務層、引擎層和存儲層,各層的接口交互及作用如下圖所示。需要注意的是,由于本文將主要講解事務的實現(xiàn)原理,因此下文針對的都是InnoDB引擎下的情況。
連接層: 負責處理客戶端的連接以及權限的認證。
服務層: 定義有許多不同的模塊,包括權限判斷,SQL接口,SQL解析,SQL分析優(yōu)化, 緩存查詢的處理以及部分內置函數(shù)執(zhí)行等。MySQL的查詢語句在服務層內進行解析、優(yōu)化、緩存以及內置函數(shù)的實現(xiàn)和存儲。
引擎層: 負責MySQL中數(shù)據(jù)的存儲和提取。MySQL中的服務器層不管理事務,事務是由存儲引擎實現(xiàn)的。其中使用最為廣泛的存儲引擎為InnoDB,其它的引擎都不支持事務。
存儲層: 負責將數(shù)據(jù)存儲于設備的文件系統(tǒng)中。
MySQL的邏輯架構
事務是MySQL區(qū)別于NoSQL的重要特征,是保證關系型數(shù)據(jù)庫數(shù)據(jù)一致性的關鍵技術。事務可看作是對數(shù)據(jù)庫操作的基本執(zhí)行單元,可能包含一個或者多個SQL語句。這些語句在執(zhí)行時,要么都執(zhí)行,要么都不執(zhí)行。
事務的執(zhí)行主要包括兩個操作,提交和回滾。
提交:commit,將事務執(zhí)行結果寫入數(shù)據(jù)庫。
回滾:rollback,回滾所有已經執(zhí)行的語句,返回修改之前的數(shù)據(jù)。
MySQL事務包含四個特性,號稱ACID四大天王。
原子性(Atomicity) :語句要么全執(zhí)行,要么全不執(zhí)行,是事務最核心的特性,事務本身就是以原子性來定義的;實現(xiàn)主要基于undo log日志實現(xiàn)的。
持久性(Durability :保證事務提交后不會因為宕機等原因導致數(shù)據(jù)丟失;實現(xiàn)主要基于redo log日志。
隔離性(Isolation) :保證事務執(zhí)行盡可能不受其他事務影響;InnoDB默認的隔離級別是RR,RR的實現(xiàn)主要基于鎖機制、數(shù)據(jù)的隱藏列、undo log和類next-key lock機制。
一致性(Consistency) :事務追求的最終目標,一致性的實現(xiàn)既需要數(shù)據(jù)庫層面的保障,也需要應用層面的保障。
事務的原子性就如原子操作一般,表示事務不可再分,其中的操作要么都做,要么都不做;如果事務中一個SQL語句執(zhí)行失敗,則已執(zhí)行的語句也必須回滾,數(shù)據(jù)庫退回到事務前的狀態(tài)。只有0和1,沒有其它值。
事務的原子性表明事務就是一個整體,當事務無法成功執(zhí)行的時候,需要將事務中已經執(zhí)行過的語句全部回滾,使得數(shù)據(jù)庫回歸到最初未開始事務的狀態(tài)。
事務的原子性就是通過undo log日志進行實現(xiàn)的。當事務需要進行回滾時,InnoDB引擎就會調用undo log日志進行SQL語句的撤銷,實現(xiàn)數(shù)據(jù)的回滾。
事務的持久性是指當事務提交之后,數(shù)據(jù)庫的改變就應該是永久性的,而不是暫時的。這也就是說,當事務提交之后,任何其它操作甚至是系統(tǒng)的宕機故障都不會對原來事務的執(zhí)行結果產生影響。
事務的持久性是通過InnoDB存儲引擎中的redo log日志來實現(xiàn)的,具體實現(xiàn)思路見下文。
原子性和持久性是單個事務本身層面的性質,而隔離性是指事務之間應該保持的關系。隔離性要求不同事務之間的影響是互不干擾的,一個事務的操作與其它事務是相互隔離的。
由于事務可能并不只包含一條SQL語句,所以在事務的執(zhí)行期間很有可能會有其它事務開始執(zhí)行。因此多事務的并發(fā)性就要求事務之間的操作是相互隔離的。這一點跟多線程之間數(shù)據(jù)同步的概念有些類似。
鎖機制
事務之間的隔離,是通過鎖機制實現(xiàn)的。當一個事務需要對數(shù)據(jù)庫中的某行數(shù)據(jù)進行修改時,需要先給數(shù)據(jù)加鎖;加了鎖的數(shù)據(jù),其它事務是不運行操作的,只能等待當前事務提交或回滾將鎖釋放。
鎖機制并不是一個陌生的概念,在許多場景中都會利用到不同實現(xiàn)的鎖對數(shù)據(jù)進行保護和同步。而在MySQL中,根據(jù)不同的劃分標準,還可將鎖分為不同的種類。
按照粒度劃分:行鎖、表鎖、頁鎖
按照使用方式劃分:共享鎖、排它鎖
按照思想劃分:悲觀鎖、樂觀鎖
鎖機制的知識點很多,由于篇幅不好全部展開講。這里對按照粒度劃分的鎖進行簡單介紹。
粒度:指數(shù)據(jù)倉庫的數(shù)據(jù)單位中保存數(shù)據(jù)的細化或綜合程度的級別。細化程度越高,粒度級就越??;相反,細化程度越低,粒度級就越大。
MySQL按照鎖的粒度劃分可以分為行鎖、表鎖和頁鎖。
行鎖:粒度最小的鎖,表示只針對當前操作的行進行加鎖;
表鎖:粒度最大的鎖,表示當前的操作對整張表加鎖;
頁鎖:粒度介于行級鎖和表級鎖中間的一種鎖,表示對頁進行加鎖。
這三種鎖是在不同層次上對數(shù)據(jù)進行鎖定,由于粒度的不同,其帶來的好處和劣勢也不一而同。
表鎖在操作數(shù)據(jù)時會鎖定整張表,因而并發(fā)性能較差;
行鎖則只鎖定需要操作的數(shù)據(jù),并發(fā)性能好。但是由于加鎖本身需要消耗資源(獲得鎖、檢查鎖、釋放鎖等都需要消耗資源),因此在鎖定數(shù)據(jù)較多情況下使用表鎖可以節(jié)省大量資源。
MySQL中不同的存儲引擎能夠支持的鎖也是不一樣的。MyIsam只支持表鎖,而InnoDB同時支持表鎖和行鎖,且出于性能考慮,絕大多數(shù)情況下使用的都是行鎖。
并發(fā)讀寫問題
在并發(fā)情況下,MySQL的同時讀寫可能會導致三類問題,臟讀、不可重復度和幻讀。
(1)臟讀:當前事務中讀到其他事務未提交的數(shù)據(jù),也就是臟數(shù)據(jù)。
以上圖為例,事務A在讀取文章的閱讀量時,讀取到了事務B為提交的數(shù)據(jù)。如果事務B最后沒有順利提交,導致事務回滾,那么實際上閱讀量并沒有修改成功,而事務A卻是讀到的修改后的值,顯然不合情理。
(2)不可重復讀:在事務A中先后兩次讀取同一個數(shù)據(jù),但是兩次讀取的結果不一樣。臟讀與不可重復讀的區(qū)別在于:前者讀到的是其他事務未提交的數(shù)據(jù),后者讀到的是其他事務已提交的數(shù)據(jù)。
以上圖為例,事務A在先后讀取文章閱讀量的數(shù)據(jù)時,結果卻不一樣。說明事務A在執(zhí)行的過程中,閱讀量的值被其它事務給修改了。這樣使得數(shù)據(jù)的查詢結果不再可靠,同樣也不合實際。
(3)幻讀:在事務A中按照某個條件先后兩次查詢數(shù)據(jù)庫,兩次查詢結果的行數(shù)不同,這種現(xiàn)象稱為幻讀。不可重復讀與幻讀的區(qū)別可以通俗的理解為:前者是數(shù)據(jù)變了,后者是數(shù)據(jù)的行數(shù)變了。
以上圖為例,當對0<閱讀量<100的文章進行查詢時,先查到了一個結果,后來查詢到了兩個結果。這表明同一個事務的查詢結果數(shù)不一,行數(shù)不一致。這樣的問題使得在根據(jù)某些條件對數(shù)據(jù)篩選的時候,前后篩選結果不具有可靠性。
隔離級別
根據(jù)上面這三種問題,產生了四種隔離級別,表明數(shù)據(jù)庫不同程度的隔離性質。
在實際的數(shù)據(jù)庫設計中,隔離級別越高,導致數(shù)據(jù)庫的并發(fā)效率會越低;而隔離級別太低,又會導致數(shù)據(jù)庫在讀寫過程中會遇到各種亂七八糟的問題。
因此在大多數(shù)數(shù)據(jù)庫系統(tǒng)中,默認的隔離級別時讀已提交(如Oracle)或者可重復讀RR(MySQL的InnoDB引擎)。
MVCC
又是一個難嚼的大塊頭。MVCC就是用來實現(xiàn)上面的第三個隔離級別,可重復讀RR。
MVCC:Multi-Version Concurrency Control,即多版本的并發(fā)控制協(xié)議。
MVCC的特點就是在同一時刻,不同事務可以讀取到不同版本的數(shù)據(jù),從而可以解決臟讀和不可重復讀的問題。
MVCC實際上就是通過數(shù)據(jù)的隱藏列和回滾日志(undo log),實現(xiàn)多個版本數(shù)據(jù)的共存。這樣的好處是,使用MVCC進行讀數(shù)據(jù)的時候,不用加鎖,從而避免了同時讀寫的沖突。
在實現(xiàn)MVCC時,每一行的數(shù)據(jù)中會額外保存幾個隱藏的列,比如當前行創(chuàng)建時的版本號和刪除時間和指向undo log的回滾指針。這里的版本號并不是實際的時間值,而是系統(tǒng)版本號。每開始新的事務,系統(tǒng)版本號都會自動遞增。事務開始時的系統(tǒng)版本號會作為事務的版本號,用來和查詢每行記錄的版本號進行比較。
每個事務又有自己的版本號,這樣事務內執(zhí)行數(shù)據(jù)操作時,就通過版本號的比較來達到數(shù)據(jù)版本控制的目的。
另外,InnoDB實現(xiàn)的隔離級別RR時可以避免幻讀現(xiàn)象的,這是通過next-key lock機制實現(xiàn)的。
next-key lock實際上就是行鎖的一種,只不過它不只是會鎖住當前行記錄的本身,還會鎖定一個范圍。比如上面幻讀的例子,開始查詢0<閱讀量<100的文章時,只查到了一個結果。next-key lock會將查詢出的這一行進行鎖定,同時還會對0<閱讀量<100這個范圍進行加鎖,這實際上是一種間隙鎖。間隙鎖能夠防止其他事務在這個間隙修改或者插入記錄。這樣一來,就保證了在0<閱讀量<100這個間隙中,只存在原來的一行數(shù)據(jù),從而避免了幻讀。
間隙鎖:封鎖索引記錄中的間隔
雖然InnoDB使用next-key lock能夠避免幻讀問題,但卻并不是真正的可串行化隔離。再來看一個例子吧。
首先提一個問題:
在T6時間,事務A提交事務之后,猜一猜文章A和文章B的閱讀量為多少?
答案是,文章AB的閱讀量都被修改成了10000。這代表著事務B的提交實際上對事務A的執(zhí)行產生了影響,表明兩個事務之間并不是完全隔離的。雖然能夠避免幻讀現(xiàn)象,但是卻沒有達到可串行化的級別。
這還說明,避免臟讀、不可重復讀和幻讀,是達到可串行化的隔離級別的必要不充分條件??纱谢嵌寄軌虮苊馀K讀、不可重復讀和幻讀,但是避免臟讀、不可重復讀和幻讀卻不一定達到了可串行化。
一致性是指事務執(zhí)行結束后,數(shù)據(jù)庫的完整性約束沒有被破壞,事務執(zhí)行的前后都是合法的數(shù)據(jù)狀態(tài)。
一致性是事務追求的最終目標,原子性、持久性和隔離性,實際上都是為了保證數(shù)據(jù)庫狀態(tài)的一致性而存在的。
這就不多說了吧。你細品。
了解完MySQL的基本架構,大體上能夠對MySQL的執(zhí)行流程有了比較清晰的認知。接下來我將為大家介紹一下日志系統(tǒng)。
MySQL日志系統(tǒng)是數(shù)據(jù)庫的重要組件,用于記錄數(shù)據(jù)庫的更新和修改。若數(shù)據(jù)庫發(fā)生故障,可通過不同日志記錄恢復數(shù)據(jù)庫的原來數(shù)據(jù)。因此實際上日志系統(tǒng)直接決定著MySQL運行的魯棒性和穩(wěn)健性。
MySQL的日志有很多種,如二進制日志(binlog)、錯誤日志、查詢日志、慢查詢日志等,此外InnoDB存儲引擎還提供了兩種日志:redo log(重做日志)和undo log(回滾日志)。這里將重點針對InnoDB引擎,對重做日志、回滾日志和二進制日志這三種進行分析。
重做日志(redo log)是InnoDB引擎層的日志,用來記錄事務操作引起數(shù)據(jù)的變化,記錄的是數(shù)據(jù)頁的物理修改。
重做日記的作用其實很好理解,我打個比方。數(shù)據(jù)庫中數(shù)據(jù)的修改就好比你寫的論文,萬一哪天論文丟了怎么呢?以防這種不幸的發(fā)生,我們可以在寫論文的時候,每一次修改都拿個小本本記錄一下,記錄什么時間對某一頁進行了怎么樣的修改。這就是重做日志。
InnoDB引擎對數(shù)據(jù)的更新,是先將更新記錄寫入redo log日志,然后會在系統(tǒng)空閑的時候或者是按照設定的更新策略再將日志中的內容更新到磁盤之中。這就是所謂的預寫式技術(Write Ahead logging)。這種技術可以大大減少IO操作的頻率,提升數(shù)據(jù)刷新的效率。
臟數(shù)據(jù)刷盤
值得注意的是,redo log日志的大小是固定的,為了能夠持續(xù)不斷的對更新記錄進行寫入,在redo log日志中設置了兩個標志位置,checkpoint和write_pos,分別表示記錄擦除的位置和記錄寫入的位置。redo log日志的數(shù)據(jù)寫入示意圖可見下圖。
當write_pos標志到了日志結尾時,會從結尾跳至日志頭部進行重新循環(huán)寫入。所以redo log的邏輯結構并不是線性的,而是可看作一個圓周運動。write_pos與checkpoint中間的空間可用于寫入新數(shù)據(jù),寫入和擦除都是往后推移,循環(huán)往復的。
當write_pos追上checkpoint時,表示redo log日志已經寫滿。這時不能繼續(xù)執(zhí)行新的數(shù)據(jù)庫更新語句,需要停下來先刪除一些記錄,執(zhí)行checkpoint規(guī)則騰出可寫空間。
checkpoint規(guī)則:checkpoint觸發(fā)后,將buffer中臟數(shù)據(jù)頁和臟日志頁都刷到磁盤。
臟數(shù)據(jù):指內存中未刷到磁盤的數(shù)據(jù)。
redo log中最重要的概念就是緩沖池buffer pool,這是在內存中分配的一個區(qū)域,包含了磁盤中部分數(shù)據(jù)頁的映射,作為訪問數(shù)據(jù)庫的緩沖。
當請求讀取數(shù)據(jù)時,會先判斷是否在緩沖池命中,如果未命中才會在磁盤上進行檢索后放入緩沖池;
當請求寫入數(shù)據(jù)時,會先寫入緩沖池,緩沖池中修改的數(shù)據(jù)會定期刷新到磁盤中。這一過程也被稱之為刷臟 。
因此,當數(shù)據(jù)修改時,除了修改buffer pool中的數(shù)據(jù),還會在redo log中記錄這次操作;當事務提交時,會根據(jù)redo log的記錄對數(shù)據(jù)進行刷盤。如果MySQL宕機,重啟時可以讀取redo log中的數(shù)據(jù),對數(shù)據(jù)庫進行恢復,從而保證了事務的持久性,使得數(shù)據(jù)庫獲得crash-safe能力。
臟日志刷盤
除了上面提到的對于臟數(shù)據(jù)的刷盤,實際上redo log日志在記錄時,為了保證日志文件的持久化,也需要經歷將日志記錄從內存寫入到磁盤的過程。redo log日志可分為兩個部分,一是存在易失性內存中的緩存日志redo log buff,二是保存在磁盤上的redo log日志文件redo log file。
為了確保每次記錄都能夠寫入到磁盤中的日志中,每次將redo log buffer中的日志寫入redo log file的過程中都會調用一次操作系統(tǒng)的fsync操作。
fsync函數(shù):包含在UNIX系統(tǒng)頭文件#include <unistd.h>中,用于同步內存中所有已修改的文件數(shù)據(jù)到儲存設備。
在寫入的過程中,還需要經過操作系統(tǒng)內核空間的os buffer。redo log日志的寫入過程可見下圖。
redo log日志刷盤流程
二進制日志binlog是服務層的日志,還被稱為歸檔日志。binlog主要記錄數(shù)據(jù)庫的變化情況,內容包括數(shù)據(jù)庫所有的更新操作。所有涉及數(shù)據(jù)變動的操作,都要記錄進二進制日志中。因此有了binlog可以很方便的對數(shù)據(jù)進行復制和備份,因而也常用作主從庫的同步。
這里binlog所存儲的內容看起來似乎與redo log很相似,但是其實不然。redo log是一種物理日志,記錄的是實際上對某個數(shù)據(jù)進行了怎么樣的修改;而binlog是邏輯日志,記錄的是SQL語句的原始邏輯,比如”給ID=2這一行的a字段加1 "。binlog日志中的內容是二進制的,根據(jù)日記格式參數(shù)的不同,可能基于SQL語句、基于數(shù)據(jù)本身或者二者的混合。一般常用記錄的都是SQL語句。
這里的物理和邏輯的概念,我的個人理解是:
物理的日志可看作是實際數(shù)據(jù)庫中數(shù)據(jù)頁上的變化信息,只看重結果,而不在乎是通過“何種途徑”導致了這種結果;
邏輯的日志可看作是通過了某一種方法或者操作手段導致數(shù)據(jù)發(fā)生了變化,存儲的是邏輯性的操作。
同時,redo log是基于crash recovery,保證MySQL宕機后的數(shù)據(jù)恢復;而binlog是基于point-in-time recovery,保證服務器可以基于時間點對數(shù)據(jù)進行恢復,或者對數(shù)據(jù)進行備份。
事實上最開始MySQL是沒有redo log日志的。因為起先MySQL是沒有InnoDB引擎的,自帶的引擎是MyISAM。binlog是服務層的日志,因此所有引擎都能夠使用。但是光靠binlog日志只能提供歸檔的作用,無法提供crash-safe能力,所以InnoDB引擎就采用了學自于Oracle的技術,也就是redo log,這才擁有了crash-safe能力。這里對redo log日志和binlog日志的特點分別進行了對比:
在MySQL執(zhí)行更新語句時,都會涉及到redo log日志和binlog日志的讀寫。一條更新語句的執(zhí)行過程如下:
MySQL更新語句的執(zhí)行過程
從上圖可以看出,MySQL在執(zhí)行更新語句的時候,在服務層進行語句的解析和執(zhí)行,在引擎層進行數(shù)據(jù)的提取和存儲;同時在服務層對binlog進行寫入,在InnoDB內進行redo log的寫入。
不僅如此,在對redo log寫入時有兩個階段的提交,一是binlog寫入之前prepare狀態(tài)的寫入,二是binlog寫入之后commit狀態(tài)的寫入。
之所以要安排這么一個兩階段提交,自然是有它的道理的?,F(xiàn)在我們可以假設不采用兩階段提交的方式,而是采用“單階段”進行提交,即要么先寫入redo log,后寫入binlog;要么先寫入binlog,后寫入redo log。這兩種方式的提交都會導致原先數(shù)據(jù)庫的狀態(tài)和被恢復后的數(shù)據(jù)庫的狀態(tài)不一致。
先寫入redo log,后寫入binlog:
在寫完redo log之后,數(shù)據(jù)此時具有crash-safe能力,因此系統(tǒng)崩潰,數(shù)據(jù)會恢復成事務開始之前的狀態(tài)。但是,若在redo log寫完時候,binlog寫入之前,系統(tǒng)發(fā)生了宕機。此時binlog沒有對上面的更新語句進行保存,導致當使用binlog進行數(shù)據(jù)庫的備份或者恢復時,就少了上述的更新語句。從而使得id=2這一行的數(shù)據(jù)沒有被更新。
先寫入binlog,后寫入redo log:
寫完binlog之后,所有的語句都被保存,所以通過binlog復制或恢復出來的數(shù)據(jù)庫中id=2這一行的數(shù)據(jù)會被更新為a=1。但是如果在redo log寫入之前,系統(tǒng)崩潰,那么redo log中記錄的這個事務會無效,導致實際數(shù)據(jù)庫中id=2這一行的數(shù)據(jù)并沒有更新。
由此可見,兩階段的提交就是為了避免上述的問題,使得binlog和redo log中保存的信息是一致的。
回滾日志同樣也是InnoDB引擎提供的日志,顧名思義,回滾日志的作用就是對數(shù)據(jù)進行回滾。當事務對數(shù)據(jù)庫進行修改,InnoDB引擎不僅會記錄redo log,還會生成對應的undo log日志;如果事務執(zhí)行失敗或調用了rollback,導致事務需要回滾,就可以利用undo log中的信息將數(shù)據(jù)回滾到修改之前的樣子。
但是undo log不redo log不一樣,它屬于邏輯日志。它對SQL語句執(zhí)行相關的信息進行記錄。當發(fā)生回滾時,InnoDB引擎會根據(jù)undo log日志中的記錄做與之前相反的工作。比如對于每個數(shù)據(jù)插入操作(insert),回滾時會執(zhí)行數(shù)據(jù)刪除操作(delete);對于每個數(shù)據(jù)刪除操作(delete),回滾時會執(zhí)行數(shù)據(jù)插入操作(insert);對于每個數(shù)據(jù)更新操作(update),回滾時會執(zhí)行一個相反的數(shù)據(jù)更新操作(update),把數(shù)據(jù)改回去。undo log由兩個作用,一是提供回滾,二是實現(xiàn)MVCC。
主從復制的概念很簡單,就是從原來的數(shù)據(jù)庫復制一個完全一樣的數(shù)據(jù)庫,原來的數(shù)據(jù)庫稱作主數(shù)據(jù)庫,復制的數(shù)據(jù)庫稱為從數(shù)據(jù)庫。從數(shù)據(jù)庫會與主數(shù)據(jù)庫進行數(shù)據(jù)同步,保持二者的數(shù)據(jù)一致性。
主從復制的原理實際上就是通過bin log日志實現(xiàn)的。bin log日志中保存了數(shù)據(jù)庫中所有SQL語句,通過對bin log日志中SQL的復制,然后再進行語句的執(zhí)行即可實現(xiàn)從數(shù)據(jù)庫與主數(shù)據(jù)庫的同步。
主從復制的過程可見下圖。主從復制的過程主要是靠三個線程進行的,一個運行在主服務器中的發(fā)送線程,用于發(fā)送binlog日志到從服務器。兩外兩個運行在從服務器上的I/O線程和SQL線程。I/O線程用于讀取主服務器發(fā)送過來的binlog日志內容,并拷貝到本地的中繼日志中。SQL線程用于讀取中繼日志中關于數(shù)據(jù)更新的SQL語句并執(zhí)行,從而實現(xiàn)主從庫的數(shù)據(jù)一致。
主從復制原理
之所以需要實現(xiàn)主從復制,實際上是由實際應用場景所決定的。主從復制能夠帶來的好處有:
1. 通過復制實現(xiàn)數(shù)據(jù)的異地備份,當主數(shù)據(jù)庫故障時,可切換從數(shù)據(jù)庫,避免數(shù)據(jù)丟失。
2. 可實現(xiàn)架構的擴展,當業(yè)務量越來越大,I/O訪問頻率過高時,采用多庫的存儲,可以降低磁盤I/O訪問的頻率,提高單個機器的I/O性能。
3. 可實現(xiàn)讀寫分離,使數(shù)據(jù)庫能支持更大的并發(fā)。
4. 實現(xiàn)服務器的負載均衡,通過在主服務器和從服務器之間切分處理客戶查詢的負荷。
MySQL數(shù)據(jù)庫應該算是程序員必須掌握的技術之一了。無論是項目過程中還是面試中,MySQL都是非常重要的基礎知識。不過,對于MySQL來說,真的東西太多了。我在寫這篇文章的時候,查閱了大量的資料,發(fā)現(xiàn)越看不懂的越多。還真是應了那句話:
你知道的越多,不知道的也就越多。
這篇文章著重是從理論的角度去解析MySQL基本的事務和日志系統(tǒng)的基本原理,我在表述的時候盡可能的避免采用實際的代碼去描述。即便是這篇將近一萬字+近二十副純手工繪制的圖解,也難以將MySQL的博大精深分析透徹。
但是我相信,對于初學者而言,這些理論能夠讓你對MySQL有一個整體的感知,讓你對“何謂關系型數(shù)據(jù)庫”這么一個問題有了比較清晰的認知;而對于熟練掌握MySQL的大佬來說,或許本文也能夠喚醒你塵封已久的底層理論基礎,對你之后的面試也會有一定幫助。
技術這種東西沒有絕對的對錯,倘若文中有誤還請諒解,并歡迎與我討論。自主思考永遠比被動接受更有效。
https://www.cnblogs.com/kismetv/p/10331633.html
https://www.cnblogs.com/ivy-zheng/p/11094528.html
https://blog.csdn.net/qq_39016934/article/details/90116706
https://www.jianshu.com/p/5af73b203f2a
https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html#auto_id_2
?TIOBE 6 月編程語言排行榜:C 與 Java 進一步拉開差距、Rust 躍進 TOP 20
?20 位行業(yè)專家共話選型經驗,CSDN「選型智囊團高端研討會」圓滿落幕!
?馬云曾賣鮮花,柳傳志賣冰箱!擺攤吧,程序員!
?韓版馬化騰:在大財閥圍堵下仍白手起家的鳳凰男,搶灘加密交易平臺、公鏈賽道
?一個神秘URL釀大禍,差點讓我背鍋!
?Uber 前無人駕駛工程師告訴你,國內無人駕駛之路還要走多久?
埋點是數(shù)據(jù)采集的專用術語,在數(shù)據(jù)驅動型業(yè)務中,如營銷策略、產品迭代、業(yè)務分析、用戶畫像等,都依賴于數(shù)據(jù)提供決策支持,希望通過數(shù)據(jù)來捕捉特定的用戶行為,如按鈕點擊量、閱讀時長等統(tǒng)計信息。因此,數(shù)據(jù)埋點可以簡單理解為:針對特定業(yè)務場景進行數(shù)據(jù)采集和上報的技術方案。
數(shù)據(jù)埋點非??粗貎杉拢粋€是數(shù)據(jù)記錄的準確性,另一個則是數(shù)據(jù)記錄的完備性。
先講數(shù)據(jù)的準確性。數(shù)據(jù)埋點非常強調規(guī)范和流程,因為參數(shù)的規(guī)范與合法,將直接影響到數(shù)據(jù)分析的準確性,如果準確性得不到保障,那么所有基于埋點得出的結論,都是不可信的。辛辛苦苦做了很久的方案,一旦因為一個疏忽的小問題,導致下游集中投訴,其實非常劃不來。
道理每個人都懂,但現(xiàn)實情況中,數(shù)據(jù)埋點所面對的客觀環(huán)境,其實非常復雜,例如:
因此本文有非常長的篇幅來寫流程問題,其實是非常有必要的。
再講數(shù)據(jù)的完備性。因為埋點主要是面向分析使用,對用戶而言是個額外的功能,因此埋點的業(yè)務侵入性很強,很容易對用戶體驗造成影響。別的不說,僅僅是流量的消耗,就很容被用戶噴。因此,要提前想清楚,我們要采集哪些東西,因為修改方案的成本,是傷不起的。
通常情況下,我們需要記錄用戶在使用產品過程中的操作行為,通過4W1H模型可以比較好的保障信息是完備的。4W1H包括:
規(guī)定好記錄信息的基本方法之后,按照固定的頻率,如每小時、每天,或者是固定的數(shù)量,比如多少條日志,或者是網絡環(huán)境,比如在Wifi下上傳,我們就可以開心的把埋點數(shù)據(jù)用起來了。
當然,數(shù)據(jù)記錄的時效性也比較重要,但因為埋點數(shù)據(jù)通常量級會比較大,且各個端數(shù)據(jù)回傳的時間不同,因此想做到實時統(tǒng)計,還是需要分場景來展開。在Flink技術日漸成熟的今天,全鏈路的實時采集與統(tǒng)計,已經不是什么難題。
在埋點的技術方案中,首先要重視的,是用戶唯一標識的建設。如果做不到對用戶的唯一識別,那么基礎的UV統(tǒng)計,都將是錯誤的。
因此,在數(shù)據(jù)埋點方案中,有兩個信息是一定要記錄的,即設備ID+用戶ID。設備ID代表用戶使用哪個設備,如安卓的ANDROID_ID/IMEI,IOS中的IDFA/UDID,瀏覽器的Cookie,小程序的OpenID等。用戶ID,代表用戶在產品中所注冊的賬號,通常是手機號,也可以是郵箱等其他格式。
當這兩個信息能夠獲得時,不論是用戶更換設備,或者是同一臺設備不同賬號登錄,我們都能夠根據(jù)這兩個ID,來識別出誰在對設備做操作。
其次,我們來看一下Web的數(shù)據(jù)采集技術。Web端數(shù)據(jù)采集主要通過三種方式實現(xiàn):服務器日志、URL解析及JS回傳。
瀏覽器的日志采集種類又可以分為兩大類:頁面瀏覽日志和頁面交互日志。
除此之外,還有一些針對特定場合統(tǒng)計的日志,例如頁面曝光時長日志、用戶在線操作監(jiān)控等,但原理都基于上述兩類日志,只是在統(tǒng)計上有所區(qū)分。
再次,我們來看下客戶端的數(shù)據(jù)采集。與網頁日志對應的,是手機應用為基礎的客戶端日志,由于早期手機網絡通訊能力較差,因而SDK往往采用延遲發(fā)送日志的方式,也就是先將日志統(tǒng)計在本地,然后選擇在Wifi環(huán)境下上傳,因而往往會出現(xiàn)統(tǒng)計數(shù)據(jù)延遲的情況?,F(xiàn)如今網絡環(huán)境好了很多,4G、5G流量充足,尤其是視頻類APP基本上都是一直聯(lián)網,因而很多統(tǒng)計能夠做到實時統(tǒng)計。
客戶端的日志統(tǒng)計主要通過SDK來完成,根據(jù)不同的用戶行為分成不同的事件,“事件”是客戶端日志行為的最小單位,根據(jù)類型的不同,可以分為頁面事件(類比頁面瀏覽)和控件點擊事件(類比頁面交互)。對于頁面事件,不同的SDK有不同的方式,主要區(qū)別為是在頁面創(chuàng)建時發(fā)送日志,還是在頁面瀏覽結束后發(fā)送日志,區(qū)別在于業(yè)務統(tǒng)計是否需要采集用戶的頁面停留時長。
頁面事件的統(tǒng)計主要統(tǒng)計如下三類信息:
最后,我們還需要考慮小程序等場景的埋點方案,小程序通常情況下,開發(fā)者會聲明好相應的方法,按照需求調用即可,例如微信提供了API上報和填寫配置兩種方案。
埋點其實還需要考慮數(shù)據(jù)上傳的方案,批量的數(shù)據(jù)可以通過Flume直接上報,流式的可以寫到Kafka,或者直接使用Flink來處理。這些框架相關的內容不是本文考慮的重點,有興趣的可以自行查閱資料。
有了指導思路和技術方案后,我們就可以著手制定相應的數(shù)據(jù)埋點流程規(guī)范了。
籠統(tǒng)上,流程規(guī)范會分成五個步驟,即需求評審、埋點申請、技術開發(fā)、埋點驗證、發(fā)布上線。
第一步,需求評審。
前文提到過,數(shù)據(jù)埋點的方案一旦確定,返工和排查問題的成本都很高,但數(shù)據(jù)埋點之后的分析工作,又涉及到了PD、BI、算法、數(shù)據(jù)等多個角色。因此非常有必要,將需求內容和數(shù)據(jù)口徑統(tǒng)一收口,所有人在一套口徑下,將需求定義出來,隨后業(yè)務側再介入,進行埋點方案的設計和開發(fā)。
以前文提到的4W1H模型為例,常見的記錄內容如下:
最后我們統(tǒng)計時,按照上述約定,統(tǒng)計用戶在某個時間和地點中,看到了哪些信息,并完成了怎樣的動作。上下游的相關人員,在使用這份數(shù)據(jù)時,產生的歧義或者是分歧,會小很多。
第二步,埋點申請。
當下的熱門應用,大多是以超級APP的形式出現(xiàn),比如微信、淘寶、支付寶、抖音,超級APP會承載非常多的業(yè)務,因此技術方案上會十分統(tǒng)一。
因此,當我們的技術方案確定后,通常要在相應的埋點平臺上,進行埋點申請。申請的內容包括分配的SPM、SCM碼是什么,涉及到的平臺是哪些,等等。SPM、SCM是什么,有什么用,同樣可以自行查閱。
第三步,技術開發(fā)。
當需求確定、申請通過后,我們就可以開始開發(fā)動作了,這里基本上是對研發(fā)同學進行約束。埋點的開發(fā),簡單講,是分成行為埋點和事件埋點兩個大類,每一類根據(jù)端的不同進行相應的開發(fā)。具體的技術方案詳見前文01章節(jié)。
詳細的設計規(guī)范,是需要留文檔的,因為代碼不能反應業(yè)務的真實意圖,而不論是事后復盤與業(yè)務交接,都需要完整的文檔來闡述設計思路。
第四步,埋點驗證。
埋點的驗證很關鍵,如果上線后才發(fā)現(xiàn)問題,那么歷史數(shù)據(jù)是無法追溯的。
驗證有兩種方式,一種是實時的功能驗證,一種是離線的日志驗證。
實時功能驗證,指功能開發(fā)好后,在灰度環(huán)境上測試相應的埋點功能是否正常,比如點擊相應的業(yè)務模塊,日志是否會正確的打印出來。通常而言,我們需要驗證如下三個類型的問題:
除去實時驗證,我們也需要把日志寫到測試環(huán)境中,查看數(shù)據(jù)上報的過程是否正確,以及對上報后的數(shù)據(jù)進行統(tǒng)計,側面驗證記錄的準確性,如統(tǒng)計基本的PV、UV,行為、事件的發(fā)生數(shù)量。
很多時候,數(shù)據(jù)是需要多方驗證的,存在一定的上下游信息不同步問題,比如對某個默認值的定義有歧義,日志統(tǒng)計會有效的發(fā)現(xiàn)這類問題。
第五步,發(fā)布上線。
應用的發(fā)布上線通常會有不同的周期,例如移動端會有統(tǒng)一的發(fā)版時間,而網頁版只需要根據(jù)自己的節(jié)奏走,因此數(shù)據(jù)開始統(tǒng)計的時間是不同的。最后,應用應當對所有已發(fā)布的埋點數(shù)據(jù),有統(tǒng)一的管理方法。
大多數(shù)時候,數(shù)據(jù)埋點的技術方案,只需要設計一次,但數(shù)據(jù)準確性的驗證,卻需要隨著產品的生命周期持續(xù)下去,因此僅僅依靠人肉來準確性驗證是不夠的,我們需要平臺來支持自動化的工作。埋點的準確性,大體有兩種方法保障:一種是灰度環(huán)境下驗證真實用戶數(shù)據(jù)的準確性;另一種則是在線上環(huán)境中,驗證全量數(shù)據(jù)的準確性。因此,發(fā)布上線之后,后續(xù)的管理動作,應該是對現(xiàn)有流程的自動化管理,因為團隊大了,需要埋點的東西多種多樣,讓平臺自己測試、自動化測試,就是很多測試團隊必須走的路。
目前行業(yè)中,已經有很多比較成熟的數(shù)據(jù)統(tǒng)計平臺,大家對于數(shù)據(jù)埋點也都有自己的方案。常見的有:GrowingIO、神策數(shù)據(jù)、百度統(tǒng)計、谷歌分析、友盟等。官網都有比較詳細的介紹,這里不再贅述。
數(shù)據(jù)埋點只是技能的一種,通過埋點的數(shù)據(jù),如何去做分析,其實也很重要。做過互聯(lián)網的同學,基本都會有自己的寶藏庫,來看看業(yè)界的同行都是如何分析問題的,著名的如艾瑞咨詢的數(shù)據(jù)報告。其實再高大上的報告,歸根結底,也是通過數(shù)據(jù)+模型來分析得到的結論。
最后說說自己做數(shù)據(jù)埋點方案的利弊。一些流量型的業(yè)務模型,使用第三方是沒有問題的,因為第三方通常提供了很強大、很完備的功能,穩(wěn)定性也有保障,但缺點是,無法做平臺規(guī)則之外的數(shù)據(jù)埋點。但如果業(yè)務數(shù)據(jù)是非常敏感的,比如金融相關,那么還是建議自己做技術方案,且現(xiàn)有的數(shù)據(jù)埋點方法,都是基于流量分析平臺來做的,對于一些偏傳統(tǒng)的業(yè)務場景,其實并不是非常適用。
最后,數(shù)據(jù)埋點,只是一種技術或者是工具,想要得出有價值的分析成果,需要有有科學的分析模型做指導,也需要有正確的學習路線來堅持。
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。