Mit6.830-SimpleDB-Lab4-事务-学习笔记

Lab4主要包括5个exercise

exercise1、2 :实现一个page级别,遵从二段锁协议的锁管理器。即在访问任何page之前,事务应该获取该page上适当类型的锁,并且在事务提交之前不应该释放任何锁。


exercise3 :完善BufferPool中的evictPage()方法,避免数据丢失,当需要置换的页面为脏页时,要跳过脏页,置换掉不是脏页的page


exercise4 :实现事务的功能,当事务提交时,将事务涉及的脏页写回磁盘,然后释放锁。当事务回滚时,清理该事务涉及到的脏页,重新从磁盘中读取清理的page


exercise5:实现死锁防范功能,当发生死锁时,抛出AbortException

两段锁协议

  • 第一阶段:扩展阶段,事务可以申请获得任意数据上的任意锁,但是不能释放任何锁。
  • 第二阶段:收缩阶段,事务可以释放任何数据上的任何类型的锁,但是不能再次申请任何锁

遵守两端所协议可能会发生死锁:两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,因此遵守两段锁协议的事务可能发生死锁。

simpleDB中的实现过程为,事务提交之前可以获取任何锁,事务提交之后释放该事务所拥有的所有锁。同时在获取锁的过程中进行死锁检测。

实验概述

事务

事务是一组以原子方式执行的数据库操作(例如,插入、删除和读取);也就是说,要么所有的动作都完成了,要么一个动作都没有完成。

  • 原子性:通过两段锁协议和BufferPool的管理实现simpleDB的原子性
  • 一致性:通过原子性实现事务的一致性,simpleDB中没有解决其他一致性问题(例如,键约束)
  • 隔离性:严格的两段锁提供隔离
  • 持久性:事务提交时将脏页强制写进磁盘

锁的赋予

  • 在事务可以读取一个对象之前,它必须拥有一个共享锁。
  • 在一个事务可以写一个对象之前,它必须有一个排他锁。
  • 多个事务可以在一个对象上拥有一个共享锁。
  • 只有一个事务可能对一个对象具有排他锁。
  • 如果对象o上只有事务t持有共享锁,则t可以将 其对o的锁升级为排他锁。

exercise 1

在BufferPool中编写获取和释放锁的方法 ,构造一个page级别的锁管理器

在BufferPool中构造两个类,PageLock类和LockManager类。当事务通过BufferPool访问page时,需要通过LockManager申请锁。

PageLock类

存储事务id和该事务获取的锁的类型。

    private 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 TransactionId getTid() {
            return tid;
        }

        public int getType() {
            return type;
        }

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

LockManager类

参数:

  • ConcurrentHashMap<PageId,ConcurrentHashMap<TransactionId,PageLock>> lockMap;:pageId与锁的映射,记录page上现存的锁。
    在这里插入图片描述

