PROJECT #4 - CONCURRENCY CONTROL

PROJECT #4 - CONCURRENCY CONTROL

note:最后一个项目啦,拖了好久勒,现来更新 ing,欢迎品读~

题目

第四个编程项目是在你的数据库系统中实现一个锁管理器,然后用它来支持并发查询的执行。锁管理器负责跟踪发给事务的元组级锁,并支持共享和独占锁,根据隔离级别适当地授予和释放锁。

在这里插入图片描述

在本项目中,将要实现以下隔离级别:

Repeatable reads:可重复读取只允许读取已committed的数据,并进一步要求,在一个事务对一个数据项的两次读取之间,不允许其他事务对其进行更新。然而,相对于其他事务,该事务可能不是可序列化的。例如,当它在搜索满足某些条件的数据时,一个事务可能会找到一些由已承诺的事务插入的数据,但可能不会找到由同一事务插入的其他数据(没有索引锁导致的幻读)。

Read committed :读取承诺只允许读取已committed的数据,但不要求可重复读取。例如,在事务对一个数据项的两次读取之间,另一个事务可能已经更新了该数据项并提交。

Read Uncommitted:读取未提交的数据允许读取未提交的数据。它是SQL允许的最低隔离级别。

要点

  • 需要注意以上隔离级别的区别,首先是第一个可重复读,它要求事务一直持有读锁,直到事务提交才释放,于是需要在顺序扫描的初始化中,需要判断隔离级别,如果是可重复读,则需要对该表加全局读锁,保证可重复读,而第二个读已提交,他要求读取已经提交的数据,也就是说只需要在读的时候持有锁就行,读完就可以解锁,不用等到提交才解锁,最后一个读未提交,可以脏读,不需要加锁,如果加锁就abort。

  • 关于读锁、写锁、可升级锁的加锁方式:

    要理解为什么要加锁,说白了就是保证资源的合理分配,谁先用,谁的优先级就大!其次,对于死锁也要采取策略:

在这里插入图片描述

这里选择第二种死锁策略:Wound-Wait

  • 读锁:获取该资源的等待列表,将请求加入该请求列表,接着引入死锁策略,防止死锁,这里需要注意,当abort一个事务后,不仅需要将其从等待列表中删除,还需要使用notify函数,及时通知已经中止的线程,使其在等待中及时退出(这就是条件变量的强大,可以在线程间进行通讯)。还有一点要注意的是,由于需要进行等待,当多个高优先级的事务提交后,会导致资源的等待列表的迭代器失效,为此需要重新进行获取,然后继续等待,这个问题我遇到多次,这也是多线程导致的问题。

  • 写锁:和读锁差不多,只需要改一下列表的等待条件和死锁策略即可,同时,当事务状态为中止时,需要及时返回false,最后在获得写锁的条件是:当前事务的状态不是中止状态同时等待列表的长度为一(也就是只有该写请求)。

  • 升级锁:这里需要注意upgrading_的使用,这个变量主要是保证该资源的等待列表不能出现两个升级的事务,因为这样会导致死锁,其次,升级锁也需要保证该事务在这之前已经获得读锁,接着是死锁策略的实现和之前一样,最后获得升级锁的条件与写锁一致,但最后需要将upgrading复位,便于下一个事务获取升级锁。

  • 解锁:首先是两阶段锁的判定,在这里需要注意只有当隔离级别是可重复读的时候才需要保证,因为只有在要提交的时候才需要进行解锁。

  • 关于条件变量的使用

    • **condition_variable是一个类,搭配互斥量mutex来用,这个类有它自己的一些函数,这里就主要讲wait函数和notify_all和notify_one(后面简称notify)函数,故名思意,wait就是有一个等待的作用,notify就是有一个通知的作用。 简而言之就是程序运行到wait函数的时候会先在此阻塞,然后自动unlock,那么其他线程在拿到锁以后就会往下运行,当运行到notify()函数的时候,就会唤醒wait函数,然后自动lock并继续下运行。**为此,在进行线程间的调度的时候,需要将两者进行搭配使用,才能达到预料的效果。
  • 关于索引更新的问题

    • 由于在进行删除或者更新的时候,需要进行索引的插入或者更新,为此在回退的时候需要进行回退操作,其中包括索引的回退,这是在测试案例里面要求的一项,而tableheap里面会进行tablewriteset的维护,于是在进行更新或者删除的时候已经完成事务对tablewriteset的维护,但是tableindexwriteset没有相应的维护,为此需要在删除和更新索引后,也要对该事务的索引修改集进行更新。
  • 关于多线程的调试心得

    • 由于在测试案例里面存在多个线程并发的情况,为此在调试时增加了调试的难度,这里将其中的技术进行归纳与总结:

      • 常用的gdb命令:

        (1)查看可切换调试的线程:info threads

        (2)切换调试的线程:thread 线程id

        (3)只运行当前线程:set scheduler-locking on

        (4)运行全部的线程:set scheduler-locking off

    • 在进行debug的时候,常会碰见死锁需要认真考量,最好进行绘制其wait for graph,在进行调试的时候,也不是很方便,尤其是没理解条件变量的情况下。
      在这里插入图片描述

  • 关于lambda表达式的用法

    • 都说lambda的引入后,使得代码的可读性和便捷性

    • 语法:

      [ capture ] ( params ) opt -> ret { body; };
      // [函数对象参数] (函数参数)修饰符->返回值类型{函数体};
      

      [capture] 标识一个Lambda表达式的开始,这一部分是不可以忽略的。函数对象参数只能使用到定义该Lambda表达式为止定义过的局部变量,包括Lambda表达式所在类的成员变量。函数参数有以下几种形式:

      空:代表不捕获Lambda表达式外的变量;
      &:代表以引用传递的方式捕获Lambda表达式外的变量;
      =:代表以值传递的方式捕获Lambda表达式外的变量,即以const引用的方式传值;
      this:表示Lambda表达式可以使用Lambda表达式所在类的成员变量;

      拓展:

      a或=a:表示以值引用的方式传递变量a aa,即const int a,在函数体内不可改变a的值;但是可以对Lambda表达式使用mutable修饰符修饰,使得函数对象参数可以进行赋值,但是该函数对象参数不是被修改为引用传递方式,下面进行细说;
      &a:表示以引用传递的方式传递变量a aa,在函数体内可以改变a的值;
      x,&y:x为值传递方式,y为引用传值方式;
      =,&x,&y:除x,y为引用传递方式以外,其他参数都为值传递方式进行传递;
      &,x,y:除x,y为值传递方式以外,其他参数都为引用传递方式进行传递;

  • 关于在执行器中加入并发控制的问题

    • 顺序扫描:根据事务的隔离级别进行加锁,加读锁
    • 删除:根据事务的级别加锁,加写锁或者升级锁
    • 更新:根据事务的级别加锁,加写锁或者升级锁

总结

在这里插入图片描述

终于完成了整个项目,这让我感受到了C++的强大,数据库的魅力,Orz~,还有测试案例,Andy Team真是太厉害了,项目在测试中不断修改,才能达到完美,试问哪里还有这么好的地儿呢哈哈哈哈。RESPECT!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值