MIT6.830 lab4 SimpleDB Transactions 实验报告

一、实验预览

lab4要做的是让SimpleDB支持事务,所以实验前需要对事务的基本概念有了解,并知道ACID的特点。lab4是基于严格两阶段封锁协议去实现原子性和隔离性的,所以开始前也需要了解两阶段封锁协议是如何实现事务的。对于一致性和持久性,这里假设暂时不会发送断电等异常,所以暂时不需要崩溃恢复,不需要undo log从,后面lab6会有专门的崩溃恢复的解决方案。

事务的基本概念:

A transaction is a group of database actions (e.g., inserts, deletes,
and reads) that are executed atomically; that is, either all of
the actions complete or none of them do, and it is not apparent to an
outside observer of the database that these actions were not completed
as a part of a single, indivisible action.

ACID特性:

To help you understand
how transaction management works in SimpleDB, we briefly review how
it ensures that the ACID properties are satisfied:

  • Atomicity: Strict two-phase locking and careful buffer management
    ensure atomicity.
  • Consistency: The database is transaction consistent by virtue of
    atomicity. Other consistency issues (e.g., key constraints) are
    not addressed in SimpleDB.
  • Isolation: Strict two-phase locking provides isolation.
  • Durability: A FORCE buffer management policy ensures
    durability (see Section 2.3 below)

这里提到了ACID在SimpleDB中如何去实现。ACID实现的前提是严格两阶段封锁协议(Strict two-phase locking)。所以我们需要先了解两阶段封锁协议。

两阶段封锁协议

首先是封锁协议:我们将要求在系统中的每一个事务遵从封锁协议,封锁协议的一组规则规定事务何时可以对数据项们进行加锁、解锁。

对于两阶段封锁协议:两阶段封锁协议要求每个事务分两个节点提出加锁和解锁申请:

  1. 增长阶段:事务可以获得锁,但不能释放锁;
  2. 缩减阶段:事务可以释放锁,但不能获得新锁。

最初,事务处于增长阶段,事务根据需要获得锁。一旦该事务释放了锁,它就进入了缩减阶段,并且不能再发出加锁请求。

严格两阶段封锁协议不仅要求封锁是两阶段,还要求事务持有的所有排他锁必须在事务提交后方可释放。这个要求保证未提交事务所写的任何数据在该事务提交之前均已排他方式加锁,防止了其他事务读这些数据。

强两阶段封锁协议。它要求事务提交之前不释放任何锁。在该条件下,事务可以按其提交的顺序串行化。

锁转换:在两阶段封锁协议中,我们允许进行锁转换。我们用升级表示从共享到排他的转换,用降级表示从排他到共享的转换。锁升级只能发送在增长阶段,锁降级只能发生在缩减阶段。

Recovery and Buffer Management

To simplify your job, we recommend that you implement a NO STEAL/FORCE
buffer management policy.

As we discussed in class, this means that:

  • You shouldn’t evict dirty (updated) pages from the buffer pool if they
    are locked by an uncommitted transaction (this is NO STEAL).

    在事务提交前不需要将脏页写回磁盘

  • On transaction commit, you should force dirty pages to disk (e.g.,
    write the pages out) (this is FORCE).

    事务提交时将脏页写回磁盘

To further simplify your life, you may assume that SimpleDB will not crash
while processing a transactionComplete command. Note that
these three points mean that you do not need to implement log-based
recovery in this lab, since you will never need to undo any work (you never evict
dirty pages) and you will never need to redo any work (you force
updates on commit and will not crash during commit processing).

因为是在事务提交后才将脏页写入磁盘的,所以不需要实现redo log;

因为我们保证在提交事务的时候不会发送故障,所以不需要实现undo log;

Granting Locks

You will need to add calls to SimpleDB (in BufferPool,
for example), that allow a caller to request or release a (shared or
exclusive) lock on a specific object on behalf of a specific
transaction.

We recommend locking at page granularity; please do not
implement table-level locking (even though it is possible) for simplicity of testing. The rest
of this document and our unit tests assume page-level locking.