方法:

  • public synchronized boolean acquireLock(PageId pageId, TransactionId tid, int requiredType):事务向LockManager请求获取page上锁的方法,申请思路如下:
    在这里插入图片描述
    根据以上思路实现acquireLock的主要内容
        public synchronized boolean acquireLock(PageId pageId, TransactionId tid, int requiredType) throws TransactionAbortedException, InterruptedException {
            final String lockType = requiredType == 0 ? "read lock" : "write lock";
            final String thread = Thread.currentThread().getName();
            if(lockMap.get(pageId) == null){
                PageLock pageLock = new PageLock(tid,requiredType);
                ConcurrentHashMap<TransactionId,PageLock> pageLocks = new ConcurrentHashMap<>();
                pageLocks.put(tid,pageLock);
                lockMap.put(pageId,pageLocks);
                //System.out.println(thread + ": the " + pageId + " have no lock, transaction" + tid + " require " + lockType + ", accept");
                return true;
            }
            ConcurrentHashMap<TransactionId,PageLock> pageLocks = lockMap.get(pageId);

            if(pageLocks.get(tid) == null){
                // tid没有该page上的锁
                if(pageLocks.size() > 1){
                    //page 上有其他事务的读锁
                    if (requiredType == PageLock.SHARE){
                        //tid 请求读锁
                        PageLock pageLock = new PageLock(tid,PageLock.SHARE);
                        pageLocks.put(tid,pageLock);
                        lockMap.put(pageId,pageLocks);
                        //System.out.println(thread + ": the " + pageId + " have many read locks, transaction" + tid + " require " + lockType + ", accept and add a new read lock");
                        return true;
                    }
                    if (requiredType == PageLock.EXCLUSIVE){
                        // tid 需要获取写锁
                        wait(20);
                        System.out.println(thread + ": the " + pageId + " have lock with diff txid, transaction" + tid + " require write lock, await...");
                        return false;
                    }
                }
                if (pageLocks.size() == 1){
                    //page 上有一个其他事务的锁  可能是读锁,也可能是写锁
                    PageLock curLock = null;
                    for (PageLock lock : pageLocks.values()){
                        curLock = lock;
                    }
                    if (curLock.getType() == PageLock.SHARE){
                        //如果是读锁
                        if (requiredType == PageLock.SHARE){
                            // tid 需要获取的是读锁
                            PageLock pageLock = new PageLock(tid,PageLock.SHARE);
                            pageLocks.put(tid,pageLock);
                            lockMap.put(pageId,pageLocks);
                            //System.out.println(thread + ": the " + pageId + " have one read lock with diff txid, transaction" + tid + " require read lock, accept and add a new read lock");
                            return true;
                        }
                        if (requiredType == PageLock.EXCLUSIVE){
                            // tid 需要获取写锁
                            wait(10);
                            System.out.println(thread + ": the " + pageId + " have lock with diff txid, transaction" + tid + " require write lock, await...");
                            return false;
                        }
                    }
                    if (curLock.getType() == PageLock.EXCLUSIVE){
                        // 如果是写锁
                        wait(10);
                        System.out.println(thread + ": the " + pageId + " have one write lock with diff txid, transaction" + tid + " require read lock, await...");
                        return false;
                    }
                }

            }
            if (pageLocks.get(tid) != null){
                // tid有该page上的锁
                PageLock pageLock = pageLocks.get(tid);
                if (pageLock.getType() == PageLock.SHARE){
                    // tid 有 page 上的读锁
                    if (requiredType == PageLock.SHARE){
                        //tid 需要获取的是读锁
                        //System.out.println(thread + ": the " + pageId + " have one lock with same txid, transaction" + tid + " require " + lockType + ", accept");
                        return true;
                    }
                    if (requiredType == PageLock.EXCLUSIVE){
                        //tid 需要获取的是写锁
                        if(pageLocks.size() == 1){
                            // 该page上 只有tid的 读锁,则可以将其升级为写锁
                            pageLock.setType(PageLock.EXCLUSIVE);
                            pageLocks.put(tid,pageLock);
                            //System.out.println(thread + ": the " + pageId + " have read lock with same txid, transaction" + tid + " require write lock, accept and upgrade!!!");
                            return true;
                        }
                        if (pageLocks.size() > 1){
                            // 该page 上还有其他事务的锁,则不能升级
                            System.out.println(thread + ": the " + pageId + " have many read locks, transaction" + tid + " require write lock, abort!!!");
                            throw new TransactionAbortedException();
                        }
                    }
                }
                if (pageLock.getType() == PageLock.EXCLUSIVE){
                    // tid 有 page上的写锁
                    //System.out.println(thread + ": the " + pageId + " have write lock with same txid, transaction" + tid + " require " + lockType + ", accept");
                    return true;
                }
            }

            System.out.println("----------------------------------------------------");
            return false;
        }
  • public synchronized boolean isholdLock(TransactionId tid, PageId pid):判断指定事务是否持有某一page上的锁
        public synchronized boolean isholdLock(TransactionId tid, PageId pid){
            ConcurrentHashMap<TransactionId,PageLock> pageLocks;
            pageLocks = lockMap.get(pid);
            if(pageLocks == null){
                return false;
            }
            PageLock pageLock = pageLocks.get(tid);
            if(pageLock == null){
                return false;
            }
            return true;
        }
  • public synchronized boolean releaseLock(TransactionId tid, PageId pid):释放指定事务在某一page上的所有锁
        public synchronized boolean releaseLock(TransactionId tid, PageId pid){
            if (isholdLock(tid,pid)){
                ConcurrentHashMap<TransactionId,PageLock> pageLocks = lockMap.get(pid);
                pageLocks.remove(tid);
                if (pageLocks.size() == 0){
                    lockMap.remove(pid);
                }
                this.notifyAll();
                return true;
            }
            return false;
        }
  • public synchronized void completeTransaction(TransactionId tid):事务执行完成,释放该事务在所有page上的锁
        public synchronized void completeTransaction(TransactionId tid){
            Set<PageId> pageIds = lockMap.keySet();
            for (PageId pageId : pageIds){
                releaseLock(tid,pageId);
            }
            //System.out.println(Thread.currentThread().getName() + "  transaction" + tid + "   release the lock on the all locks");
        }

