Author: Sixing Yan
1.前言
當數(shù)據(jù)庫系統(tǒng)執(zhí)行一條SQL語句的時候,它會為這條語句生成對應(yīng)的執(zhí)行計劃(Plan),針對查詢和更新兩種操作行為奕扣,計劃又分為查詢計劃和更新計劃。在SimpleDB中,plan 接口包含了一個Scan成員叔汁。客戶端接受SQL語句后民轴,會返回完成的這個SQL語句的計劃Plan攻柠。當客戶端開始讀取數(shù)據(jù)時,服務(wù)器就會根據(jù)這個Plan來執(zhí)行掃描數(shù)據(jù)Scan后裸。
在SimpleDB中瑰钮,所有的XXPlan 都實現(xiàn)了Plan接口(例如,ProjectPlan)微驶,而除了TableScan和SelectScan外浪谴,其他的XXScan都是實現(xiàn)Scan接口(例如开睡,ProjectScan)。因為TableScan和SelectScan這兩個會在update等操作里出現(xiàn)苟耻,所以用了特別的接口(UpdateScan)規(guī)范化它們的行為篇恒。
這一節(jié)通過解析“SimpleDB如何插入一條記錄”作為線索,來理清SimpleDB如何在數(shù)據(jù)表中插入一個數(shù)值凶杖。我們從TableScan開始胁艰,因為它繼承了UpdateScan接口,用于處理涉及Update(insert/update/delete)的情況(示意圖如下)智蝠。
2.解析
TableScan>
tableScan 是實現(xiàn)了updateScan接口腾么,當往里面插入一個值時調(diào)用setVal方法,這個方法會先判斷對應(yīng)的fieldname是哪一種類型(通過Schema的type方法判斷)杈湾。這里假設(shè)該值是String類型的解虱,然后調(diào)用它的RecordFile成員rf的setString方法
TableScan>RecordFile
RecordFile rf 憑借TableInfo和Transaction 初始化,它將會通過RecordFormatter 生成一個符合Record標準的block漆撞,然后將這個block加入當前的文件(即是tableInfo所對應(yīng)的heap file)殴泰;接著把 blocknum游標(一個整型)移到0,然后用用該block number和當前文件名生成一個Block浮驳,并用該block悍汛,tableInfo 和 transaction 生成一個record管理器 RecordPage,即是對一個特定的Block的管理員抹恳。
回憶前面员凝,Block本身并沒有讀寫功能,而是Page對它有讀寫功能奋献,這樣對應(yīng)不同的結(jié)構(gòu)健霹,只要改變Page的讀寫方式即可。
于是乎瓶蚂,RecordFile有了RecordPage rp成員糖埋,當它執(zhí)行setString方法時,就調(diào)用Record Page rp 的setString窃这。
TableScan>RecordFile>RecordPage
Record Page 的setString 首先要獲得 一個位置參數(shù) position 來自 fieldpos(fldname)瞳别,具體是 當前的位置+這個列(field)的位移
當前位置通過 當前的slot編號 乘以 每個slot的size:currentslot * slotsize
這個位移來自需要調(diào)用TableInfo的offset方法確定該列的位移,原理是查找TableInfo ti 里的offset (Map<String, Integer>) 成員杭攻,查找該列的長度
然后調(diào)用Transaction tx的 setString祟敛。
回憶前面,RecordPage 的初始化使用了block兆解,tableInfo 和 transaction 三種參數(shù)
這里setString 即是使用這個指定的block馆铁,在指定的位置 position,插入確定類型的數(shù)值 val
TableScan>RecordFile>RecordPage>Transaction
調(diào)用Transaction 中的setString方法锅睛,這里將會在recoveryMgr和Buffer兩個地方插入數(shù)據(jù)埠巨。首先將傳入的block 上鎖(使用“一致性管理器” concurMgr.xLock方法)历谍。其次將這個block傳入buffer(myBufferrs.getBuffer),獲得一個Buffer對象 buff辣垒。接著先在“恢復(fù)管理器”recoveryMgr中保存這個數(shù)值 望侈,使用setFloat方法,傳入(buff, offset, val)勋桶,拿到一個與log有關(guān)的lsn參數(shù)脱衙。最后才執(zhí)行 Buffer對象的setString 方法(傳入offset, val, lsn, )
TableScan>RecordFile>RecordPage>Transaction>RecoveryMgr
于是recoveryMgr中的setString方法,首先拿出這個位置的舊的數(shù)值哥遮,然后向日志中插入這個舊的數(shù)據(jù)岂丘,然后返回插入日志的位置陵究。需要“恢復(fù)”的時候眠饮,就可以從這個日志的位置上讀回原來的數(shù)值了。
這里向日志插入舊數(shù)據(jù)的做法铜邮,是生成一個SetStringRecord對象的實例仪召,使用它的writeToLog方法
TableScan>RecordFile>RecordPage>Transaction>RecoveryMgr>SetStringRecord
初始化有兩種方法,一種是通過傳入指定參數(shù)松蒜,調(diào)用writeToLog來產(chǎn)生一條日志記錄扔茅。另一種方法通過讀取一條日志記錄,重現(xiàn)相關(guān)參數(shù)(傳入一個BasicLogRecord對象)
TableScan>RecordFile>RecordPage>Transaction>RecoveryMgr>SetStringRecord>LogRecord
這個接口包含“設(shè)定字符串型數(shù)據(jù)”的編號秸苗,SETSTRING=5.
TableScan>RecordFile>RecordPage>Transaction>RecoveryMgr>SetStringRecord>LogRecord>BasicLogRecord
這個對象包裝了對Page的讀寫
TableScan>RecordFile>RecordPage>Transaction>Buffer
buffer的setString 方法實際由page的setString執(zhí)行
TableScan>RecordFile>RecordPage>Transaction>Buffer>Page
在這個對象中召娜,最后由ByteBuffer 完成數(shù)據(jù)的插入,ByteBuffer 由java.nio.ByteBuffer 引入惊楼。
3.總結(jié)
可以看到玖瘸,當要寫入一個數(shù)值的時候,服務(wù)器會逐步委托到Record組件檀咙,根據(jù)record的規(guī)范來寫入給定數(shù)值雅倒;該數(shù)值會放入當前事務(wù)中進行處理;事務(wù)一方面委托文件系統(tǒng)來寫入數(shù)據(jù)到本地弧可,一方面則插入日志記錄以備將來undo使用蔑匣。