MIT-6.830 Lab6 总结与备忘
实现基于日志的abort rollback和基于日志的崩溃恢复,参考代码已经提供了定义日志格式的代码,并在处理事务的适当时候将操作记录附加到日志文件
Page第一次被读入,会记录该页面的原始内容,作为一个snapshot。当一个事务更新一个Page时,日志相应的Record包含原始的页面内容before-image和修改后的页面内容after-image——使用before-image回滚,并在崩溃恢复中撤销失败的事务,而after-image则在恢复过程中重新操作一遍成功的事务
页级锁简化了rollback和recovery,如果一个事务修改了一个页面,则一定有一个独占锁,此时可以通过覆盖整个页面来undo对页面的修改
Exercise 1
思路整理
调用writePage(p)之前,
BufferPool.flushPage()
中插入以下代码,p是对被写入页面的引用;这些代码会让日志系统向日志写入新的记录1
2
3
4
5
6
7// append an update record to the log, with
// a before-image and after-image.
TransactionId dirtier = p.isDirty();
if (dirtier != null){
Database.getLogFile().logWrite(dirtier, p.getBeforeImage(), p);
Database.getLogFile().force();
}BufferPool.transactionComplete()
为被commit事务修改了的页面调用flushPage()
,而flush之后,调用p.setBeforeImage()
,以便后来的事务中止时回滚到该页面的这个提交版本以上完成时,可以通过LogTest系统测试的三个子测试
Implement
LogFile.rollback()
,通过系统测试LogTest的TestAbort和TestAbortCommitInterleaved一个事务中止并释放锁之前,rollback函数被调用,undo事务对数据库做出的任何改变
rollback()
读取日志文件,找到与中止事务相关的更新记录,从这些记录中提取before-image,写入tableraf.seek()
:在日志文件中移动指针raf.readInt()
:读取日志记录readPageData()
:读取before-image和after-imagemap tidToFirstLogRecord
(由事务ID映射到文件的偏移量)确定读取特定事务的日志文件位置- make sure that you discard any page from the buffer pool whose before-image you write back to the table file
LogFile.java
中logCommit()
等函数会生成日志记录,并追加到日志中Logfile.print()
能够显示日志内容几种Logfile:
具体实现
参考
LogFile.print()
写出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public void rollback(TransactionId tid)
throws NoSuchElementException, IOException {
synchronized (Database.getBufferPool()) {
synchronized (this) {
preAppend();
// some code goes here
Long logRecord = this.tidToFirstLogRecord.get(tid.getId());
raf.seek(logRecord);
// 需要知道日志的结构
while (true) {
try {
int cpType = raf.readInt();
long cpTid = raf.readLong();
if (cpType == UPDATE_RECORD) {
long start = raf.getFilePointer();
Page before = this.readPageData(raf);
long middle = raf.getFilePointer();
Page after = this.readPageData(raf);
if (tid.getId() == cpTid) {
// 写入table
Database.getCatalog().getDatabaseFile(before.getId().getTableId()).writePage(before);
// 将原来的page从bufferpool删除
Database.getBufferPool().discardPage(before.getId());
}
}
// Each log record ends with a long integer file offset representing the position in the log file where the record began.
raf.readLong(); // 将整个日志读完
} catch (EOFException e) {
break;
}
}
}
}
}
Exercise 2
思路整理
- Implement
LogFile.recover()
,通过LogTest系统测试 - 如果数据库崩溃,然后重新启动,
LogFile.recover()
会在任何新事务开始之前被调用。此时应当先检查有没有checkpoint,如果有则从checkpoint向前扫描,没有则从日志文件向前扫描,建立之前没有成功的事务列表,然后redo;同时undo updates of loser transactions
具体实现
具体过程其实就是,读取日志时,分别存储事务对应的before-image和事务对应的after-image(对应着每一条update record),同时记录哪些事务已经被commit。读完日志后,根据事务是否被commit,分别通过before-image和after-image的写入,来恢复数据库。而checkpoint record则是用于移动指针
这里还修改上一个exercise的bug,将
flushPages()
中node.getData().setBeforeImage()
移到if判断外1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71public void recover() throws IOException {
synchronized (Database.getBufferPool()) {
synchronized (this) {
recoveryUndecided = false;
// some code goes here
raf = new RandomAccessFile(logFile, "rw");
// 已提交的事务id
Set<Long> committedId = new HashSet<>();
// 事务id对应的beforePage和afterPage
Map<Long, List<Page>> beforePages = new HashMap<>();
Map<Long, List<Page>> afterPages = new HashMap<>();
while (true) {
try {
int type = raf.readInt();
long txid = raf.readLong();
switch (type) {
case UPDATE_RECORD:
Page beforeImage = readPageData(raf);
Page afterImage = readPageData(raf);
List<Page> l1 = beforePages.getOrDefault(txid, new ArrayList<>());
l1.add(beforeImage);
beforePages.put(txid, l1);
List<Page> l2 = afterPages.getOrDefault(txid, new ArrayList<>());
l2.add(afterImage);
afterPages.put(txid, l2);
break;
case COMMIT_RECORD:
committedId.add(txid);
break;
case CHECKPOINT_RECORD:
int numTxs = raf.readInt();
while (numTxs -- > 0) {
raf.readLong();
raf.readLong();
}
break;
default:
break;
}
//end
raf.readLong();
} catch (EOFException e) {
break;
}
}
// 未提交事务,直接写before-image
for (long txid :beforePages.keySet()) {
if (!committedId.contains(txid)) {
List<Page> pages = beforePages.get(txid);
for (Page p : pages) {
Database.getCatalog().getDatabaseFile(p.getId().getTableId()).writePage(p);
}
}
}
// 已提交事务,直接写after-image
for (long txid : committedId) {
if (afterPages.containsKey(txid)) {
List<Page> pages = afterPages.get(txid);
for (Page page : pages) {
Database.getCatalog().getDatabaseFile(page.getId().getTableId()).writePage(page);
}
}
}
}
}
}