exercise2、5

完善BufferPool中的getPage()方法、并实现死锁检测

  Permissions类:枚举类,有READ_ONLY READ_WRITE两个属性。当getPage()方法传入READ_ONLY时,表明该事务请求的是share_lock。传入READ_WRITE时,请求的是exclusive_lock。


  同时还要检查以往Lab中调用getPage()方法的其他方法(HeapFile.insertTuple()HeapFile.deleteTuple())是否传入了相应的Permissions对象。除此之外,要对HeapFile.insertTuple()进行完善,当事务向page中的空slot插入tuple时,首先需要获取该page上的锁。如果该page上没有空的slot,需要访问下一个page,如果该HeapFile上所有的page都已满,需要创建一个新的page将tuple插入其中。但是,虽然该事务不会对已满的page进行操作,可此时它依旧持有这些page上的锁,其它事务也不能访问这些page,所以当判断某一page上的slot已满时,需要释放掉该page上的锁。虽然这不满足两段锁协议,但该事务并没有使用page中的任何数据,后序也不会用到,所以并不会有任何影响,而且也可以让其他事务访问那些slot已满的page。


解决死锁的方式有:

  • 超时等待:对每个事务设置一个获取锁的超时时间,如果在超时时间内获取不到锁,我们就认为可能发生了死锁,将该事务进行中断。
  • 循环等待图检测:建立事务等待关系的等待图,当等待图出现了环时,说明有死锁发生,在加锁前就进行死锁检测,如果本次加锁请求会导致死锁,就终止该事务。

本exercise中采取的是超时等待的方式解决死锁。


