參考何人聽我楚狂聲的代碼,深入理解數(shù)據(jù)庫知識,順便作為自己項目的準(zhǔn)備。
什么是事務(wù)
Transaction是關(guān)系型數(shù)據(jù)庫的核心組成,它將數(shù)據(jù)有條理地保存在儲存介質(zhì)(磁盤)中,
并在邏輯上,將數(shù)據(jù)以結(jié)構(gòu)化的形態(tài)呈現(xiàn)給用戶。支持?jǐn)?shù)據(jù)的增、刪、改、查,并在過程中保障數(shù)據(jù)的正確且可靠。
為了保證數(shù)據(jù)正確可靠,對應(yīng)的擁有四大特性:
- 原子性(Atomicity): 事務(wù)要么全部完成,要么全部取消。 如果事務(wù)崩潰,狀態(tài)回到事務(wù)之前(事務(wù)回滾)。
- 隔離性(Isolation): 如果2個事務(wù) T1 和 T2 同時運行,事務(wù) T1 和 T2 最終的結(jié)果是相同的,不管 T1和T2誰先結(jié)束。
- 持久性(Durability): 一旦事務(wù)提交,不管發(fā)生什么(崩潰或者出錯),數(shù)據(jù)要保存在數(shù)據(jù)庫中。
- 一致性(Consistency): 只有合法的數(shù)據(jù)(依照關(guān)系約束和函數(shù)約束)才能寫入數(shù)據(jù)庫。
如何保證原子性
begin; -- 開始一個事務(wù)
update table set A = A - 1億; -- 偽sql,僅作示意
update table set B = B + 1億;
-- 其他讀寫操作
commit; -- 提交事務(wù)
要保證上面操作的原子性, 就得等begin和commit之間的操作全部成功完成后,才將結(jié)果統(tǒng)一提交給數(shù)據(jù)庫保存,如果途中任意一個操作失敗,就撤銷前面的操作,且操作不會提交數(shù)據(jù)庫保存,這樣就保證了同生共死。
如何保證隔離性
引入數(shù)據(jù)的隔離機(jī)制,確保同時只能有一個事務(wù)在修改A,一個修改完了,另一個才來修改。 這需要對數(shù)據(jù)A加上互斥鎖。在事務(wù)中更新某條數(shù)據(jù)獲得的互斥鎖,只有在事務(wù)提交或失敗之后才會釋放,在此之前,其他事務(wù)是只能讀,不能寫這條數(shù)據(jù)的。
這也就引出了隔離性的強度,四大級別。(不展開了)
如何保證持久性
如果在事務(wù)提交后,事務(wù)的數(shù)據(jù)還沒有真正落到磁盤上,此時數(shù)據(jù)庫奔潰了,事務(wù)對應(yīng)的數(shù)據(jù)會不會丟?
事務(wù)會保證數(shù)據(jù)不會丟,當(dāng)數(shù)據(jù)庫因不可抗拒的原因奔潰后重啟,它會保證:
- 成功提交的事務(wù),數(shù)據(jù)會保存到磁盤
- 未提交的事務(wù),相應(yīng)的數(shù)據(jù)會回滾
事務(wù)日志
數(shù)據(jù)庫通過事務(wù)日志來達(dá)到這個目標(biāo)。 事務(wù)的每一個操作(增/刪/改)產(chǎn)生一條日志,內(nèi)容組成大概如下:
- LSN:一個按時間順序分配的唯一日志序列號,靠后的操作的LSN比靠前的大。
- TransID:產(chǎn)生操作的事務(wù)ID。
- PageID:被修改的數(shù)據(jù)在磁盤上的位置,數(shù)據(jù)以頁為單位存儲。
- PrevLSN:同一個事務(wù)產(chǎn)生的上一條日志記錄的指針。
- UNDO:取消本次操作的方法,按照此方法回滾。
- REDO:重復(fù)本次操作的方法,如有必要,重復(fù)此方法保證操作成功。
磁盤上每個頁(保存數(shù)據(jù)的,不是保存日志的)都記錄著最后一個修改該數(shù)據(jù)操作的LSN。數(shù)據(jù)庫會通過解析事務(wù)日志,將修改真正落到磁盤上(寫盤),隨后清理事務(wù)日志(正常情況下)。
這也是數(shù)據(jù)庫在保證數(shù)據(jù)安全和性能這兩個點之前的折中辦法:
- 如果每次更新都寫盤,由于數(shù)據(jù)是隨機(jī)的,會造成大量的隨機(jī)IO,性能會非常差
- 如果每次更新不馬上寫盤,那一旦數(shù)據(jù)庫崩潰,數(shù)據(jù)就會丟失
折中的辦法就是:
- 將數(shù)據(jù)的變更以事務(wù)日志的方式,按照時間先后追加到日志緩沖區(qū),由特定算法寫入事務(wù)日志,這是順序IO,性能較好
- 通過數(shù)據(jù)管理器解析事務(wù)日志,由特定的算法擇機(jī)進(jìn)行寫盤
數(shù)據(jù)庫恢復(fù)
當(dāng)數(shù)據(jù)庫從崩潰中恢復(fù)時,會有以下幾個步驟:
- 解析存在的事務(wù)日志,分析哪些事務(wù)需要回滾,哪些需要寫盤(還沒來得及寫盤,數(shù)據(jù)庫就崩潰了)。
- Redo,進(jìn)行寫盤。檢測對應(yīng)數(shù)據(jù)所在數(shù)據(jù)頁的LSN,如果數(shù)據(jù)頁的LSN>=事務(wù)操作的LSN,說明已經(jīng)寫過盤,不然進(jìn)行寫盤操作。
- Undo, 按照LSN倒序進(jìn)行回滾
經(jīng)過這幾個階段,在數(shù)據(jù)庫恢復(fù)后,可以達(dá)到奔潰前的狀態(tài),也保證了數(shù)據(jù)的一致性。
XID
XID是什么
MySQL Binlog 文件由 event 組成,event 有不同的類型,而XID_EVENT 表示一個事務(wù)的提交操作。
當(dāng)事務(wù)提交時,在 binlog 依賴的內(nèi)部 XA 中,額外添加了 Xid 結(jié)構(gòu),binlog 有多種數(shù)據(jù)類型:
- statement 格式,記錄為基本語句,包含 Commit
- row 格式,記錄為基于行
- mixed 格式,日志記錄使用混合格式
不論是 statement 還是 row 格式,binlog 都會添加一個 XID_EVENT 作為事務(wù)的結(jié)束,該事件記錄了事務(wù)的 ID 也就是 Xid,在 MySQL 進(jìn)行崩潰恢復(fù)時根據(jù) binlog 中提交的情況來決定如何恢復(fù)。
XID如何生成
MySQL 內(nèi)部維護(hù)了一個全局變量 global_query_id,每次執(zhí)行語句的時候?qū)⑺x值給 Query_id,然后給這個變量加 1。如果當(dāng)前語句是這個事務(wù)執(zhí)行的第一條語句,那么 MySQL 還會同時把 Query_id 賦值給這個事務(wù)的 Xid。
XID是唯一的嗎
global_query_id 是一個純內(nèi)存變量,重啟之后就清零了。所以在同一個數(shù)據(jù)庫實例中,不同事務(wù)的 Xid 也是有可能相同的。
但是 MySQL 重啟之后會重新生成新的 binlog 文件,這就保證了,同一個 binlog 文件里,Xid 一定是惟一的。
雖然 MySQL 重啟不會導(dǎo)致同一個 binlog 里面出現(xiàn)兩個相同的 Xid,但是如果 global_query_id 達(dá)到上限后,就會繼續(xù)從 0 開始計數(shù)。從理論上講,還是就會出現(xiàn)同一個 binlog 里面出現(xiàn)相同 Xid 的場景。
在本項目中的設(shè)計
在 MYDB 中,每一個事務(wù)都有一個 XID,這個 ID 唯一標(biāo)識了這個事務(wù)。事務(wù)的 XID 從 1 開始標(biāo)號,并自增,不可重復(fù)。并特殊規(guī)定 XID=0 是一個超級事務(wù)(Super Transaction)。當(dāng)一些操作想在沒有申請事務(wù)的情況下進(jìn)行,那么可以將操作的 XID 設(shè)置為 0,這樣就不會產(chǎn)生記錄。XID 為 0 的事務(wù)的狀態(tài)永遠(yuǎn)是 committed。
XID文件存儲格式
事務(wù)的個數(shù)(8個字節(jié)) | 事務(wù) xid1 的狀態(tài)(1個字節(jié)) | 事務(wù) xid2 的狀態(tài)(1個字節(jié))| ...
TransactionManager 維護(hù)了一個 XID 格式的文件,用來記錄各個事務(wù)的狀態(tài)。MYDB 中,每個事務(wù)都有下面的三種狀態(tài):
- active,正在進(jìn)行,尚未結(jié)束
- committed,已提交
- aborted,已撤銷(回滾)
XID 文件給每個事務(wù)分配了一個字節(jié)的空間,用來保存其狀態(tài)。同時,在 XID 文件的頭部,還保存了一個 8 字節(jié)的數(shù)字,記錄了這個 XID 文件管理的事務(wù)的個數(shù)。于是,事務(wù) xid 在文件中的狀態(tài)就存儲在 (xid-1)+8 字節(jié)處,xid-1 是因為 xid 0(Super XID) 的狀態(tài)不需要記錄。
SQL解析器
MySQL的SQL解析器(SQL parser)是一個負(fù)責(zé)將SQL語句轉(zhuǎn)換為可執(zhí)行的指令的組件。其主要功能是將輸入的SQL語句分解為語法單元,然后將這些語法單元轉(zhuǎn)換為內(nèi)部表示的數(shù)據(jù)結(jié)構(gòu),最終生成一個可執(zhí)行的查詢計劃。解析器是MySQL中的一個重要組成部分,它直接影響查詢的性能和正確性。
MySQL的SQL解析器基于傳統(tǒng)的編譯原理中的語法分析技術(shù)。在解析SQL語句的過程中,它會進(jìn)行詞法分析、語法分析、語義分析等操作。
- 詞法分析:將SQL語句分解為語法單元(token),如SELECT、FROM、WHERE等關(guān)鍵字、表名、列名、運算符等。詞法分析器會識別和記錄每個語法單元的類型和位置。
- 語法分析:將詞法分析器生成的語法單元按照SQL語法規(guī)則組合成語法樹。語法分析器會檢查SQL語句是否符合語法規(guī)則,同時生成語法樹,確定查詢的邏輯結(jié)構(gòu)。
- 語義分析:在語義分析階段,MySQL的SQL解析器會對SQL語句的語義進(jìn)行檢查。這一階段的任務(wù)包括對表和列名進(jìn)行解析,檢查SQL語句的語義正確性,并將SQL語句轉(zhuǎn)換為適當(dāng)?shù)膬?nèi)部數(shù)據(jù)結(jié)構(gòu)。
最終,SQL解析器將SQL語句轉(zhuǎn)換為一個可執(zhí)行的查詢計劃,交給MySQL的查詢執(zhí)行引擎進(jìn)一步處理。
數(shù)據(jù)庫整體結(jié)構(gòu)
MYDB 分為后端和前端,前后端通過 socket 進(jìn)行交互。前端(客戶端)的職責(zé)很單一,讀取用戶輸入,并發(fā)送到后端執(zhí)行,輸出返回結(jié)果,并等待下一次輸入。MYDB 后端則需要解析 SQL,如果是合法的 SQL,就嘗試執(zhí)行并返回結(jié)果。不包括解析器,MYDB 的后端劃分為五個模塊,每個模塊都又一定的職責(zé),通過接口向其依賴的模塊提供方法。五個模塊如下:
- Transaction Manager(TM)
- Data Manager(DM)
- Version Manager(VM)
- Index Manager(IM)
- Table Manager(TBM)
每個模塊的職責(zé)如下:文章來源:http://www.zghlxwxcb.cn/news/detail-610784.html
- TM 通過維護(hù) XID 文件來維護(hù)事務(wù)的狀態(tài),并提供接口供其他模塊來查詢某個事務(wù)的狀態(tài)。
- DM 直接管理數(shù)據(jù)庫 DB 文件和日志文件。DM 的主要職責(zé)有:1) 分頁管理 DB 文件,并進(jìn)行緩存;2) 管理日志文件,保證在發(fā)生錯誤時可以根據(jù)日志進(jìn)行恢復(fù);3) 抽象 DB 文件為 DataItem 供上層模塊使用,并提供緩存。
- VM 基于兩段鎖協(xié)議實現(xiàn)了調(diào)度序列的可串行化,并實現(xiàn)了 MVCC 以消除讀寫阻塞。同時實現(xiàn)了兩種隔離級別。
- IM 實現(xiàn)了基于 B+ 樹的索引,目前 where 只支持已索引字段。
- TBM 實現(xiàn)了對字段和表的管理。同時,解析 SQL 語句,并根據(jù)語句操作表。
Transaction Manager
TransactionManager 提供了一些接口供其他模塊調(diào)用,用來創(chuàng)建事務(wù)和查詢事務(wù)狀態(tài)。更具體的:文章來源地址http://www.zghlxwxcb.cn/news/detail-610784.html
public interface TransactionManager {
long begin(); // 開啟一個新事務(wù)
void commit(long xid); // 提交一個事務(wù)
void abort(long xid); // 取消一個事務(wù)
boolean isActive(long xid); // 查詢一個事務(wù)的狀態(tài)是否是正在進(jìn)行的狀態(tài)
boolean isCommitted(long xid); // 查詢一個事務(wù)的狀態(tài)是否是已提交
boolean isAborted(long xid); // 查詢一個事務(wù)的狀態(tài)是否是已取消
void close(); // 關(guān)閉TM
}
實現(xiàn)
常量定義
// XID文件頭長度
static final int LEN_XID_HEADER_LENGTH = 8;
// 每個事務(wù)的占用長度
private static final int XID_FIELD_SIZE = 1;
// 事務(wù)的三種狀態(tài)
private static final byte FIELD_TRAN_ACTIVE = 0;
private static final byte FIELD_TRAN_COMMITTED = 1;
private static final byte FIELD_TRAN_ABORTED = 2;
// 超級事務(wù),永遠(yuǎn)為commited狀態(tài)
public static final long SUPER_XID = 0;
// XID 文件后綴
static final String XID_SUFFIX = ".xid";
到了這里,關(guān)于我的項目準(zhǔn)備(數(shù)據(jù)庫篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!