00:00
同学们,那么关于呢,咱们从不同的角度呢,对这个锁呢来进行划分,然后以及下边呢,有具体的哪些锁呢,我们讲的整体上就差不多了,最后的话呢,我们再给大家呢补充两个内容,一个呢叫做全局锁,一个呢叫做词锁啊这两个概念啊,首先呢,我们来看一下这个叫全局锁啊,顾名思义全局锁就是呢,对我们整个数据库的这个实例呢进行加锁,那一旦加锁以后呢,我们整个数据库实例下的所有的数据库,所有的表啊怎么办呢?诶你就只能够处于一种只读的状态了,就是别的事物过来以后呢,你可以去读,但是呢,一定不能够去更新。啊,你其他的事物或者叫其他的线程,你想呢,去做这个,呃,D里边这个增删改操作,或者说你是DDL的这种操作啊,基表啊,啊或者job table啊是吧,乃至于说其他的这种更新的这种行为都是不能做的。只能够读啊,不能够去改,典型的这个场景的话呢,就是我们做这个全库的一个逻辑备份啊,在备份的过程当中,不能够再去修改啊,只能读,那这个呢,具具体的这个指令呢,哎,我就放到这儿了。
01:05
啊,这个呢,我们比较好理解啊,那相应的来讲呢,这个力度呢,肯定是最粗的是吧,那比我们这个表级别呢,还要这个这个粗一些,但是这种场景的出现呢,肯定是比较少的啊OK,行,这呢是我们说的它,然后下边这个概念呢,叫做死锁啊死锁的话呢,咱们前边讲到哪的时候呢,演示过呀。诶,是不是我们在讲这个间隙锁的时候呢,提过对吧?诶间隙锁呢,属于行锁,那是在行锁当中或者叶锁当中,我们可能会出现这种死锁的问题,诶当时呢,我们说这个位置的时候呢,其实大家也看到了这个inno DB,它针对这个死锁的,呃解决方案啊,其实我们已经感受到了,是吧?那么下边呢,我们对这个死索呢,在进行个刻画,首先的话呢,提到死索这个词啊,应该说对于很多Java程序来讲呢,并不陌生,因为呢,我们在讲到这个多线程来访问共享数据的时候呢,实际上呢,就讲过这个死索的问题。比如说我们现在有一个线程,它呢先要获取我们这个共享数据,然后呢在获取这个第二个共享数据,那么在这个操作的过程当中啊,我们又有一个线程,它呢,诶正好呢,跟刚才这个线程呢,顺序呢是反着的,它呢得先获得我们这个共享数据,然后再获取上边这个共享数据,那么在这个过程当中,这两个线程就有可能会构成死锁,对吧?那我们现在呢,这个死索呢,在数据库事务当中讲到的,其实跟现程当这个死锁的原理啊是完全一样的。
02:23
只不过呢,习惯上呢,我们把这个线上这个词呢,就换成了事物这个词,但是事物本身呢,也是在这个线程当中来支持执行的,所以呢是完全一致的,OK,那这个概念怎么说呢?叫两个事物都持有对方需要的所。并且啊,在等待对方呢去释放,同时啊,自己还不释放自己这个锁,那互相呢,僵持下去就构成了思索。那我们这儿呢,也可以举两个例子,第一个例子,那这呢有两个事物啊,事物一事二啊,那很很显然呢,就是大家知道一个事物是不会构成思索的,对吧?哎,必须至少有两个失误啊,然后这个失物一的话呢,它针对有抗的账户呢,先针对ID1的这个数据呢,我们相当于获取了一个叫排查锁,好那么在他执行下边这个操作之前啊,我们这个失物二呢,它也针对有看堂账户,针对于ID2的这样一条数据呢,诶相当于获取了一个排大锁。
03:13
好,那么接下来的话,我们这个11呢,又针对ID2的这个数据呢,它试图去获取一个排打锁,此时的话呢,由于我们12呢,已经啊是不是在执行了,所以呢它呢只能是等待。那这个等待的话呢,其实是很正常的啊,因为别人已经拿着锁了,那你这时候就得等是吧?呃,那问题呢,就出在我们接下来这个事物二,如果这个事物二呢,它下边这个update操作呢,它操作的比如是ID3,那其实呢还没有构成思索,但是它这块呢,操作的恰恰呢,就是ID1。当你要是操作的是ID1的话呢,那那你想想这个时候我们ID已经被我们这个11呢,是不是已经占有这个锁了,是不是你这时候呢,也得是处于一个等待的状态啊,那此时呢,这哥俩呢,就僵持下去了,说我拿着一的锁,我等待二,我拿着二的锁等待一,咱俩呢,谁也不释放,哎,这个彼此需要这个锁,那咱们就等着吧,僵持着吧,那这个呢就没头了。
04:03
对吧,哎,这就不行,这就构成这个思索啊,那像咱们说这个思索问题呢,是一定要解决的,因为呢,咱们讲事五的时候提到过,说事五啊,必须有两个,只能是两个状态选其一啊,最终状态要么呢,就是你执行完了,提交了,这是commit的一种情况,要么呢,你这是终止的,相当于roll back了,你不能在这块僵持着这个半死不活的,那那不行。对吧,OK啊,一会儿我们说如何呢去解决啊,然后再举个例子呢,比如说我们这个用户A呢,给用户B呢,转让100块钱啊,在与此同时呢,我们这个用户B啊也给这个用户A啊转账100块钱,那这个过程有可能会出现次数,不是说一定会出现的哈,有可能出现,那我们这个呢,描述了一种场景,就是这个事物一他先针对A账户呢,我们相当于进行了一个排打索的一个相当一个占用,对吧。然后这时候的话,我们这个事物二呢,它针对用户币呢,诶相当于有个排打锁,接下来的话呢,你这个用事物一呢,又想呢,针对用户币呢,获取一个排打锁,这时候就不行了,因为人家已经拿了锁了,那你这边呢就等待了,然后这个时候呢,我们这个12呢,他呢也去操作这个A,这时候呢,A已经被人家呢拿到锁了,你这时候你也等待,哎是不是跟我们这个上边这个情况呢大同小异,那就出现了一个呃思索的一个场景。
05:13
OK,那如果用这个生活中的例子来进行刻画的话呢,诶,网上呢,有这样的图,我觉得还是比较形象的,你比如说我们这个黄车,他呢,要想开走,是不是得依赖于我们这个绿车得开走啊,绿车得往后依赖,相当于呢,就是呃,我要想走,你你们都得移走,他们往下移的话呢,最后翻回来他自己还得移走,是吧?所以这块呢,就构成了一个思索。啊,如果再换一个比较简单的例子呢,你比如说我们有两根筷子啊,一个A这个人拿一根,B这个人拿一根,说呢,呃,要想吃饭得用两根筷子啊,就相当于我们有两根筷子呢,才能够把这个事物呢执行下去,但是现在呢,谁都拿着一根啊,然后呢,都等待对方呢,先释放这一根筷子给自己,自己呢还不去释放,那就僵持到这儿了,就构成一个失所。啊,显然是不行的是吧?好,那么出现这个思索的话呢,我们总结一下呢,这个必要条件是什么呢?呃,首先呢,你至少得有两个事物啊,毫无疑问对吧?然后呢,每个事物呢,首先他自己呢持有锁,而且他还去申请新的锁,而新的锁呢,恰好是对外,呃,这个另外一个事物呢,它所拥有的,哎,这不就构成一个环了嘛,啊OK啊,然后这个锁资源的话呢,我们也进行刻画呢,它只能是被一个事物呢进行持有和操作的。
06:19
然后呢,事物之间因为持有所和申请所导致呢,彼此构成一个循环等待啊,这呢就是死锁的这两个场景,那么死组的关键呢,就是两个以上的这个事误,或者叫session会话,他加速的这个顺序啊是不一致的啊,你比如说咱们刚才提到A给B转账,B给A转账这个事儿,如果说呢,我们这个12呢,他不是先减钱再加钱了,它上来呢,就先给A呢加钱,就是我们把这两条语句呢颠倒一下。大家你会发现呢,我们这两个事物呢,就不会构成思索,因为呢,他们访问这两个共享的这个资源,一个是A是吧,一个是B,咱们就以这俩用户来说了啊哎,他们在访问A和B的时候呢,他们俩的顺序是一样的,那这时候呢,就不会出现思索问题。
07:00
对吧,哎,这个应该也比较清楚,那这儿呢,其实也是我们解决这个思索的一个措施,就是说呢,你可以把相关的这个搜库语句呢,改成这个获取索的顺序啊一致那就可以了。好,下边说如何处理思索啊,讲这个之前呢,咱们再给大家举个例子吧,是吧,把这个这个现象呢,咱们再做一个呈现啊,啊这里呢,我们就使用一下账户这样一个表啊,优一下艾特硅谷DB3这个数据库,对吧,首先我们做一个select星啊from。哎,Account一下,好,那这时候我们看到有三个人,嗯,咱们呢,就比如说这ID1和ID3,他们二者之间呢,进行转账吧,我们在这个事务当中呢,我们让这个一这个人呢给这个三这个人呢转账,比如十块钱,然后呢,在这个另外的一个事物当中呢,反过来让这个三呢给一呢转账,这个十块钱中间呢,就有可能会出现死锁,来吧,我们进行一个操作,那首先呢,我们需要呢,去study一下这个transaction是吧,我用一下begin也可以啊。Update。Can一下这个balance。
08:02
Balance呢?先去减去十块钱,Where?嗯,ID呢,等于一啊,这没问题,好,然后接下来的话呢,这个操作先别写呢,我们回到另外的一个事物当中啊,这呢,我们也是去use一下。这个呢,我们就直接呢,就begin了。行,嗯,然后呢,我们也去做一个,呃,咱们做一个select也行,先看一下from。好,没问题是吧,好update。这个account。哎,减去100,哎减去18,说where ID呢,等于这个呢,我写的是三没问题对吧,好,走起。嗯,好,笔根有了,嗯,这个时候呢,我们相当于是两个事物就已经开启了,然后开启以后呢,他们都握了一个各自的这个ID的数数据的一个排查锁,然后呢,我们回到第一个这个事物这块呢,它接下来我们已经减了十块钱了,你是不是要给我们这个ID3的这个加上十块钱啊好,那这块我们就来再做一个ID操作,只能是加十块。
09:12
这个呢针对的是ID3这条数据对吧?好,我们走起这时候的话呢,我们发现呢,就已经在等待了,这个等待呢,就因为我们这个事物呢,已经是不是获取这个排查锁了,所以你等是正常的啊,那么在这个呢,咱们得着急操作啊,因为呢,默认有一个时间,过了一个时间的话呢,它就会这个结束了,咱们赶紧的去操作一下,我们接下来的话呢,给这个ID为一的这个用户呢,咱们是不是加上十块钱啊。好,大家注意看啊,哎,我这样写一下,走起。好,大家你会发现呢,现在我们是不是出现这个死锁的情况了啊,出现死索了,好arting,然后呢,Transaction这个出现死索以后呢,咱们回过来再看一下,你会发现呢,我们这块的话呢,没有在等待了,而是呢,是不是执行了呀,啊那为什么它执行了呀,那肯定意味着我们这块呢,是不是有锁的一个释放,那就我们上边这个ID3的这条,呃,数据呢,对应的这个锁肯定释放了,那怎么就释放了呢?是因为我们这个事物呢回滚了。
10:04
哎,就这样个原因呢造成的,好,那回过来我们下边呢,就来看一看,出现这个思索该怎么去解决,相当于呢,我们刚才这个演示呢,就已经看到了这个in DB它是如何解决的了,那我们把这个事儿呢,相当于在这个理论上呢,我们进行一个说明。啊,那么该如何呢去解决思索呢?刚才那会我们也讲到了,说事物的它的这个最终状态呢,只能是二选一,一个呢叫做已提交的状态,相当于是commit了,一个呢就是的状态啊,相当于你回滚了,对吧,那你现在等着呢,这肯定不是事儿,怎么办呢?哎,那如果说你出现思索了,这个时候的话呢,我们一种方案呢,叫做超时,我们有一个默认参数叫in DB lock wait timeout,这个呢,默认的这个值呢是50秒啊,你也可以呢过来呢去做一个十物查看啊。Show。啊,Variables啊like一下,那我们把这个参数来给它粘过来。哎,走起一下啊,这呢是不是,哎50单位呢,是秒,相当于呢,我们超过50秒的时候呢,它会自动的进行一个回滚啊,当然呢,自己呢去演示就可以了,比如说呢,我们像刚才呢,在这里边呃,执行了这个操作以后,然后呢,我们这块呢,执行了这个操作,然后第三步的话呢,执行这个操作的时候呢,假设呢,你就不去执行后边这个操作的时候呢,50秒以后,这个事物呢,它自动的就会回滚回去,因为呢,已经超时了啊,超过50秒呢就会回滚。
11:21
好,这呢相当于也是我们一种是不是解决,其实这呢也呃也也不是死锁了啊,就是你不是死锁的话呢,你超过50秒等待的话呢,我们也自动的会去做这个回滚,是这意思吧,嗯,OK啊,那如果呢,我们出现死索的话呢,呃,一种处理方案啊,那就说呢,我们就等着啊,等到50秒了,这个时候我们就自动呢把这个数据呢,就做了一个回滚了啊这是OK的啊,但是这种方案的话呢,大家想想的话,是不是比较崩溃,因为50秒呢挺长的。你像我们因为出现思索,我们在真正的这个实际项目当中,有可能这个会有多个这种思索的这个出现,互相交织在一起,你要是等50秒,这个其实是很崩溃的啊,那怎么办呢?有同学会想到说那不是不是一个参数吗?我把这个变量的这个值改小一点不就行了吗?比如改成一秒,改成0.1秒啊,你只要超过0.1秒,我们立马就给你回滚行不行呢?
12:09
啊,理论上的话呢,死锁问题肯定是能解决,但是呢,我们可能会误伤,什么叫误伤呢?你看我刚才这块啊,如果说呢,我们执行了这叫第一步操作一,这个呢,我们叫这个操作二,然后呢,这个我们叫操作三,那我们执行完这个操作三的时候呢,其实这时候呢在等待,这个的等待其实不是死索,但是如果呢,你要是超时时间是一秒钟的话呢,我们等了一秒钟,立马呢这个事物就回滚了,那其实呢是不合适的,对吧?等待呢,我们说是正常的啊,那你把这个参数改了以后呢,不光伤到了,不管呢,是把死锁问题解决了,你把我们这个正常的一个等待呢,是不是也给伤到了,所以这个呢不太合适,怎么办呢?我们下边提到叫死锁的一个检测机制。这个呢,就是咱们刚才看到这个演示效果当中,为什么说咱们一构成思索以后,直接呢,我们这里边呢,就会报这样的一个信息,它检测到这个思索了,对吧?诶这呢,我们提到了一个叫wait for graph这样一个算法来进行主动的一个死锁检测,这呢是一种主动的一个检测行为。
13:05
啊,怎么做的呢?哎,我给大家举个例子啊,这呢依赖于两个表,一个呢叫事物等待链表,我们这里边儿,比如说T1T2T3T4有四个事物呢处于等待状态,好,那么我们另外一个呢,叫锁的信息链表,比如我们针对肉一这条数据啊,我们呢有T2呢获得了一个排大锁,然后T1呢现在也在申请一个共享锁,然后针对肉二这条数据的话呢,我们先后的这样的一些锁的情况啊这呢叫锁的信息列表,基于这两个表的话呢,我们画一张图,这个图呢叫wait for graph等待图。这个等待图里边呢,我们有这个节点也有呢,相关的这个连接的这个线,这个节点是什么呢?节点就是我们这里边这个等待这个事物,等待这个链表里边这几个事物啊,T1T2T3T4好,然后的话呢,诶大家你看我们这时候T1是不是在这儿,他呢,是不是要等这个T2啊,所以我们就说T1呢,哎,这个你画一个箭头叫等T2。啊就可以了,然后呢,我们再看这块,这块的话呢,针对我们这个呃T2来说,T2在这儿,T2呢是不是要等这个T4和T1啊哎,所以你这块呢,T2呢要等这个T1 T2呢要等这个T4,那针对这个T3来讲的话呢,T3要等这个142,所以呢,他要等这个呃一,他要等这个四,他要等这个二,哎这样的话呢,我们就把这个呢就都画完了,那其次的话呢,这块哎其实诶他需要呢等这个T1啊这个呢我们把这个也可以。
14:23
哎,这个是共享的了是吧,他俩是共享的了啊OK,哎,共享的话呢,这个它俩就不需要这个等待了啊行,那么这儿呢,我们就构成这样的一个图形了,那在这个图当中啊,我们看一看是不是会出现一个环,你比如说这块呢,出现一个环,就非常短的俩呢,就构成个环了。对吧,有的时候呢,我们可能会出现这个三个构成环的情况啊,小心一点行,那么通过这个呢,诶我们叫wait for graph呢,我们就能发现呢,它是不是有这个死速的情况,一旦呢,检测到有这个死索了,这是我们这个印度DB这个存储引擎啊,会选择回滚安度量最小的一个事物。啊,你比如说在我们这个题目当中呢,相当于就把我们这个事物呢,做了一个回滚操作了。
15:03
啊,咱们这俩呢,其实它这个事物的这个回滚力度呢,差不太多对吧,那实际情况呢,有可能有的这个力度呢,小一点是吧,回滚的成本低一些,他就选择这个成本最低的进行一个回滚操作,他一回滚的话呢,是不是就使得呢,其他这个事物呢,就能够正常的去执行了。OK的啊,那这里边呢,涉及到我们这个,呃,使用的这个参数呢,它默认的是个on,我们才能够去开启这样的一种检测的方式啊,这个大家注意一下。好,那么这种方式的话呢,有什么缺点没有。啊,缺点的话呢,我们说也是有的,比如说呢,我们这里边并发的一个线程比较多,100个并发线程呢,现在要更新同一行数据,那这时候大家都得等待,那我们是不是就要画个这样的图,哎,画这个图的话呢,我们其实呢,相当于我们就要便利一下你啊跟其他这些呢,有没有这个等待的一个关系,每个都这样去判断,是不是100乘100就成了1万次了,那是我们要有1万个线程呢,那时候呢,检测的次数就太多了。啊,这个成本比较高,那怎么办呢?啊,第一种方式那就别用了。啊,别用了,那那不行是吧,哎,我们要是通过这个太帽的话呢,这个太不靠谱了,所以说我们要不用的话呢,咱们真要出现思索的话呢,去在这僵持着不合适,所以对我们这个业务呢,会产生影响,所以咱们得用,那既然要用,那我们就得考虑如何呢,去降低这个并发的这个量比较大的这种场景,去控制这个并发访问的数量,怎么办呀,我们可以在这个实际的数据库中间件当中啊,对于更新相农行的这个数据啊,我们让他排个队啊,一排队的话呢,我们就尽量的就减少这个思索的一个。
16:26
呃,这个检测的一个工作了,OK,好,那么再进一步这个思路的话呢,我们可以考虑呢,就是减少这个锁的一个冲突,你比如说我们这个每一个超市啊,这个连锁超市啊,涉及到比如说呃五六十家这个超市,这个超市的话呢,每这个买一个东西啊,这个呃有支出有收入,这时候我们都在这个,呃总部这块呢,去更新这个表,这个显然就不太靠谱了。啊,这时候我们就会增大这个所冲突的一个概率,怎么办呀?诶平时的话呢,你现在你自己这个店里边儿呢,进行这个呃,这个账目的一个添加呀,呃,这个增加呀,减少啊这样一个行为,然后呢,在固定的一个时间,比如说凌晨几点,每个月一次,诶我们呢,把这个数据呢,再更新到这个总部这样的一个表当中就可以了,减少这个所的冲突是吧。
17:09
好,这是一个思路,好下面我们再谈谈该如何呢去避免这个思索,第一个呢,我们合理的去设计这个索引,使得呢,让我们这个S呢,在检索过程当中,尽可能少的去锁定相关的一些行数据啊,你想想你要是这个索引设计不合理的话呢,本来我们要查询相关数据呢,我们锁三行数据就行,你这块呢,一便利啊,锁了30行,那这时候呢,你锁的数据越多,是不是就越有可能造成思索的风险呀。OK,然后下一个呢,就要调整业务啊,这个SQ的一个执行顺序啊,避免呢,像update delete这种长时间持有素的,这个在我们相关的一些事务前面呢,诶这个占用着的啊,这个大家小心一点是吧,下边呢,就避免这个大事物,我们把这个大事物呢,给它分解成多个小事物,你事物越小,那你握你单位这个事物握取的这个锁呢就少一点,那么你出现这个死锁的概率呢,是不是就小一点。是吧,你占的越多啊,这个占着茅坑不拉屎是吧?啊,占的越多的话呢,这时候你的风险呢,其实就越大啊,越有可能会构成思索。
18:04
好在这个并发比较高的这种系统当中,我们说不要呢显示的去加锁啊,有点自己作的这种感觉是吧?OK,然后下边呢,就是降低事物的隔离级别,当然这个呢,我们得具体问题距离,呃,这个来看了啊,你看适不适合我们去降低这个隔离级别,OK,那么关于思索的话呢,我们就刻画到这儿啊,整体来看呢,其实是比较简单的啊。
我来说两句