完善getPage方法

  当事务通过BufferPool的getPage()方法获取page时,先确定该事务需要获取锁的类型。通过LockManager获取响应类型的锁,成功获取则正常读取页面。获取失败时,执行acquireLock()方法的线程会进行超时等待,等待超时或者被其他线程唤醒后会再次尝试获取锁,等待时间超过500ms时,对该事务进行回滚并抛出异常,避免死锁的发生。

    public synchronized Page getPage(TransactionId tid, PageId pid, Permissions perm)
        throws TransactionAbortedException, DbException {
        // some code goes here
		//------------------------lab3添加的内容------------------
        int lockType;
        if (perm == Permissions.READ_ONLY){
            lockType = PageLock.SHARE;
        } else {
            lockType = PageLock.EXCLUSIVE;
        }
        long st = System.currentTimeMillis();
        boolean isacquired = false;
        while(!isacquired){

            try {
                isacquired = lockManager.acquireLock(pid,tid,lockType);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long now = System.currentTimeMillis();
            if(now - st > 500){
                throw new TransactionAbortedException();
            }
        }
		//------------------------lab3添加的内容------------------

        if(!bufferPool.containsKey(pid)){
            DbFile dbFile = Database.getCatalog().getDatabaseFile(pid.getTableId());
            Page page = dbFile.readPage(pid);
            LinkedNode node = new LinkedNode(pid,page);
            if(numPages > bufferPool.size()){
                addToHead(node);
                bufferPool.put(pid,node);
                return node.page;
            }
            else{
                //LRU
                evictPage();
                addToHead(node);
                assert bufferPool.size() < numPages;
                bufferPool.put(page.getId(), node);
                return page;
            }
        }
        else{
            LinkedNode node = bufferPool.get(pid);
            moveToHead(node);
            return node.page;
        }

    }

    public void unsafeReleasePage(TransactionId tid, PageId pid) {
        // some code goes here
        // not necessary for lab1|lab2
        lockManager.releaseLock(tid,pid);
    }
	

完善InsertTuple方法

完善HeapFile中的InsertTuple方法

    public List<Page> insertTuple(TransactionId tid, Tuple t)
            throws DbException, IOException, TransactionAbortedException {
        // some code goes here
        // not necessary for lab1
        ArrayList<Page> arrayList = new ArrayList<>();
        HeapPage heapPage;
        for(int pgNo=0; pgNo<numPages(); pgNo++){
            HeapPageId pageId = new HeapPageId(getId(),pgNo);
            heapPage = (HeapPage) Database.getBufferPool().getPage(tid,pageId,Permissions.READ_WRITE);
            if(heapPage.getNumEmptySlots() == 0){
            	//完善的内容,当该page上没有空slot时,释放该page上的锁,避免影响其他事务的访问
                Database.getBufferPool().unsafeReleasePage(tid,pageId);
                continue;
            }
            else {
                heapPage.insertTuple(t);
                arrayList.add(heapPage);
                return arrayList;
            }
        }

        BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(file,true));
        byte[] emptyPage = HeapPage.createEmptyPageData();
        bw.write(emptyPage);
        bw.close();

        HeapPageId newPageId = new HeapPageId(getId(),numPages()-1);
        HeapPage newPage = (HeapPage) Database.getBufferPool().getPage(tid,newPageId,Permissions.READ_WRITE);
        newPage.insertTuple(t);
        arrayList.add(newPage);
        return arrayList;
    }

exercise3

实现NO STEAL策略

  事务对page的修改只有在commit之后才会写入到磁盘,但是在之前的Lab中实现页面置换策略时,当置换掉的页面是dirty page时,也会将更改写回到磁盘。这是不允许的,所以需要完善evictPage()方法,当需要置换的page是dirty page时,需要跳过此page,去置换下一个非dirty的page。当BufferPool中缓存的page都是dirty page时,抛出异常。

完善evictPage()方法

    private synchronized  void evictPage() throws DbException {
        // some code goes here
        // not necessary for lab1

        for (int i=0; i<numPages; i++){
            LinkedNode tail = removeTail();
            Page evictPage = tail.getPage();
            if (evictPage.isDirty() != null){
                addToHead(tail);
            }
            else{
                PageId evictPageId = tail.getPageId();
                discardPage(evictPageId);
                return;
            }
        }
        throw new DbException("all pages are dirty page ");
    }

exercise 4

实现BufferPool中的transactionComplete方法

transactionComplete() 有两个版本,一个接受额外的布尔参数(当布尔参数为true时,进行提交。false时进行回滚),另一个不接受。没有附加参数的版本应该总是提交,因此可以简单地通过调用来实现 transactionComplete(tid, true)。当进行回滚时,从BufferPool中清除掉该事务造成的脏页,并将原始版本重新读到BufferPool中

	 public void transactionComplete(TransactionId tid){
        // some code goes here
        // not necessary for lab1|lab2
        transactionComplete(tid,true);
    }

    public void transactionComplete(TransactionId tid, boolean commit)  {
        // some code goes here
        // not necessary for lab1|lab2
        if(commit){
            try {
                flushPages(tid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        else {
            restorePages(tid);
        }
        lockManager.completeTransaction(tid);
    }

    public synchronized void restorePages(TransactionId tid){
        for(LinkedNode node : bufferPool.values()){
            PageId pageId = node.getPageId();
            Page page = node.getPage();
            if(tid.equals(page.isDirty())){
                int tableId = pageId.getTableId();
                DbFile table = Database.getCatalog().getDatabaseFile(tableId);
                Page pageFromDisk = table.readPage(pageId);

                node.setPage(pageFromDisk);
                bufferPool.put(pageId,node);
                moveToHead(node);
            }
        }
    }

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值