数据库—并发控制(封锁)

事务

一个SQl或者一组SQL语句都能称为一个事务。

  • 显示开启事务
begin transaction //开启事务
SQL 语句,
SQL 语句,
.
.
.
commit(提交事务)/rollback(回滚事务)

事务的特性

  • 原子性
    也就是说一个事务就是不可再分的(我理解是:这个需要用户自定义或者按照具体情况具体分析的,比如转账就需要分析到这个事务必须有转账方的金额减少,收账方的金额增加,并且二者增减的金额是一样的)
  • 一致性
    一致性,我其实只理解到了两种情况,就是要么SQL里面的语句都一致性的执行成功了,否则只要不一致,就是说但凡有一个语句执行失败了就不叫一致性,这时候就要回滚事务。
  • 隔离性
    隔离性就是指封锁的意思,比如在一位老师修改学生成绩的时候,另一位老师就必须等待修改完才能继续修改,否则会出现错乱不知道修改的成绩到底对不对。这时候就需要隔离开两个老师的修改时间,不能在同一个时间段内进行对一个数据的修改。
  • 持续性
    我不太能理解为什么叫做持久性,书上的意思是说修改完后的数据具有持久性,修改完了的数据是怎样的后面就是怎样,也就是修改后具有永久性。

并发操作

并发操作就是可以同时对一个数据进行操作,但是盲目的并发会造成很多问题。
以下就是并发操作可能带来的数据不一致问题。

  • 丢失修改
    A,B两个事务,A读取数据准备修改数据,这时候B也读取了数据准备修改数据,这时候两份数据是同一份的,但是A事务先修改了数据,但是B正准备修改,但是他不知道这玩意被修改过了,但是他能咋办蒙古人只能单纯的继续操作这份过期数据,但是傻人有傻福,B的数据是最原始数据修改的,然后也是最晚提交的,所以最后这份数据最后的样子应该是最晚提交的那个,而A修改的虽然照常生效,但是由于被B的数据覆盖掉了,所以A的修改就丢失修改了。

  • 不可重复读
    名字没搞明白为什么叫做这,我更喜欢叫做验证数据错误。
    意思就是:事务A读取一份数据后进行程序该进行的一系列操作,这时候事务B进来将数据修改了提交了,然后事务A想要再读一遍数据出来进行验证操作,完犊子了这会,验证的必然错误,因为这份数据被修改过了,数据都不一样了,验证发生错误了。当然不止出现另一方(事务B)修改操作出现验证错误现象,删除与插入都可能会出现验证错误。

  • 脏读
    意思是:在事务A对数据进行修改更新操作的时候,这时候事务B读取了数据,但是可能觉得修改后不满意,将其数据恢复回原来的样子(rollback),然后事务B不知道哇,只能拿着修改过的并不是现在的数据进行操作了。

  • 不可重复读与脏读区别
    首先是立场不一样:
    不可重读站在读取方,我希望读取两次验证一下数据,这时候在我还没读取第二次的时候有另一个事务将其数据修改了,我就变成重读数据验证错误了。
    脏读是站在另一方事务,比如我在修改数据,修改完后,我在衡量自己修改的好不好,但是另一方突然来读取,可是我下一步我觉得不满意了,然后rollback,这时候另一方就变成脏读了,因为拿到的数据虽然是修改过的,但是我还是觉得原来的数据好就还原回去了,当然另一方事务拿到的就是错误的。

封锁

排它锁(X锁)

exclude

  • 排它锁,顾名思义就是只要一个事务对某个数据对象加了这样一个X锁,任何其他事务想要访问该数据只能等待这个事务完事了解锁了才能进行加锁
  • 注意事项:这里不能继续加锁的意思是其他事务不能继续加锁,但是对于拿到了加锁权限的这个事务他还能继续加,不管他加啥都行,这里相对的是不同事务。(毕竟你都到手了, 只要你是个正常事务,这个数据对象你想怎么加锁就怎么加锁,想怎么增删查改都行)

补充注意事项