You will need to create data structures that keep track of which locks
each transaction holds and check to see if a lock should be granted
to a transaction when it is requested.

You will need to implement shared and exclusive locks; recall that these
work as follows:

  • Before a transaction can read an object, it must have a shared lock on it.
  • Before a transaction can write an object, it must have an exclusive lock on it.
  • Multiple transactions can have a shared lock on an object.
  • Only one transaction may have an exclusive lock on an object.
  • If transaction t is the only transaction holding a shared lock on
    an object o, t may upgrade
    its lock on o to an exclusive lock.

If a transaction requests a lock that cannot be immediately granted, your code
should block, waiting for that lock to become available (i.e., be
released by another transaction running in a different thread).
Be careful about race conditions in your lock implementation — think about
how concurrent invocations to your lock may affect the behavior.

1.一个事务读一个对象,必须持有共享锁;

2.一个事务写一个对象,必须持有排它锁;

3.多个事务可以对同一个对象加共享锁;

4.一个对象只能被一个事务加排它锁;

5.如果只有一个事务在一个对象上加共享锁,那么共享锁可能升级为排它锁

当一个事务申请锁不能立刻获得,此时应该阻塞。

二、实验过程

Exercise1 Granting Locks

Write the methods that acquire and release locks in BufferPool. Assuming
you are using page-level locking, you will need to complete the following:

  • Modify getPage() to block and acquire the desired lock
    before returning a page.
  • Implement unsafeReleasePage(). This method is primarily used
    for testing, and at the end of transactions.
  • Implement holdsLock() so that logic in Exercise 2 can
    determine whether a page is already locked by a transaction.

You may find it helpful to define a LockManager class that is responsible for
maintaining state about transactions and locks, but the design decision is up to
you.

You may need to implement the next exercise before your code passes
the unit tests in LockingTest.

exercise1需要做的是在getPage获取数据页前进行加锁,这里我们使用一个LockManager来实现对锁的管理,LockManager中主要有申请锁、释放锁、查看指定数据页的指定事务是否有锁这三个功能,其中加锁的逻辑比较麻烦,需要基于严格两阶段封锁协议去实现。事务t对指定的页面加锁时,思路如下:

  1. 锁管理器中没有任何锁或者该页面没有被任何事务加锁,可以直接加读/写锁;

  2. 如果t在页面有锁,分以下情况讨论:

    2.1 加的是读锁:直接加锁;

    2.2 加的是写锁:如果锁数量为1,进行锁升级;如果锁数量大于1,会死锁,抛异常中断事务;

  3. 如果t在页面无锁,分以下情况讨论:

    3.1 加的是读锁:如果锁数量为1,这个锁是读锁则可以加,是写锁就wait;如果锁数量大于1,说明有很多读锁,直接加;

    3.2 加的是写锁:不管是多个读锁还是一个写锁,都不能加,wait

其它两个就比较容易了。

实现LockManager前需要有一个简单的锁,PageLock主要有两个属性,一个是事务id,一个是锁的类型,在getPage时会传入该事务获取的锁的类型,我们去申请一个新锁时会创建一个PageLock对象,创建时指定锁的类型。实现代码如下:

public class PageLock{
   
    public static final int SHARE = 0;
    public static final int EXCLUSIVE = 1;
    private TransactionId tid;
    private int type;
    public PageLock(TransactionId tid, int type) {
   
        this.tid = tid;
        this.type = type;
    }

    public int getType() {
   
        return this.type;
    }

    public TransactionId getTid() {
   
        return this.tid;
    }

    public void setType(int type) {
   
        this.type = type;
    }
}

锁管理器的实现如下:

public class LockManager {
   

    private ConcurrentMap<PageId, ConcurrentMap<TransactionId, PageLock>> pageLocks;


    public LockManager() {
   
        pageLocks = new ConcurrentHashMap<>();
    }

    public synchronized boolean requireLock(PageId pid, TransactionId tid, int requireType) throws InterruptedException, TransactionAbortedException {
   
        final String lockType = requireType == 0 ? "read lock" : "write lock";
        final String thread = Thread.
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值