2021SC@SDUSC
Wal文件格式
WAL日志包括一个头和0到多个框(frames),每个框记录一个页(page)修改的内容。对数据库所有的修改都通过修改框的方式写入WAL日志。单个的WAL日志可记录多个事务。WAL日志中的内容会定期修改到数据库文件中,这个操作成为检查点。3 单个WAL日志可多次使用。也就是说,WAL可以被框占满,当检查点动作过后,其他的框又能写到原来的位置上。WAL日志总是从开始到结尾顺序增长。附加到框后面的检查总数(checksums)和计数(counters)用来确定WAL中的那些框有效,哪些框是以前检查点动作操作完成的。 WAL头有32字节长,包括以下8个32位的大端无符号整数
0: Magic number. 0x377f0682 or 0x377f0683
4: File format version. Currently 3007000
8: Database page size. Example: 1024
12: Checkpoint sequence number
16: Salt-1, random integer incremented with each checkpoint
20: Salt-2, a different random integer changing with each ckpt
24: Checksum-1 (first part of checksum for first 24 bytes of header).
28: Checksum-2 (second part of checksum for first 24 bytes of header).
在Wal.c中实现:
struct WalIndexHdr {
u32 iVersion;
wal索引版本
u32 unused; 未使用区域
u32 iChange; 每次事务的计数器
u8 isInit;
u8 bigEndCksum; 判断wal中的checksum的类型是否为big-endian
u16 szPage; 数据库page数量
u32 mxFrame; WAL中最后一个有效帧的索引
u32 nPage; 数据库中的页大小
u32 aFrameCksum[2]; 日志最后一帧的checksum
u32 aSalt[2]; 从WAL头复制两个salt值
u32 aCksum[2];
预写日志文件是由下列对象的一个实例来表示。 struct Wal {
sqlite3_vfs pVfs;
sqlite3_file pDbFd;
sqlite3_file pWalFd;
u32 iCallback;
i64 mxWalSize;
int nWiData;
int szFirstBlock;
uolatile u32 ** apWiData ;
u32 szPage ;
i16 readLock ;
u8 syncFlags ;
u8 exclusiveMode ;
u8 writeLock ;
u8 ckptLock ;
u8 readonly;
u8 truncateOnCommit ;
u8 syncHeader ;
u8 padToSectorBoundary
walIndexHdr HDR ;
const char zWalName ;
u32 nCkpt ;
u8 LOCKERROR ;
# ENDIF
} ;
校验
跟在WAL头上的是0到多个框。每个框由一个24字节的框头(frame-header)和一个页大小的页数据组成。框头是6个大端的32位无符号整数,如下:
0: Page number.
4: For commit records, the size of the database image in pages after the commit. For all other records, zero.
8: Salt-1 (copied from the header)
12: Salt-2 (copied from the header)
16: Checksum-1.
20: Checksum-2.
当一个框满足下面条件时,才被认为是有效的: 1.在框头中Salt-1和salt-2的值跟在wal头里面的一致 2.框头的最后的8个字节的校验和完全匹配一下内容的校验结果包括在WAL头上最开始的8个字节和当前框的所有内容。
如果WAL文件的开头4个字节是0x377f0683,那么校验和就使用32位的大端计算,如果是0x377f0682,则用小端计算。校验和始终存储在框头使用大端模式存储而无论使用什么模式计算的。校验和的计算方法如下:
for i from 0 to n-1 step 2:
s0 += x[i] + s1;
s1 += x[i+1] + s0;
endfor
读事务
开始一个读事务时,读者记录WAL中最后一个有效的框的索引。读者用myFrame值记录所有的子顺序读操作。新的事务能被添加到WAl中,只要读者使用原始的myFrame值并且忽略新的增加的内容它将看到一个快照。这个技术允许多事务并行读不同的数据库内容。
读算法
从数据库中读一页(假设为页P),读者首先检查WAL来确定是否包含页P,如果包含,那么页P的最后一个有效实例是在该 WAL框中。如果WAl没有包含页P,那么页P就从数据库文件中读入。 实现算法:
结束读事务:当读事务结束时,调用此方法,释放锁.
写事务:
写事务开始前应该有个一个sqlite3WalBeginWriteTransaction(Wal *pWal)已经被调用开始了一个读事务。
//写事务开启前必须读事务在运行 //同一时间段只有一个写事务允许进行,如果有多个写事务请求,则返回sql_busy. //如果另一个连接开始了读事务在这个文件上,这个时候是不允许写入的。
写事务的关闭和释放写锁:
保存点
建立保存点,在事务回滚时,如果建立保存点,就会大大降低回滚代价。保存WAL_SAVEPOINT_NDATA的值到aWALData中。 如果有任何数据被写入(但不提交)到日志文件中,该函数移动写指针返回到事务的开始。此外,该函数被用来调用事务使数据到WAL每一帧。如果回调函数不返回SQLITE_OK,它不会被再次调用,并且错误代码返回给调用者。否则,如果回调函数不返回一个错误,这个函数返回SQLITE_OK。 恢复WAL索引头的状态,iframe是在客户端开始写入到数据库之前的客户端缓存。
性能问题
在一般情况下,WAL会提高SQLite的事务性能但是在某些极端况下,却会导致SQLite事务性能的下降。
1.在事务执行时间较长或者要修改的数据量达到GB级的时候,WAL文件会被占用,它会暂时阻止checkpoint的执行(checkpoint会清空WAL文件),这将导致WAL文件变得很大,增加寻址时间,最终导致读写性能的下降。
2.当checkpoint执行的时候,会降低当时的读写性能,因此,WAL可能会导致周期性的性能下降。
检查点 我们最终会将追加在Wal文件中的说有事务写回到初始的数据库中,将Wal文件中事务写回到数据库中被叫做“检查点”。 回滚和预写另一个不同是回滚日志有两个原语操作:读和写,而Wal有三个原语操作:读,写,检查点。默认的,Sqlite 当达到1000页这个阀值是就会自动的设置一个检查点。 Sqllite Wal自动设置检查点编译时间 可以指定不同的默认值。使用Wal的程序不必为了使这些检查点产生而做任何事。但他们如果想设置检查点,可以调整自动设置检查点的阀值,他们可以关闭自动设置检查点在空闲时或在只有一个线程或进程时。 sqlite3WalCheckpoint()用来实现sqlite3_wal_checkpoint()和相关接口。获取一个checkpoint锁,然后尽可能多的回填信息到数据中。 当用SQLITE_BUSY意味着另一个线程或进程已经在运行checkpoint或则进行恢复。如果无法获得写锁,那么一个被动的检查点将代替运行。由于checkpoint无法加载锁,是毫无意义的阻塞等待任何读者,假设没有其他错误发生时,该函数将返回SQLITE_BUSY给调用者。
读取 Wal-index 头和将日志文件中的数据拷贝到数据文件中。
释放锁和调用结束事务。
|