这里排它锁意思是不允许其他事务进行加锁,没有说不给其他事务读取数据(不清楚为什么很多人都不讲清楚这点,还是说我太笨了?),还有就是其他博主写的把我也绕进去了,明明排它锁只是排斥其他事务想要加进来的锁,又不是排斥其他事务读取,排它锁最主要的功能我觉得是防止其他事务加锁并防止除了本事务之外的事务对该数据进行修改操作。

共享锁(S锁)

share

  • 共享锁,顾名思义是共享的,但是又不完全共享,S锁只能对其进行读取操作,得此权限叫做共享,但只共享读取这个东西。例子:一个事务对某一份数据对象进行加S锁,其他事务同样可以加S锁,但是注意不能加X锁,大家的事务都可以对这个数据对象进行加锁读取,但就是不能加X锁。(大家一起共享读取数据,但是谁也被修改,不然这就不叫共享了,这有私心了。所以我觉得这名字起得好啊。)
  • 注意事项:这里讨论的是已经加了一次S锁后的数据对象了,然后其他事务可以再对这个数据对象进行加S锁访问。(S锁只能读取数据)

封锁粒度

  • 封锁粒度大
    我还是不能理解起名字的人的心思,这样子说这个定义好容易引起误会。这里说的封锁粒度不是指力度,粒度大不是力度大。(仅代表个人见解,虽然是写给自己的笔记)
    所以说,粒度大,上锁的东西颗粒很大,也就是范围很大,比如对整个数据库进行上锁(虽然很少这样做),这就是最夸张的说法了,封锁粒度确实很大。
    带来的好处就是容易管理,易开发(因为封锁对象很少),开销很小,并发度小(如果封X锁数据库那更别提什么并发了,压根没人能跟你抢)
  • 封锁粒度小
    当然与上述相反,封锁粒度很小,就代表封锁的对象很多,且开发难度高, 硬件服务器之类的开销也大,毕竟这么多锁,并发度肯定高,不然你也不会搞这么多锁,不就为了并发度高吗,显然并发度高了,操作数据库的用户自然高,然后硬件肯定也要高级的,不然小小笔记本作服务器直接崩掉。

封锁协议


  • 申请锁(X、S)的时间
  • 持锁的时间(这个很重要,第一次想到的就是死锁,我需要用定时器的超时功能去解锁)
  • 何时释放(何时解锁)

