00:00
好,同学们,咱们接着来看一看这个页索啊,那前面的话呢,我们已经说清楚了,叫表索和行索,对吧?那页索怎么回事呢?哎,咱们说呢,通常一个数据表啊,它当中的这个数据呢,是不是放在多个数据页当中啊,对吧?那么一个页当中呢,我们是不是放多行数据啊,那所以说呢,从这个力度上来讲的话呢,我们这个页索啊,它就介于表锁和行锁之间。啊正因呢,从力度上来讲呢,它介于这俩之间,所以呢,它表现出来的特性呢,也是介于这俩之间的啊你比如说呢,这个一二锁它的开销啊,介于表锁和行锁之间,那我们知道呢,这个表锁的开销呢是最小的,行锁的开销呢是最大的是吧?诶它呢介于这俩之间,它呢也会出现这个死锁的问题,那我们讲呢,说这个行锁呢,会出现词索对吧?啊页索呢也会出现,那就比如说我们这样的一个事物A,他呢先要锁定这个,呃,这个数据页啊,这个数据你可能锁定这里边的一行,或者我们整个数据页啊然后的话呢,我们还需要呢,再去操作的时候呢,锁定另外一个数据页,而我们这个时候呢,另外的一个事物呢,他要先锁定同样的这个页,就跟他正好反着来啊,然后呢再去锁定这个页,他们呢才能把这个15执行完,这个呢就是必须先锁它再锁它,那么这时候呢,我们这个15A呢,锁在锁了这个页的时候呢,我们B呢就把这个页给锁住了,他俩呢就接着都等待对方呢先解锁,这个时候呢,不就出现这种死锁的场景了吗。
01:16
对吧,啊,那么在我们这个业索层面呢,也会出现思索。那么这个锁定的力度啊,也介于这个表锁和行锁之间,那就意味着它的并发性呢,就介于这俩之间啊,这个大家了解一下就可以了,行,那么我们在实际开发当中啊,一般呢,咱们选择的话呢,像印度DB,那就是我们选的这种叫行锁了是吧?那MYS的话呢,由于你不支持这个行锁,那就进行这种表级的这种锁定啊就OK了啊页锁的话呢,哎,大家做一个了解就行。啊,但是下边这个事儿的话呢,大家可能一般呢,呃,有不清楚的同学啊,这个呢,我们给大家解释一下啊,这里边提到什么呢?就我们每一个层级啊,你表所呀,行所呀,一锁呀是吧,每个层级呢,这个锁的数量它是有限的啊这呢会涉及到就锁呢,它要占据内存空间那个事儿啊,所以呢,我们这个锁空间的大小啊,它是有限制的。
02:05
那么这个锁空间大小有限制,那是不是就意味着我们如果说这个层级上的锁数量呢,超过了这个层级的阈值的限制了,你比如说我们这个行锁啊,它有个表空间,那个它有个锁空间的大小,然后你锁的数量超过这个阈值限制怎么办啊,我们说就会自动的进行锁升级。啊,这个是自动的行为,不用我们自己手动的去改啊,那么所升级的话呢,就有可能把我们这个in DB中的行锁就升级为表锁,那一旦出现升级了,那么这个呃特征呢,也就表现为这个表所的特征,首先呢,你占用的这个空间呢,降低了,那当然其次的话呢,你这个并发性呢,是不是也就跟着降低了呀啊这个大家注意一下这个问题啊,就是一般情况下呢,其实我们都不会来超过这个锁空间的大小的啊,当然这个在极端情况下呢,有这样的一个风险啊,这个大家注意一下就可以了。行,那么这样的话呢,我们关于从这个叫呃操作的这个数据的力度上来讲的这样的,呃三类情况我们就讲清楚了啊,这块应该算是我们讲的比较重的一些内容,哎,当然呢,下来以后呢,自己呢再去体会体会啊,去真正的实操操作一下。
03:08
然后接下来的话呢,我们再考虑啊,从这个对待所的态度上来进行划分啊,态度上划分呢,我们分成呢,叫乐观所和悲观所啊态度上对吧?啊就跟那刚才呢,前面我们讲这个锁概述分类的时候呢,提到过说镇上有一瓶水是吧?啊就剩这个半瓶了,然后呢,这个乐观的人会看到说,诶不错,还剩半瓶水,那么悲观人呢,看到说哎呀就剩半瓶水了是吧?这就是我们对待这个情况的不同的看法,那么同样的在我们呢,诶使用事物呢,去操作这个表中数据的时候呢,那也会有两种不同的态度,然后呢,分成叫乐观派和这个悲观派啊注意这面是我们这个所的这种设计的一个思想。啊是锁的这种设计的思想是一种思维方式啊,你要注意啊,上面呢,我们讲这个力度的时候,是不是真正你去实操的时候的一些行为是吧?诶一些所的分类啊,那么呃,首先呢,我们来讲一下,这个叫悲观锁。
04:01
悲观所的话呢,就是对数据被其他事物的修改啊,持保守态度啊,就是我们说的总有在总想着有刁民想害朕是吧,哎,就这种悲观的这样一种态度,哎,我们这种情况下啊,悲观所这种情况下呢,我们会通过数据库自身的锁机制啊来去实现。啊来去实现,所以说这种悲观锁的,它是一种设计思想,这种思想的具体体现呢,哎,就是使用锁,就我们上面提到的那些表锁呀,行锁呀是吧,哎,这个X锁呀,X锁呀是吧,这样的一些锁啊,那乐观锁的话呢,一会儿我们说啊,它主要是通过程序来去体现的。啊,通过程序呢去来体现的。好,那下边我们看一下说被关所呢,总是假设最坏的情况,每次呢,去拿数据的时候呢,都认为别人会修改啊,所以呢,每次在拿数据的时候呢,都会考虑呢进行上锁,那么你一上锁的话呢,是不是就会阻塞别人呢,对这个数据的一个修改了,对吧?哎,这个是很正常的嘛啊那么共享资源每次呢,只给一个县城的使用,其他县城阻塞,用完以后呢,再把县程转给其他的县啊,再把资源呢转给其他县城。
05:06
啊,那这个呢,典型的啊,大家呢,接触过Java的话呢,Java里边这个snchizedt lock是吧,诶这种独占所的思想呢,其实就是我们悲观思想,悲观所这种思想的一种体现。啊,大家应该很清楚对吧,在我们Java程序当中,一旦呢,你使用了C或lock,那么这时候呢,诶哪一个线程啊,拿到了这个C中的这个同步监视器,它就能去操作,那其他的线程呢,这时候都得去阻塞。啊,那么很显然呢,这种呢,会导致我们的并发性呢,会比较差一些,当然呢,对于这个数据的安全性啊,是不是有很好的保障,对吧?诶这个注意一下啊,下边我们给大家举个例子啊,比如这个商品的秒杀的过程当中啊,尤其在秒杀的这种场景本身呢,商品数量不多,但是这时候呢,同时呢,可能操作的这个呃,事物绘画就会多一些,对吧?那么这时候呢,我们一定要避免一种叫超卖的现象啊,超卖清楚吧?啊,你有100个商品呢,你卖了101个,这就属于超卖了,对吧?啊比如说我们这个商品表中呢,有个字段呢,叫康ity来表示我们这个商品的这个库存量啊,以这个华为比如MATE40啊这个手机为例。
06:06
啊,华为MATE40啊,据说呢,这个2022年的,这个上半年说要发布这个MATE50了啊,这个华为呢,确实不容易啊,这个我一直呢都在用这个MATE30啊,这个其实确实做的还是挺好的啊,很期待呢,华为MATE50在美国的这样一个主机下呢,还是有很多的突破的啊,特别的不容易啊,这个给华为点个赞,这个华为呢,这个MATE40手机啊,ID呢是1001啊现在这个产量比较少是吧,秒杀啊就100个,那么如果呢,不使用这个锁的情况下呢,首先我们查询这个库存发现呢,诶这个有100台,好,然后呢,我们就去根据这个ID号呢,去生成对应的这个订单。啊,生成订单了,那么接下来的话呢,我们去修改一下这个库存,那这个呢,你看用户呢,到底是买多少台是吧?诶这个我们就从库存里边做一个冲减就可以了,那么在这个并发量比较小的这种场景下呢,是没有问题的,但是你如果要是一种秒杀的场景啊,那这时候呢,如果同时啊想去购买的这个用户呢,远远大于啊我们这时候呢,这个剩下的这个数量啊,这个时候呢,我们就可能会出现这样的场景,所以呢,线程A啊,或者我们叫这个绘画A啊,15A啊也行。
07:10
呃,他呢,一查询有100台手机,这个一查询有100台手机,然后呢,这个事物呢,它先生成这个订单,他也生成订单了,相当于就是订单都生成了,生成订单的话呢,我们接下来就进入这个冲减库存的这个环节,那假设呢,我们这时候呢,他要买这个60台手手机,他也要也买60台手机,你想这时候呢,都冲减以后呢,总共就100台,是不是成了负20台了啊,这就出现这种超卖的现象。啊,超卖的现象啊,那这个呢,显然是不可以的,那怎么办呢?那我们就得保证呢,县城A或者县城B的是不是他在,呃这个查询生成订单,包括减库存的时候呢,他得先都执行完,然后呢,再让其他的这种县城呢,再去执行,才能够避免这种超卖的场景。对吧?哎,那么如果我们用这个呃,这个这个SQL语句啊来实现的话呢,就相当于呢,我们去加锁了。来通过我们加锁这样的一个情况呢去体现啊,那么加锁的话呢,从我们一开始这时候做查询的时候,我们就for update,相当于我们是不是加了一个叫排查锁呀啊,那么这时候呢,其他的这个事物呢,如果你要是再通过这种呃,X啊X啊这种方式呢,再去做查询,就都得是一种阻塞的状态。
08:13
对吧,你就都得等着才行啊,那么当我们这个事物呢,呃,他订单生成了,然后库存也冲减之后,那么其他的事物或叫现成了,你再过来做查询,看看还剩多少,看看还能不能买是吧?诶就这样个情况。啊,这个大家注意一下,那么此时的话呢,我们这个select for update,这就是属于我们MY当中的悲观锁啊,悲观锁啊,这个这个情这个情况啊,然后大家需要注意的啊,这个事儿呢,我们要注意一下,前面呢,我们没有明确给大家提过,说呢,我们这样的一种方式呢,诶它呢,执行过程当中会把所有扫描的这个行啊都会加上速。注意是扫描的行都给加上锁,所以说呢,我们在使用的时候呢,一定要确保咱们查询的时候啊,你比如我这时候是不是ID等于1001是吧,一定要确保ID这个上面呢,是有索引的。
09:00
啊,是要有索引的,为什么呀,因为我们要有索引的话呢,你比如我们对应的这个还是个主键哈,那这样的话呢,就看这个B加数,我们一直往下找扫描的话呢,是不是就直接定位你这个ID1001这条数据了,我们就把它锁住,相当于就是你锁的力度呢,其实还是比较小的,如果ID呢,还有一个1002是不是不影响啊,就是你锁的是这个,然后人家这个呢,还正常的,还能够别的事物去操作,对吧?但是如果说你要是没有这个锁的话。诶不是没有我们这个索引的话呢,假设你这个字段呢,还比较靠后,那我们从这个表里边的话呢,就是一条一条这样去检索,那检索到这儿呢,才找到这个1001了,那么它会把它检索到的这个数据呢,啊,因为我都不确定你可能会要操作,他会把你扫描到的数据呢,全部都加锁。啊,那这时候的话呢,是不是就影响别的这个呃事物呢,去操作你本身不需要的这个数据了,对吧?诶所以这时候呢,我们要注意在这种场景下的话呢,我们需要呢,根据你这个过滤条件,适当的一定要设计这个索引,而且呢,要让它用上索引啊,要让我们这场用雾化器呢去用上索引。好,就是这个搜Q局呢,写的时候呢,要注意一点啊,OK,这个呢是我们要给大家强调的这个点啊,那么下边呢,提到这个悲观锁的这种适用场景啊,不是特别多啊,因为呢,它使用的我们这种所机制呢去实现,这个时候呢,会导致我们这个并发性呢,其实就会比较差一些,对吧?哎,并发性就比较差一些,那么尤其呢,是对于这种长事物来讲,那我们这时如果锁定了你这个事物还特别长,这个时候呢,是不是呃导致我们其他这种并发的事物呢,都得等着啊,这个呢是我们呃这个无法承受的这样的点啊,怎么办呢?这我们要想一种更好的方式啊,就是称为呢叫乐观锁。
10:35
哎,叫乐观说啊,他对于我们呃,这个一个数据被多个失误呢,操作的时候呢,他认为这种并发问题呢,会比较少一些啊,他就要比较乐观。嗯,OK。好呃,下面呢,我们就来看一看这个乐观锁是什么情况啊,那乐观锁啊,虽然说我们叫锁了啊,但是它相较于这个悲观锁呢啊,悲观锁就是拿我们数据库中的这个锁机制来实现的。啊哎,所机制来实现的,而我们这个乐观所呢,是在这个程序啊层面来实现的,诶你看注意它没有采用数据库自身的所机制,而是用这种程序来实现啊,那么这个乐观所呢,它适用于比较读比较多的场景啊,因为我们说呢,呃,读和读啊两个事物呢,都去读同一个数据啊,都读的话呢,其实是没有并化问题的,对吧?所以我们既然乐观的话呢,那你事实情况呢,你确实你也要你这个事儿呢,你也别做的太过分哈,你也要乐观一点啊,乐观是什么呢?就是你这个真实的事物去操作这个数据的话呢,尽量也是这个改的情况要少一点是吧,你要这个改的情况特别多了,咱要保证安全性,那你是不是就真的得悲观一点了?
11:37
对吧,就成这样了啊,所以说呢,我们这个乐观所在它的适用场景呢,就是我们这个读操作就比较多一些,改操作呢,哎,就相对来说要少一些才可以。好,因为任何事物都有利有弊,你很难说呢,这管所就全部碾压被关锁啊,是不是就完全把被管所给干掉,那不至于啊,他们有不同的这种适用场景啊。那么乐观所的话呢,我们说通过程序来实现呢啊,通过具体什么情况呢?比如我们通过这种版本号机制,或者叫CS这个机制啊,一说到CS的话呢,很多同学学过这个GC的应该比较清楚啊,我们里边呢,是不是重点还讲解了这个CS这个机制,对吧?OK啊,那么在我们Java中的GUUC啊,不就是我们这个Java u concurrent啊,就是这样的一个包啊,里边涉及到的相关的这个API啊好,那这块呢,咱们GC呢这块就呃,先不去这里边展开过多去说它这个事了啊,然后呢,针对我们这边提到这个乐观所的话呢,我们说有两种实现机制啊,这块我们举例一下,第一个呢叫版本号机制,第二呢叫时间戳的机制。
12:33
好,那么这个版本号的机制是怎么回事呢?就是我们给这个表啊设计一个版本的一个字段啊,我们这时呢,叫一个version吧啊让我们去读的时候呢,就是比如说我们针对啊这个表中的啊一条数据啊,我们去读是吧?哎,我们先读,然后接下来的话呢,你有可能是不是也要修改啊,假设呢,你要修改这条记中的这个version呢,假设我就先写成一了啊这个事物呢叫A,那么A事的话呢,先读一下这个数据,哎,这个version呢是一,然后接下来的话呢,我是不是要对它进行一个update操作了,哎,同样的还是这个事物啊,我去update操作的时候呢,注意你看我去修改,修改的时候呢,我一定要找是version,就等于这个一的这个数据。
13:11
那我就找等于一这个数据,那如果呢,这时候读的时候呢,这个版本是一,然后呢,你去改的时候版本还是一,那就说明呢,在这个期间没有别的事物呢去修改过它。因为只要修改的话呢,我们就会给这个version呢,去加个一啊,这要注意啊,那我们去修改完以后呢,你这个数据呢,相应其他字段都改了,然后对应的你记得把我们这个呃,Version是一呢,你比如说你改成12了。啊,你看这时候我们有个version的加一是吧,你改成二了啊,那么另外的这个事物呢,B过来了啊,B过来时候呢,B呢来一读这个时候是二,然后呢,读完以后呢,他要接着去修改,修改的时候呢,他也去找这个我二的,呃,这个记录,那这时候发现还是二,那就说明你刚才读完下边你在写,读和写这个之间没有别的事物呢,去修改过它。啊,所以说的话呢,你相当于理论上来讲,我们就使得他这两个查和改的这个行为是不是保证了一个原子性啊。
14:02
对吧,哎,就相信你在你这个查询和修改这个期间啊,没有别人呢去操作过你。啊,没有别人呢去操作过你这个具体的数据,就像我们这时候一查,说这个订单呢,里边还剩这个,呃,比如100台呢,是吧,或者说呢,还有这个手机呢,然后我们这块呢,接着就去冲检一下啊这是没有问题的啊啊那么呃,能够限制什么样的场景呢?就比如说我们这时候呢,诶A呢,这时候去查的时候呢,它是一,然后呢,这个诶这时候B呢,他也查了一下,他也发现是一,然后呢,A接下来呢,想想去修改,但是呢,A这个修改行为呢,没有B快啊B呢先把这个数据给改了,改完之后呢,这个version呢,我们就改成是二了。啊,这是这个B呢,先改的啊B改完之后呢,这个A呢,他再去做修改的时候呢,这时候他要找的是where version,等于是不是一啊啊这个你找一的时候呢,发现呢,已经没有这个数据了,那就相当于你要修改的这个记录不是你当初查的时候的那个数据了,那有可能这个产品数量已经被冲减了,是吧?啊那时候呃,修改就修改没成,没成的话呢,那就相当于你自己再去查看一下,一看哎哟,这都是二了,然后你得基于这个二呢再去考虑去修改啊就这个意思。
15:06
好呃,那么这种方式呢,其实就类似于我们说的这个版本控制工具里边啊,像SVN或CVS呢,诶它的这种设计的思路啊,就是这样子的,那如果说我们自己啊,当前这个呃代码咱们修改以后的话呢,检查一下这个当前代码,这个版本号跟我们服务器上这版本号呢,看是不是一致,诶如果一致呢,我们就把这个数据啊,这个代码呢给提交上去,那如果不一致的话呢,我们就需要呢,去更新一下我们这个服务器上这个代码了,然后呢,你再去修改再提交是吧,这样的情况啊。嗯,这个呢,就是我们说的这种版本号的这种机制,然后的话呢,我们再看一下这个乐观所中的这个叫时间戳的这种机制啊,其实这种机制呢,跟我们上面呢,其实是原理是一样的哈,上面呢是用一个版本号,我们这块呢是用这个时间戳啊,如果你当前这个数据的时间戳跟更新之前啊,你取到的这个时间戳是一样的。啊,那这时候我们就能更新成功啊,否则呢,就出现版本冲突啊,其实呢,这不就跟这个时间戳呢,就相当于我们这个version一样。
16:02
啊这样一个情况,行,这个咱就不多去解释了啊,那么我们举一个这个秒杀的场景啊,来去体现一下我们这个乐观锁,我们还是呢去做这个查询啊1001啊这个ID呢,它这个库存,哎查询到这个库存呢,发现它是大于零的,那么们就会呢,生成一个订单,然后接下来的话呢,我们就去修改这个商品的数量,哎这个时候的话呢,我们需要呢version啊等于啊你查到这个version啊,就是跟你之前呢,在查询的时候,那个version呢,它必须得是一样的才行。那就保证呢,相当于是这个操作和这个操作呢,相当于是在同一个事物,哎,它有一个原子性啊,相当于你在这个操作的过程当中,别人呢,没有改过啊,因为别人要一旦改过的话呢,这个version呢,你就找不到了,是吧,来注意一下。好,嗯,这里边呢,有一个点需要大家去注意一下,就是呃,我们呢,在这个在多台数据库服务器的这个情况下啊,然后呢,我们会出现这种读写分离,那么要是读写分离的话呢,诶原理上呢,会出现我们查的时候呢,用的是从机,诶查的时候呢,诶这个我们用的是这个从机,而这个修改行为的话呢,我们用的是这个主机啊,那么这个从机查出来对应的这个worseson跟这个主机上这个worse呢?呃,有可能就导由于我们这个主从的这个同步的会出现问题哈,那它一旦出现问题的时候呢,我们上面是个沃森,下边是个沃森,匹配不上了,是不是导致我们那个更新呢,可能就会失败。
17:17
啊,这种情况呢,会出现啊,那怎么办呢?那我们就必须保证了,咱们这时候的这个查询的行为呢,也是用的主机啊,因为你下边这个改的话,肯定是用主机了,所以我们希望上面一查呢,就引用主机,所以咱们可以使用叫强制读取叫master表,就我们这个主机的啊,就主从的这个主啊,不是这个一台一台机器的意思啊,这个主就是主机从机啊这个意思的,主机从那个master这里边呢,我们去读这个。哎,数据啊,这个时候你这个version呢,跟这个version室呢,我们才能够保证呢,不是因为这个主从复制的问题出现的啊OK。啊,应该能听懂啊好,那么如果说呢,下边提到我们要频繁的去对一条数据呢,诶如果有多个这个事物啊,同时呢,要针对一条数据呢,频繁的去修改,那么这时候呢,你想想,比如我们这种读到的是一,然后这种好多这个都过来都读到是一了,然后现在他那改成二了,其他这一堆事物呢,去改都是基于一呢,去改的,是不是全都改失败了。
18:11
啊,那就是候我们会导致呢,在业务层面感知上呢,会有大量的这种失败的情况出现啊,那怎么办呢?诶我们还可以呢,通过这样的一个方式啊,就是确保呢,我们这个quantity减number呢,它是大于零的啊,就是不要出现这种超慢的场景啊,我们呢也可以啊就这样一个情况呢,去做个操作啊,这样的话呢,就相当于是你后边买不了的话呢,那就真实的是发现呢,就是哎库存已经没有了是吧,只要库存有的话呢,还可以去做这个冲减啊。好,那这呢,就我们说的这个乐观锁,那么总体来讲呢,我们做一个呃对比啊,说这个乐观锁呢,它适合于这种毒比较多的这种场景啊,就是乐观嘛,它就相对于认为来讲,我们事物对这个表中的数据呢,修改的情况比较少一些啊,这个读的话呢,因为我们说是允许并发执行的啊,所以呢,诶乐观所适合于读多的场景。这个它是用我们程序来实现的啊,它呢不存在这种词索的场景啊,代码实现的是吧,这个时候呢,没有这个词索的场景,然后呢,我们这个悲观锁的话呢,适合于这个写操作比较多的场景啊,因为呢,总认为呢,这个刁民要害朕嘛,所以我这种呢,都得是排他的是吧,这种情况要多一些啊,那进而的话呢,排他性多呢,我们这个并发性呢,其实就要差一些是吧,但是呢,能很好的去防止这个读写和写血的这种冲突问题。
19:23
啊,没有问题啊行,那么这样的话呢,咱们就把这个乐观锁和悲观所,哎这个呢,我们就这种设计的思想呢,咱们就说清楚了啊,大家呢体会一下。
我来说两句