用时:10day
用时较长,一部分原因是时间与学期开始阶段课程作业时间重叠,另外就是lab4相比前3个lab,难度上升了一个大台阶。
本实验实现了一个简单的locking-based transaction system,在具备数据库基础知识的前提下,精读文档、学习部分课程视频可以完成实验。主要难点在于锁管理器的实现,我的方法是新建一个LockManager类,在类中维护一个map记录哪些页面上有锁,类方法使用同步方法,需要考虑的细节有map的中应该存放什么类型的数据、需不需要对锁新建数据结构、并发访问控制等等。此外,事务abort时如何回滚、BufferPool空间不足需要驱逐页面时哪些页面能够驱逐都是需要考虑的细节实现。
关于Debug:
Locking and transactions can be quite tricky to debug!
lab4涉及多线程,在出现bug时需要多线程调试,目前我使用了两种方法:
- 利用IDEA的多线程调试器,打断点时右键断点,将断点设置为Thread级别,优点是只需手动添加断点然后debug模式运行即可,缺点是由于代码中存在wait(time)方法,手动调试时由于在断点处会额外停留一段时间,所以打断点调试时观察到的程序行为与直接运行程序看到的行为非常可能不一样、或者说大概率不一样。
- 在程序的关键位置/可能出错的位置添加打印方法,打印出锁管理器的状态、BufferPool的状态、事务的状态或者其他关键变量,直接运行程序,查看打印进行调试。
关键词:
transaction、two-phase locking、concurrency control、锁升级
Exercise 1 & Exercise 2
要求:实现一个页面粒度加锁的锁管理器,基于锁管理器完善BufferPool的getPage()方法,实现unsafeReleasePage()、holdsLock()方法。
数据结构设计:
新建LockManager用于管理锁的获取和释放,LockManager在Database类中注册(类似BufferPool);新建Lock类,记录锁的类型,哪些事务持有该锁;在类TransactionId(Transaction的唯一标识)中添加局部变量ArrayList occupiedLocks用于记录事务持有了哪些锁。
实现细节:
实验要求实现两阶段锁(在事务commit或者abort后才统一释放锁,有一个例外是在插入tuple时需要加读锁读取页面以找到一个empty slot用于插入新的记录、如果发现一个页面没有空余位置可以立即释放读锁)、事务独占读锁则读锁可以升级为写锁。细节:注意wait()和notify()的使用,检查修改页面时有没有调用markDirty()方法,根据Permissions申请锁等等。
Exercise 3
比较容易,修改BufferPool.evictPage(),实现no steal,即不能驱逐被一个未提交事务持有锁的脏页(事务回滚时这种脏页可以discard、从BufferPool删除,但是此操作不能称为页面驱逐)。
Exercise 4
需注意细节,否则易出错。
要求:实现BufferPool的transactionComplete()方法等,在事务commit或者abort时进行相应处理。
abort时:
丢弃修改过的页面(或者恢复修改过的页面),实现中选择丢弃修改的页面(采用此种方式应注意,如果对于该事务占用的某个读锁,其他事务也在共享,则不能直接从BufferPool中discard删除页面,否则会出现丢失修改的情况,下面详细记录了这个bug);释放事务占用的锁。
commit时:
将脏页flush回磁盘持久化存储;释放事务占用的锁。
Exercise 5
要求:实现死锁检测或预防
目前我简单地通过在LockManager中设置超时来处理死锁,通过离散化超时时间降低死锁的可能性,这就要求超时时间不能过短、各个事务的超时时间不能一样,实现是设置一个超时基数TIMEOUT_MILLIS,超时是乘以一个大于0的随机数(一定不能为0,否则wait()不会超时而是会持续等待、产生死锁),其中超时基数不能过小,否则随机数的意义就不大了,不能将超时时间显著区分开。
如果不设置随机超时时间,或者设置随机超时但是超时基数过小,则有很大可能发生如下情况:
事务A、B先共享一把读锁,接着都想升级读锁,事务A 先超时并abort,还没有来得及释放读锁(让事务B独占读锁从而具备升级读锁的条件),事务B就已经超时,与预期的事务B能够升级读锁不一致。
这个问题在DeadlockTest中测试不出来,因为该测试中失败的事务不会重新执行,在TransactionTest中会测试出来,该测试中失败的事务会abort然后新建一个事务执行同样的内容,相当于事务失败会重试。
可以升级的地方:
实现不同粒度的锁、根据依赖检测死锁等
Lab4新涉及的类:
主要集中在simpledb.transaction文件夹中,其中新增了锁管理器需要的类:LockManager、Lock,类关系简明、省略类图。