00:00
好,同学们,咱们继续来讲这个事物啊,来看这个第15章所。呃,前面的话呢,咱们讲了叫锐度日志和安度日志对吧?诶,通过这两种事物日志啊,咱们来体现这个事物的叫原子性,一致性啊,持久性是吧,那么我们这个事物的这个隔离性应该由谁来体现呢?哎,我们说呢,通过这一章呢,讲的这个所机制呢来实现。好,那下边的话呢,我们来讲讲这个锁的内容啊,这个锁的话呢,我们说首先它不是一个数据库的一个概念,那我们在这个编程当中啊,只要涉及到了我们多个进程或者线程去并发的访问某一个共享的资源,那这时候呢,我们都需要呢,通过这个所机制呢,来去解决这个并发的问题,对吧?啊那尤其呢,是针对一些敏感的数据,你比如说订单呀,金额呀,对吧,那么这样的数据呢,我们是一定要保证这个数据的一个叫完整性,或者叫一致性,或者叫安全性是吧?这样的情况的,那么这时候怎么办呢?我们就需要呢,保证这个数据啊,在任何的时刻啊,或者说呢,某一段这个时间之内呢,最多呢,只能有一个线程呢在访问啊,当我们这个线程的访问结束以后,那么其他的线程呢,才可以继续进来呢,访问这个所谓的共享的数据。
01:10
对吧,诶这就是这样个场景,好,那么在咱们这个数据库这个层面呢,咱们说呀,其实同样的会遇到这个共享的这个资源问题,那么这里边儿这个资源问题呢,除了咱们说的一些计算资源,比如说像CPU啊,像RA啊,像那IO资源啊,除了这些资源的话呢,需要做这个争抢之外呢,我们还有啊,需要共享抢的这个数据啊,那就是我们数据库中的这些数据了。那么这些数据的话呢,我们说为了要保证这个数据的这种一致性是吧,或者我们操作的过程当中啊,多个呃会话啊,多个请求,或者叫多个事物啊,对于我们数据库中这个数据呢,在并发访问的时候呢,诶,它的这个安全性,那我们就得需要用到这个锁机制。那需要用到这个锁机制,跟我们在这个,比如说Java层面呢,咱们使用的这个C呀,Lock锁呀,或者说我们后边讲的这个GC啊,其实是一样的啊,类似的这种解决方案啊,用这种锁机制。
02:02
那么锁机制的话呢,也就为我们MYSQL当中提供的各个隔离级别呢,诶提供了这个保障,那么具体的话呢,这种隔离级别是如何保障的,那就是我们下边具体的要讲的这么多种丰富的锁啊,那么呢,我们呃锁使用锁的话呢,呃,这个会出现这种锁的冲突呢,这是不可避免的了,对吧?我们现在这个事物呢,正在操作呢,其他事物呢,你想过来啊,已经锁住了是吧,你这是怎么处理,包括呢,我们后边还有这个死锁的问题是吧?那这呢都是会啊出现的啊,那么锁的这种冲突呢,当然它会影响我们这个并发的性能了,但是这里边呢,就会有一个平衡,对吧?诶我们既想提高这种并发性,但同时的话呢,我们还一定要保证这个数据的一个完整性,一致性啊,安全性是吧,这样的一个场景。啊,那这里边呢,我们就会达到一个平衡啊,具体的话呢,我们再看一看,呃,咱们呢,只希望并发性高一点,那我们可以呢,让这个锁的力度的小一点啊,希望这个并发性的第一点呢,啊,我们可以让这个锁的力度大一点,比如说呢,有表锁呀是吧,行锁呀啊一锁呀,这种力度呢是不一样的啊我们下边继续讲的时候呢,再说啊好,那么咱们在讲具体这些所之前啊,咱们呢先呢,从宏观上呢,对我们并发事务访问相同的记录啊,咱们先有一个呃直观的印象。
03:16
啊,就咱们所谓的那叫大处着眼,小处着手对吧,咱们呢,先呢大处着眼一下,那么对于这个并发的事物呢,访问访问这个相同的记录呢,我们说会有几种不同的情况啊,这里边呢,首先我们提到呢,是相同的记录,那这个呢,相同的记录呢,你既可以看作呢,力度比较小的,那就是同一行数据,那也可以看到力度大一点的就是同一个表,对吧?啊这个呢,都可以一定程度上理解成的是这个相同的,首先的话呢,我们得保证你是相同的啊,你要是不同的,比如我们这个事物A呢,你访问的是这个A表啊,那你这个事物B的话呢,它访问的是这个B表,他俩呢,根本就是访问的数据都不一样,那是不是也不会出现我们所谓的这种数据的这种。啊,一致性问题会受到影响的,对吧,那你这该怎么着还怎么着呗,啊,各自访问各自就完了,那加锁有什么意义呢?对吧,就没必要了啊好,那我们这里边儿呢,肯定是针对的是啊,相同的记录。
04:07
那么针对这种相同的记录的话呢,我们说啊,咱们可能会出现的场景是什么呢?其实呢,主要针对就是我们针对这个相同的记录做的事儿,那无外乎呢,就是增长改查呗,啊那这里边儿呢,我们把这个查询呢,就看作是叫读操作,然后呢,增删改呢,就看作叫写操作啊那我们这个并发事物呢,咱们就以两个事物为例,那就是一个事物呢在读,另外一个事物呢也在读,那我们就叫读读的情况。啊,两个事呢都在写,那就是写写的情况,那一个事物呢,读一个事物写呢,就是我们说的这种情况。对吧?好,那我们一个一个来进行分析,首先呢,我们来看看这种独读的情况啊,两个事物呢都是读,有什么问题吗?哎,我们说呢,不会有任何问题,因为呢读操作呢都没有对这个机动呢产生任何的影响,那就大家都读就完了呗,对吧,没有问题的啊好,这种情况的话呢,我们就不用多说了啊,是非常安全的,不需要呢考虑这种锁的这种问题是吧?然后呢,呃,或者说我们即使考虑锁的问题的话呢,那你这两个呢,互相之间它俩也是兼容的,对吧?啊你不会说呢,因为我们这个事物呢,针对这条记录呢,加了一个毒锁是吧?然后我们这个事物呢,也去读你你也加个毒索,这俩呢,这个这个需要排队,没必要了啊,你就同时呢执行就完了啊。
05:18
好,那么下面的话呢,提到这个写写的这种情况,就是两个数呢都是写。啊都是血,那么这个时候的话呢,我们说可能会发生这种叫脏血的问题啊,脏血的问题呢,咱们在讲这个事物这个并发的这种问题的时候呢,提到过是吧,大家还有印象吗。啊,还有印象吗?你比如我们这呢,是一条记录,那我们这个事物A呢,对它呢进行写入,这个事物B呢也是对它进行写入的,对吧?诶这样一个场景啊,那我们这个记录呢,本身这个值呢,比如是一,哎,然后的话呢,我们AA跟B呢去读还都是一啊然后A的话呢,就把它改成实例二了。然后B的话呢,就把它改成是三了,对吧,然后A呢,这时候呢,做了一个commit操作啊给提交了,那他就认为我们把这个二呢是不是给固定下来了,但我们B的话呢,做了一个roll back,那我roll back完以后的话呢,相当于把这个三呢,是不是又回滚回去变成一了,那A再一查结果是一了。
06:08
是吧,也成这样一个情况了啊对,这就是我们说的这种脏血的这种问题啊,那么这个脏写的问题的话呢,你会发现呢,我们其中的一个事物呢,对它进行修改,还没改完的时候呢,另外的时候呢,你要过来参与,这时候就乱套了,怎么办呢?诶我们要想解决这个问题呢,必须呢让他们俩呢排队执行。啊,一个月来啊,你10A啊改完了清了没事了啊吧,提交了啊回滚了是吧,然后呢,另外一个事物呢,再进来,哎,就是排队呢去执行来解决他的个安全问题啊,那么这个时候呢,我们是通过这种所机制啊来实现的话呢,怎么体现呢?哎这里边我们就提到了一个叫锁结构的一个概念啊锁结构一个概念,那么这呢是一条记录啊,哪个事物呢,也没来操作它,那就是他自己,那当有一个事物,比如说那个叫事物T1。当这个事物T1呢,他要来操作这条记录的时候呢,发现呢,诶这条记录呢,没有提前的任何事物呢去操作它,那这时候呢,我们这个事物T啊,它就会生成一个锁结构,这个锁结构当中的啊字段啊这呢咱们先主要说两个字段,后续呢,咱们会说具体的这个锁的内存结构了啊那么这个字段的话呢,一个叫啊传三的信息,就是你当前这个事物是谁。
07:16
那以及的话呢,就是我们还有一个字段叫is waiting啊,它代表了当前事物呢,是否需要等待啊,这个呢,大家注意呢,就是我们只要是操作这条记录啊,那我们会生成这个锁结构,这个锁结构呢,是跟你这个事物相关的,也就是说呢,如果我们还有一个事物呢,也想来操作它,就会生成另外的一个锁结构。啊,不会说呢,我们有一条记录呢,是不是就这一个锁结构,不是它是跟事物打交道啊,有几个事物呢,就会有几个所结构好,那么如果说我们这个T的这个,呃,事物呢,是最先来想操作他的想写是吧。那么这时候呢,提前也没有别的事物呢去来阻塞啊,那这时候呢,他的一字waiting呢,就是false,意味着就是我们当前的这个事物呢,是可以执行的啊,相当于叫获取锁成功了啊,或者叫加锁成功了啊,就是这样个情况好,那么如果呢,在我们当前这个事物呢,还没有结束的情况下,那另外的一个事物呢又来了,那么这个事物的话呢,是不是也会生成一个锁结构啊,那对应的我们叫做T2。
08:12
哎,叫做T2,那么由于我们这个呃,T1呢还没有操作完,那针对这个写事物的话呢,我们说不得排队嘛,那这时候呢,你这个T2的事物呢,是不是is waiting呢,就是出啊,哎相当的就是你获取锁失败了啊,你得呢等待啊这样的场景,好,那么当我们这个事物这个T1你提交了啊,或者你回滚了都行,总之呢,我们这个事务呢结束了,这个结束以后的话呢,我们就会看看还有没有别的这个事物呢,哎还想去操作我们这样的一条记录。啊,如果有,那这里边是不是就有找到我们这个TR了,找到它以后的话呢,我们就可以把它的这个is waiting呢,从这个true的状态呢,就改成是false了啊,那就意味着它就不是一个等待的状态了,他就可以进行操作了啊就是这样一个场景是吧?哎这样子,哎整体来体现的就是他俩这个写的这个操作这个事物呢,就是得排队啊,一个一个来。
09:02
好啊,那么整体来看的话呢,我们有这样的几种行为,第一个呢,就是不加锁啊,就是这个记录上呢,没有任何的这个事物呢去操作它是吧,就不加锁,也没有生成对应的锁结构,那么第二呢,叫获取锁成功,或者叫加锁成功,那就相当于我们这个事物呢,呃,生成了锁结构了,并且呢,这个参数呢,它是false啊,它就可以执行啊类似于呢,像Java里边呢,讲那个C的,相当于它就获取到这个C后边那个同步监视器了啊,那如果其他的这个线程啊,在我们这叫事物了哈,那你要是没有啊,这个这个获取到这个锁,相当于别的事物呢,正在操作呢,那怎么办呢?哎,那你的这个参数呢,就是一个处的状态,就表示呢,你得等待。啊,你得等待啊,等待别的事物执行完以后呢,哎,你再去啊,这个这个操作啊。好,这呢就是我们针对这个写写的这种行为,大家需要记住的就是排队啊,排队执行啊,然后呢,对应的这个所结构的一个情况,然后呢,我们下面来谈,重点谈一下这个叫读和写的这种行为。哎,咱们重点来谈那个读和写的行为,就是一个事呢来读啊,另外一个事呢来写是吧,那么我们说在这种情况下呢,有可能会发生叫脏读,不可重复读和换读,诶这呢,是不是就咱们前面讲到的说这个并发的主要的几个问题是吧。
10:14
正因为有这样几个问题,我们才分成是不同的隔离级别了啊,咱们上边这个呢,脏血的问题呢,你会发现呢,在任何一个隔离级别下呢,其实都没有这种情况的发生啊,因为呢,我们就通过锁基症状让他排队执行,它就不会出现这种脏血的问题是吧?诶关键呢,就是有毒有血的时候呢,我们该怎么去解决这几个问题,哎,首先呢,大家得明确呢,是在读写的时候出现这几个事儿了啊,你比如说这个脏读吧。还有印象没有啊,这呢是具体的一条记录,然后这个A事物呢,比如他负责读这个B事物呢,来负责这个写好,那么这个脏读是什么情况呢?比如我们这个表中的记录呢,本身是一啊,本身是一,然后呢,这个B呢,把这个记呢从一改成二了,B呢没有提交啊,没提交的时候呢,A呢去读呢,把这个B没有提交的这个二呢给读出来了,那相当于是不是读到了B未提交的这个数据二,这就是个脏毒问题。
11:06
啊,A负责读,B负责写,这不就在读写的时候呢,出现的脏读问题对吧?好,那么这个不可重复读呢。啊,怎么叫不可重复读呢?好,现在呢A呢还负责读,他去这个表里边读数据的时候呢,诶读到这个记录呢,只是一,然后呢,他还没有结束啊,然后这个B的这个事物呢,他就把这个一呢给改成二了,他改成二以后的话呢,他还提交了现在这个事物呢就结束了啊那么这个A呢,哎,这个事物没结束,他再去读我们这个记录的时候呢,发现了这个值变成二了,诶两次啊,在同一个事物当中两次读,发现这个结果不一样,哎,这就我们说发生了叫不可重复读。啊,A呢负责读,B负责写,是不是在读写中出现的这种情况对吧?好,那么换读呢,是不是也类似啊,换读是怎么个情况呢?你比如说我们读这个叫ID呢,叫A小于十的吧,那我们A呢,在这里边去读的时候呢,他读到了五条记录,A呢还没有结束啊15,然后B呢往里边呢,AC了两条记录啊,做了两条insert啊,都是这个ID呢,十十以前的啊。
12:03
他添加两条记录之后呢,然后他自己呢,就提交了,那么A呢,再去读的时候呢,发现变成了七条记录了,多了两条记录对吧?啊,那么这个时候的话呢,相当于他就发生了这个不叫换读的问题嘛。哎,换读就是相当于你再去在同一个事物当中,你再去读的时候呢,这个记录多了嘛。啊,这叫换读是吧,哎,那A呢还是负责读,B负责写,所以呢,我们说的这三个问题呢,都是在哎,读写啊这样的这个事物当中出现的。好,那么不同的这个数据库厂商啊,它对这个SQ标准啊支持呢不太一样啊,你比如说咱们这个MYS在repeatable这个read啊,正常来讲的话呢,说SL标准里边呢,这个隔离级别是不是只能够解读解决这个脏毒和不可重复读是吧?但是呢,我们MYS的这个repeatable这个瑞,它还解决了这个换读的问题啊,这呢是咱们下一章要讲的那个MVCCC啊,再给大家呢,具体展开来说,好啊先说到这儿啊,那么针对于咱们上面提到了这样的几种场景,诶我们下边呢,说这个并发问题该怎么去解决呢?诶那这时候解决的话呢,主要指的就是脏毒,不可重毒和患毒。
13:05
啊,因为呢,咱们说了这个,呃,脏起的问题呢,是不是任何一种隔离级别都给解决掉了是吧?哎,我们下面呢,主要就看这个叫诶就是说白了就是你这个时候呢,不就是排队嘛,啊主要呢,我们想解决的就是这个,呃,一个是读,一个是写的问题。好,呃,那怎么办呢?诶两种方案啊,这个大家记住啊,就是两种方案啊,一种方案的话呢,诶我们不是一个叫读,一个叫写嘛,一种方案呢,就是呃读使用的是MVCC啊叫多版本并发控制啊血呢啊去加锁。啊,这是一种方案,那么另外一种方案呢,就是读和写呢都加锁。那都加锁好,我们先来看看第一种方案啊,第一种方案说第一种方案呢,说这个读操作呢,使用的MCC写操作呢叫加锁,为什么写操加锁呢?因为我们这个呃,增删改的操作一定是针对的这个是最新的这个记录的版本进行的操作,所以写的这个行为呢,我们一定要加锁,就像我们写写里边呢,需要加锁是一样的。
14:02
对吧,我们需要保护住它啊,而对于这个读的话呢,我们其实可以怎么办呢?诶咱们呢,生成一个叫做read。哎,剩那个read view,这个read view里边呢,它就有记录符合条件的这个记录了啊,那历史版本呢,有这个安度日志呢,来进行一个构建啊,就是我们的MVC的这个支持呢,还得需要这个安度日志啊,那么这个read view里边啊,它呢,记录的是什么呢?就是所有的已提交事务所做的一个更改,注意是已提交的事。也就是说当你这个read生成的时候呢,没有提交的事物,或者之后呢,才开启这个事物呢,这个都不会记录到当中。啊,都不会记到这里边好,那么我们读的话呢,就去这个read read里边去读,那么读的话呢,那就得看你是在什么样的隔离级别下了,哎,咱们比如说呢,叫read committed啊,叫读已提交是吧。那么在这种隔离级别下呢,我们说呢,一个事物啊,每次执行这个select的话呢,都会生成一个啊,都会生成一个,然后最后你commit的时候呢,诶这每次的select是都记录了,对吧?那么我们这个read view里边记录的它相当于是不是就是读已提交的这个数据了,你没有提交的数据呢,我们不会去记录了,所以呢,我们读的时候呢,从这个诶read里边去读的数据都是提交以后的数据,所以呢,就不会出现脏读的问题。
15:16
啊,咱们说脏读不就是读到人家未提交的嘛,啊,你从read里边呢,他只有提交过的,所以你就没有脏读没问题啊好,那么在这个repeatable read这样的一个隔离级别下呢,诶怎么办呢?我们这个事物里边呢,在它执行的过程当中,第一次select的这个操作中,我们才会去记入到这个read。啊,你第一次执行的,我们才放到这里边,你后续的那个select操作,我们根本就不去往read里边去记录,那这样的话呢,你看我们要是啊去读的时候呢,诶,我每次读的都是你第一次的这个select操作,你后续呢,修改过,或者你后续往里边插过记录,那都读不出来,那这样的话呢,你不就避免了这个不可重复读。还有这个换读的问题了。你想是吧,哎,每次读的话呢,都是读的第一次的,换读也是第一次的,你就不会看到他多的那些记录也不看到呢,呃,其他这个事物做修改过的这样一个情况了,哎,那这样的话呢,他不就避免这个问题了吗。
16:08
哎,就这样场景,所以说呢,我们使用这个macc啊,就提到这个呢,哎,他就相当于帮我们还解决了这个换堵的问题啊,就是这样个情况。好,大家呢,先有个这样的直观的一个理解啊,好,那么第二种方案呢,方案呢,咱们刚才提到就是读和写的操作都是使用枷锁了。啊,这个时候成本呢,显然就会更高一些,对吧,你看上边这个呢,读跟写呢,是不是没有冲突。而我们这个呢,就有冲突了啊,我们说呀,在一些具体的业务场景当中,咱们呢,啊,有可能还得针对这个独操作呢,也进行一个加锁啊,也就是说呢,独操作也得需要针对于是最新的版本,只要呢针对这些版本了,那你就得是考虑加锁,嗯,你比如说我们在这个银行存款当中啊,你呢,先去把这个账户的钱读出来,然后呢,你往里边存钱了,然后最后呢,把它写到数据库里边,在你读出来以后啊,你往里边去存钱的时候呢,别的这个事候呢,这时候必须让他们去等待,说白了就是你这时候呢,去读这个操作呢,是不是也要加锁呀。
17:04
哎,就这样个场景,那么我们这时候毒液加锁了,其实上就达到了,跟我们上面提到这个血跟血啊也都得加锁一样了,那不就相当于也是得排队执行吗。对吧,哎,那一旦排队了,我们说这个脏毒啊,不可重毒啊,和换毒的问题都能解决。哎,咱们刚才在这说的呢,是呃,MVC的这样一种场景呢,怎么去解决这个脏毒不可重读和换毒是吧,那我们在这个都加速的场景下,怎么去解决呢?比如我们首先看这个叫脏毒。啊脏读的话呢,就是我们这个呢A啊事物呢负责读啊,B呢事呢负责写啊那你这个B事呢你负责写,原来是一,然后呢,你把它改成二啊你只要是没提交我A呢,是不是现在就不能够去读啊,因为你这时候加锁了,我就读不了,是这意思吧?啊你提交了我才能够去读,相当于A呢就不会读到B未提交的啊这样的一个数据。那这呢,就保证了这个不会出现脏堵问题,没问题是吧?好,那么接下来那不可重复读呢。我们这个A呢事物呢去读这个数据的时候呢,是一啊,然后呢,B的话呢,想对它进行修改,想改成二,但是注意我们A现在正在读,而且我还加锁了啊,那这时候呢,你B呢15是不是必须想修改得等待呀啊当我这A事候呢,我提交了或者回滚了,然后呢你B这时候再去改。
18:17
啊,你说哎,我这时候我再去看的时候,不就变成二了吗?这呢,你再看的时候是不是相当于是另外的一个事物了,哎,就不是在我们这个事物内了。啊,这个呢,不可重复读,就也能解决。没问题是吧?好,那么接下来的话,我们看这个叫换赌啊,哎,这个换赌的话呢,诶看似的话呢,说也能够解决,但其实这块呢,就稍微的麻烦一些啊为什么这么讲呢?啊,咱们以这个不可重复读为例啊,咱们既然说叫加锁了,诶你比如说我们刚才提到说我们去读这个数据的这个值是一啊A呢,相当于正在读这个数据,我们就针对这一条数据,或者说呢是几条数据。啊,我们就都给大家去加锁啊,一条还是几条的,就看你这个circle扣怎么写了,对吧?好,那么我们把呢,哎,要访问的这个记录的话呢,我们都去给他加锁,那么这时候呢,当你这个B的这个事物呢,他想去修改这几条记录的时候呢,因为A加锁了,那你就得等待啊,就保证了这个我们这个不可重复读呢,这个情况呢,不会出现是吧?那现在的话呢,大家你看我们包括的脏组也是一样啊呃,那这个B呢,现在正修改这条记录呢,A呢,想去读读不了,因为人家加锁了是吧?呃,那是针对你这条记录加速的,那么换路呢,大家你看是不是就有事了。
19:24
什么事啊,哎,换做的话呢,是我们比如说一开始读这个叫ID啊ID比如说小于十的,咱们这读到了三条记录对吧?哎,那你说我针对这三条记录呢,我去加一下锁好可以你加锁了,那么这个B这块呢,是做的叫insert这个操作啊,咱们换读呢,是说的insert对吧?它往里边去插入数据,注意这时候插入数据呢,它会用你这三个记录的锁吗。显然不会啊,因为人家插入是不是新增的这个记录对吧?啊,那我们新增这个记录还称为那叫幻影记录了,这个幻影记录呢,你当初这个A呢,是没有办法去给他加锁的啊,那就是我们这个B呢,理论上来讲呢,它是不是就有可能叫C加入成功了,他一加成功了,你这时候再去访问ID小于十,是不是就变成那个五了,是吧,那就相当于你这时候加锁,根本你加不了人家幻影记录。
20:09
啊,因为一开始的时候你也没有这个这个这个记录呢,还是吧,那怎么办呢?啊这个呢,就首先呢,我们这呢,看似呢,就遇到了一个问题啊,这个问题的话该如何去解决呢?这其实就我们后边呢,印度DB行所里边提到这个叫A监所,还有这个呃,临建所啊,他就帮我们去解决这个事了。啊,你比如说我们这个呢,ID假设池,我们就让小于十的这块记录呢,全部都有锁,那你要加入的这个记录呢,是处在我们这个范围内的啊,就是间隙内的嘛,哎,那我就不让你去添加成功啊,只有通过这样一种方式呢,避免我A呢正在读你B呢去insert。啊,这个大家可能听起来稍微有点吃力啊,咱们讲到下边的时候呢再说。好,那么整体上来讲的话呢,我们针对于啊,注意听啊,我们针对这种一个读一个写的这样的这个嗯,两个事物的情况来讲,我们两种大的解决方案呢,就说完了啊,针对他们俩呢,做个对比,采用MCC这种方式呢,读用MCC写呢加锁这个呢,我们首先呢彼此不冲突啊,性能呢比较高,而加锁呢,就读跟写都加锁,那这时候它俩呢,是不是就得需要排队了呀啊它会影响性能啊,所以二者的区别呢,给我们看一看。
21:16
啊,那么下一章呢,咱们重点给大家讲的是这个MACC,所以这一章呢,咱们主要呢,来谈一谈这个加锁的方式啊读写是吧?哎,这块加锁的方式呢,我们怎么呢去理解好,那么首先呢,针对于我们这个引入这个锁之前的这些并发问题啊,咱们先有一个宏观上的一个认识啊。
我来说两句