三个级别封锁协议
协议协议,是有规定的读写。

  • 一级封锁协议
    事务希望对数据对象修改时候必须加一个X锁。
    防止了:丢失修改
    缺点:会出现不可重复度脏读现象
    解释:因为一个事务对数据对象只加了一个X锁,所以只能够保证在修改的时候没人打扰,但是对于其他事务来说虽确确实实在这个X锁时不能操作,但是会影响其他事务出现重复读取的数据不一致了,就是出现多次读取希望验证数据的时候,突然那个事务将其数据修改了,就会变成我重复读取出来的数据不对了。
    脏读说不好听的(因为这样记忆深刻),就是有个人读取到了数据后,获得修改数据权限的那个人将其数据rollback回去了,结果导致那个人拿到的数据是假的,在一级中是因为X锁仅仅只是对修改这类操作进行了封锁,但是没有对读取进行封锁,所以任何事务都可以在我上锁后的任意时刻对数据进行读取操作,所以即使我在修改完数据后觉得不满意将其rollback回去了,那个事务拿到的数据就是脏的数据了。
    • 总结:一级封锁就是仅仅封锁了修改操作,对读取会出现的问题一个都没解决,只解决了丢失修改。
  • 二级封锁协议
    事务希望对数据对象进行修改的时候必须要加X锁
    希望对数据对象读取操作的时候必须要加一个S锁,当读取完数据后立马释放S锁
    防止了:丢失修改(X 锁的作用)、脏读(S锁作用)
    缺点:会出现不可重复读取(因为不是当事务结束了才释放的S锁
    解释:当事务加了X后继续加S,看起来完美无瑕的准备进行串行操作的时候,结果他在事务还没结束的时候进行对S的解锁,然后可能会出现不可重复读的错误了,因为你允许了修改前后可以读取的操作。
    为什么防止了脏读:因为在二级协议中读取必须要加S锁,但事务修改的时候在二级中肯定是加了X的,所以你加的S只能等待状态,脏读是发生在修改后你读取了然后他rollback了,但这里的二级在X解锁后肯定是commit或者rollback后的,因此不可能出现脏读现象。
  • 三级封锁协议
    事务希望对数据对象进行修改的时候必须要加X锁
    希望对数据对象读取操作的时候必须要加一个S锁,事务没有结束不会释放S锁
    防止了:丢失修改(X 锁的作用)、脏读(S锁作用)、不可重复读
在这里我想用代码块来解释三级封锁协议,这协议真的太棒了,当真是妙哉。

假如说有一个事务,他希望对某个数据对象进行修改,又因为在三级协议中,
所以他必须加X锁,然后加完之后这个数据对象就归他了,
之后他又希望对该数据进行读取,又因为三级协议,所以他必须加S锁,
注意了,在三级协议中,只要加了S锁,不管你读完后还读不读,
都必须要在事务结束之后才能释放,然后现在有另外的事务B想要访问这个数据对象,
希望写操作,那他必须加X,但是吧,这数据已经上了X锁,所以B上的X失败,
只能等待,这时候来了个C,但她只希望读取,那他必须加S,但是吧,
这数据加了X锁,所以他还是不能读取。

各位,妙啊,妙就妙在这里,他强制的要求在三级协议中,你写要加X锁,
读要加S锁,如果对方拿到数据上的锁是S锁,他只是希望读取而已,
那这时候其实只是发挥三级协议的一部分威力,因为读取数据加S在二级中也是可以完成。
但假如有个事务拿到数据是希望修改的,那三级协议就威力可大了,
首先拿到数据修改上的锁必然是X锁,然后其他事务在三级协议中想要
拿数据对象进行读或写都要进行上锁,但是X是不允许其他事务上锁的,
其他事务在三级协议中也不能不上锁就读取,
由于X是允许不上锁读取数据的,但好巧不巧三级协议不允许
你不上锁读取或者修改,这就完美解决了二级协议中的不可重读的错误。
当真是妙哉,
不可重读现象:我在读取的时候任何人都不能对我拿到的数据进行修改或者读取,所以完美解决了不可重读
(这里是在三级中,因为X锁是允许其他人读取的,但由于三级协议规定你读取也要加锁,X又是不允许加锁的,所以你读取不到)
  • 思考
    这个“事务没有结束不会释放S锁”的功能好像在三级中好像没起到作用,我思来想去这玩意是干嘛的,因为我本身就是规定你读取必须加S,然后我在修改时已经加了X,是不可能让加锁读取的,你释放不释放好像也读取不到。
  • 最后我只能给出在自己认知范围内的解释:我自己是觉得在写这个算法的时候,不同的事务用的是同一个算法,所以当读取的时候必须加S锁,所以即使是拿到上锁权限的事务只能再事务结束后才能放S锁,不然如果不是这样的话,其他事务在用这个算法的时候就做不到读取之前上S锁(当然需要等待那个事务完成放X锁)。
    我觉得如果是我设计算法应该会这样想,因为我觉得没必要一直让拿到操作数据权限的那个数据一直读完就放S,读的时候又上S,好浪费性能,还不如直到事务结束才释放S,而且好像还能和其他事务的S锁代码复用,不用还要单独用二级协议算法的上S读完又放S,这样就三级协议就用三级协议的算法代码复用了。


总结:
学了这么久数据库反倒是在这里学到的东西很多,前面的学习几乎都是按照SQL与数据库发展的设计进行学习的,依旧是站在巨人的肩膀上学习。三级协议这里算是真正意义上对协议更深入的了解,在计算机网络学习协议都没那么深刻,这里的三级协议倒是让我荡起且久久不能平复的心情,因为这里的封锁协议是利用X锁与S锁的特性,然后三级协议就是人为的加上进行强制性条件,可是在其他博文中却几乎没找到有关这个的答案,迫使我翻度娘、翻wai网。果然啊还是苦后的甜才是最让人深刻的。


posted @ 2023-06-28 00:29  竹等寒  阅读(63)  评论(0编辑  收藏  举报  来源