1、 undo log版本链是个什么东西?

讲解这个MVCC机制之前,我们还得先讲讲 undo log版本链的故事,这是一个前奏,了解了这个机制,大家才能更好的理解MVCC机制。

简单来说呢,我们每条数据其实都有两个隐藏字段,一个是trx_id,一个是roll_pointer,这个trx_id就 是最近一次更新这条数据的事务id,roll_pointer就是指向你了你更新这个事务之前生成的undo log

我们给大家举个例子,现在假设有一个事务A(id=50),插入了一条数据,插入的这条数据的值是值A,因为事务A的id是50,所以这条数据 的txr_id就是50,roll_pointer指向一个空的undo log,因为之前这条数据是没有的。

接着假设有一个事务B跑来修改了一下这条数据,把值改成了值B,事务B的id是58,那么此时更新之前 会生成一个undo log记录之前的值,然后会让roll_pointer指向这个实际的undo log回滚日志

事务B修改了值为值B,此时表里的那行数据的值就是值B了,那行数 据的txr_id就是事务B的id,也就是58,roll_pointer指向了undo log,这个undo log就记录你更新之前 的那条数据的值。 所以大家看到roll_pointer指向的那个undo log,里面的值是值A,txr_id是50,因为undo log里记录的 这个值是事务A插入的,所以这个undo log的txr_id就是50 接着假设事务C又来修改了一下这个值为值C,他的事务id是69,此时会把数据行里的txr_id改成69,然 后生成一条undo log,记录之前事务B修改的那个值

数据行里的值变成了值C,txr_id是事务C的id,也就是69,然后roll_pointer 指向了本次修改之前生成的undo log,也就是记录了事务B修改的那个值,包括事务B的id,同时事务B 修改的那个undo log还串联了最早事务A插入的那个undo log

多个事务串行执行的时候,每个人修改了一行数据,都会更新隐藏字段txr_id和roll_pointer,同时 之前多个数据快照对应的undo log,会通过roll_pinter指针串联起来,形成一个重要的版本链!

2、 基于undo log多版本链条实现的ReadView机制,到底是什么?

这个ReadView呢,简单来说,就是你执行一个事务的时候,就给你生成一个ReadView,里面比较关键 的东西有4个

  • 一个是m_ids,这个就是说此时有哪些事务在MySQL里执行还没提交的;
  • 一个是min_trx_id,就是m_ids里最小的值;
  • 一个是max_trx_id,这是说mysql下一个要生成的事务id,就是最大事务id;
  • 一个是creator_trx_id,就是你这个事务的id

    那么现在我们来举个例子,让大家通过例子来理解这个ReadView是怎么用的

    假设原来数据库里就有一行数据,很早以前就有事务插入过了,事务id是32,他的值就是初始值 , 接着呢,此时两个事务并发过来执行了,一个是事务A(id=45),一个是事务B(id=59),事务B是要 去更新这行数据的,事务A是要去读取这行数据的值的 , 现在事务A直接开启一个ReadView,这个ReadView里的m_ids就包含了事务A和事务B的两个id,45和 59,然后min_trx_id就是45,max_trx_id就是60,creator_trx_id就是45,是事务A自己。

    这个时候事务A第一次查询这行数据,会走一个判断,就是判断一下当前这行数据的txr_id是否小于 ReadView中的min_trx_id,此时发现txr_id=32,是小于ReadView里的min_trx_id就是45的,说明你事 务开启之前,修改这行数据的事务早就提交了,所以此时可以查到这行数据, 接着事务B开始动手了,他把这行数据的值修改为了值B,然后这行数据的txr_id设置为自己的id,也就 是59,同时roll_pointer指向了修改之前生成的一个undo log,接着这个事务B就提交了 , 这个时候事务A再次查询,此时查询的时候,会发现一个问题,那就是此时数据行里的txr_id=59,那么 这个txr_id是大于ReadView里的min_txr_id(45),同时小于ReadView里的max_trx_id(60)的,说明 更新这条数据的事务,很可能就跟自己差不多同时开启的,于是会看一下这个txr_id=59,是否在 ReadView的m_ids列表里? 果然,在ReadView的m_ids列表里,有45和59两个事务id,直接证实了,这个修改数据的事务是跟自 己同一时段并发执行然后提交的,所以对这行数据是不能查询的!

    那么既然这行数据不能查询,那查什么呢? 简单,顺着这条数据的roll_pointer顺着undo log日志链条往下找,就会找到最近的一条undo log, trx_id是32,此时发现trx_id=32,是小于ReadView里的min_trx_id(45)的,说明这个undo log版本 必然是在事务A开启之前就执行且提交的。 好了,那么就查询最近的那个undo log里的值好了,这就是undo log多版本链条的作用,他可以保存一 个快照链条,让你可以读到之前的快照值, 看到这里,大家有没有觉得很奇妙?多个事务并发执行的时候,事务B更新的值,通过这套 ReadView+undo log日志链条的机制,就可以保证事务A不会读到并发执行的事务B更新的值,只会读 到之前最早的值。 接着假设事务A自己更新了这行数据的值,改成值A,trx_id修改为45,同时保存之前事务B修改的值的 快照, 此时事务A来查询这条数据的值,会发现这个trx_id=45,居然跟自己的ReadView里的 creator_trx_id(45)是一样的,说明什么? 说明这行数据就是自己修改的啊!自己修改的值当然是可以看到的了! 接着在事务A执行的过程中,突然开启了一个事务C,这个事务的id是78,然后他更新了那行数据的值为 值C,还提交了, 这个时候事务A再去查询,会发现当前数据的trx_id=78,大于了自己的ReadView中的 max_trx_id(60),此时说明什么? 说明是这个事务A开启之后,然后有一个事务更新了数据,自己当然是不能看到的了! 此时就会顺着undo log多版本链条往下找,自然先找到值A自己之前修改的过的那个版本,因为那个 trx_id=45跟自己的ReadView里的creator_trx_id是一样的,所以此时直接读取自己之前修改的那个版 本

    通过undo log多版本链条,加上你开启事务时候生产的一个ReadView,然后再有一个查询的时候,根 据ReadView进行判断的机制,你就知道你应该读取哪个版本的数据。 而且他可以保证你只能读到你事务开启前,别的提交事务更新的值,还有就是你自己事务更新的值。假 如说是你事务开启之前,就有别的事务正在运行,然后你事务开启之后 ,别的事务更新了值,你是绝对 读不到的!或者是你事务开启之后,比你晚开启的事务更新了值,你也是读不到的!

3、 Read Committed隔离级别是如何基于ReadView机制实现的?

这个RC隔离级别,实际上意思就是说你事务运行期间,只要别的事务修改数据还提交了,你就是可以读 到人家修改的数据的,所以是会发生不可重复读的问题,包括幻读的问题,都会有的。

那么所谓的ReadView机制,之前我们讲过,他是基于undo log版本链条实现的一套读视图机制,他意 思就是说你事务生成一个ReadView,然后呢,如果是你事务自己更新的数据,自己是可以读到的,或 者是在你生成ReadView之前提交的事务修改的值,也是可以读取到的。

但是如果是你生成ReadView的时候,就已经活跃的事务,在你生成ReadView之后修改了数据,接着提 交了,此时你是读不到的,或者是你生成ReadView以后再开启的事务修改了数据,还提交了,此时也 是读不到的。

其实这里的一个非常核心的要点在于,当你一个事务设置他处于RC隔离级别的时候,他是每次发起查 询,都重新生成一个ReadView!

首先假设我们的数据库里有一行数据,是事务id=50的一个事务之前就插入进去的,然后现在呢,活跃 着两个事务,一个是事务A(id=60),一个是事务B(id=70), 现在的情况就是,事务B发起了一次update操作,更新了这条数据,把这条数据的值修改为了值B,所 以此时数据的trx_id会变为事务B的id=70,同时会生成一条undo log,由roll_pointer来指向, 这个时候,事务A要发起一次查询操作,此时他一发起查询操作,就会生成一个ReadView,此时 ReadView里的min_trx_id=60,max_trx_id=71,creator_trx_id=60, 这个时候事务A发起查询,发现当前这条数据的trx_id是70。也就是说,属于ReadView的事务id范围之 间,说明是他生成ReadView之前就有这个活跃的事务,是这个事务修改了这条数据的值,但是此时这 个事务B还没提交,所以ReadView的m_ids活跃事务列表里,是有[60, 70]两个id的,所以此时根据 ReadView的机制,此时事务A是无法查到事务B修改的值B的。 接着就顺着undo log版本链条往下查找,就会找到一个原始值,发现他的trx_id是50,小于当前 ReadView里的min_trx_id,说明是他生成ReadView之前,就有一个事务插入了这个值并且早就提交 了,因此可以查到这个原始值, 接着,咱们假设事务B此时就提交了,好了,那么提交了就说明事务B不会活跃于数据库里了,是不是? 可以的,大家一定记住,事务B现在提交了。那么按照RC隔离级别的定义,事务B此时一旦提交了,说 明事务A下次再查询,就可以读到事务B修改过的值了,因为事务B提交了。

那么到底怎么让事务A能够独到提交的事务B修改过的值呢?

很简单,就是让事务A下次发起查询,再次生成一个ReadView。此时再次生成ReadView,数据库内活 跃的事务只有事务A了,因此min_trx_id是60,mac_trx_id是71,但是m_ids这个活跃事务列表里,只 会有一个60了,事务B的id=70不会出现在m_ids活跃事务列表里了,

此时事务A再次基于这个ReadView去查询,会发现这条数据的trx_id=70,虽然在ReadView的 min_trx_id和max_trx_id范围之间,但是此时并不在m_ids列表内,说明事务B在生成本次ReadView之 前就已经提交了。 那么既然在生成本次ReadView之前,事务B就已经提交了,就说明这次你查询就可以查到事务B修改过 的这个值了,此时事务A就会查到值B, 到此为止,RC隔离级别如何实现的,大家应该就理解了,他的关键点在于每次查询都生成新的 ReadView,那么如果在你这次查询之前,有事务修改了数据还提交了,你这次查询生成的ReadView 里,那个m_ids列表当然不包含这个已经提交的事务了,既然不包含已经提交的事务了,那么当然可以 读到人家修改过的值了。

这就是基于ReadView实现RC隔离级别的原理,希望大家好好仔细去体会,实际上,基于undo log多版 本链条以及ReadView机制实现的多事务并发执行的RC隔离级别、RR隔离级别,就是数据库的MVCC多 版本并发控制机制。

4、 MySQL最牛的RR隔离级别,是如何基于ReadView机制实现的?

今天来接着给大家讲解,MySQL中最牛的RR隔离级别,是如何同时避免不可重复读问题和幻读问题 的。

其实大家现在应该都知道,在MySQL中让多个事务并发运行的时候能够互相隔离,避免同时读写一条数 据的时候有影响,是依托undo log版本链条和ReadView机制来实现的。

上次我们都讲过了,基于ReadView机制可以实现RC隔离级别,即你每次查询的时候都生成一个 ReadView,这样的话,只要在你这次查询之前有别的事务提交了,那么别的事务更新的数据,你是可 以看到的。

那么如果是RR级别呢?RR级别下,你这个事务读一条数据,无论读多少次,都是一个值,别的事务修 改数据之后哪怕提交了,你也是看不到人家修改的值的,这就避免了不可重复读的问题。 同时如果别的事务插入了一些新的数据,你也是读不到的,这样你就可以避免幻读的问题。 那么到底是如何实现的呢?我们今天来看看。

首先我们还是假设有一条数据是事务id=5的一个事务插入的,同时此时有事务A和事务B同时在运行,事 务A的id是60,事务B的id是70, 这个时候,事务A发起了一个查询,他就是第一次查询就会生成一个ReadView,此时ReadView里的 creator_trx_id是60,min_trx_id是60,max_trx_id是71,m_ids是[60, 70], 这个时候事务A基于这个ReadView去查这条数据,会发现这条数据的trx_id为50,是小于ReadView里 的min_trx_id的,说明他发起查询之前,早就有事务插入这条数据还提交了,所以此时可以查到这条原 始值的, 接着就是事务B此时更新了这条数据的值为值B,此时会修改trx_id为70,同时生成一个undo log,而且 关键是事务B此时他还提交了,也就是说此时事务B已经结束了,

这个时候大家思考一个问题,ReadView中的m_ids此时还会是60和70吗? 那必然是的,因为ReadView一旦生成了就不会改变了,这个时候虽然事务B已经结束了,但是事务A的 ReadView里,还是会有60和70两个事务id。 他的意思其实就是,在你事务A开启查询的时候,事务B当时是在运行的,就是这个意思。 那么好,接着此时事务A去查询这条数据的值,他会惊讶的发现此时数据的trx_id是70了,70一方面是 在ReadView的min_trx_id和max_trx_id的范围区间的,同时还在m_ids列表中

这说明什么? 说明起码是事务A开启查询的时候,id为70的这个事务B还是在运行的,然后由这个事务B更新了这条数 据,所以此时事务A是不能查询到事务B更新的这个值的,因此这个时候继续顺着指针往历史版本链条上 去找 接着事务A顺着指针找到下面一条数据,trx_id为50,是小于ReadView的min_trx_id的,说明在他开启 查询之前,就已经提交了这个事务了,所以事务A是可以查询到这个值的,此时事务A查到的是原始值,

大家看到这里有什么感想?是不是感觉到这一下子就避免了不可重复读的问题? 你事务A多次读同一个数据,每次读到的都是一样的值,除非是他自己修改了值,否则读到的一直会一 样的值。

不管别的事务如何修改数据,事务A的ReadView始终是不变的,他基于这个ReadView始终看到的值是 一样的! 接着我们来看看幻读的问题他是如何解决的。假设现在事务A先用select * from x where id>10来查 询,此时可能查到的就是一条数据,而且读到的是这条数据的原始值的那个版本,至于原因,上面都解 释过了

现在有一个事务C插入了一条数据,然后提交了, 接着,此时事务A再次查询,此时会发现符合条件的有2条数据,一条是原始值那个数据,一条是事务C 插入的那条数据,但是事务C插入的那条数据的trx_id是80,这个80是大于自己的ReadView的 max_trx_id的,说明是自己发起查询之后,这个事务才启动的,所以此时这条数据是不能查询的。 因此事务A本次查询,还是只能查到原始值一条数据, 所以大家可以看到,在这里,事务A根本不会发生幻读,他根据条件范围查询的时候,每次读到的数据 都是一样的,不会读到人家插入进去的数据,这都是依托ReadView机制实现的!

5、 梳理一下数据库的多事务并发运行的隔离机制

今天给大家简单梳理一下MySQL中的多事务并发运行的隔离原理,其实这套隔离原理,说白了就是 MVCC机制,也就是multi-version concurrent control,就是多版本并发控制机制,专门控制多个事务 并发运行的时候,互相之间会如何影响。

首先我们先要明白,多个事务并发运行的时候,同时读写一个数据,可能会出现脏写、脏读、不可重复 读、幻读几个问题

所谓的脏写,就是两个事务都更新一个数据,结果有一个人回滚了把另外一个人更新的数据也回滚没 了。 脏读,就是一个事务读到了另外一个事务没提交的时候修改的数据,结果另外一个事务回滚了,下次读 就读不到了。 不可重复读,就是多次读一条数据,别的事务老是修改数据值还提交了,多次读到的值不同。 幻读,就是范围查询,每次查到的数据不同,有时候别的事务插入了新的值,就会读到更多的数据。

针对这些问题,所以才有RU、RC、RR和串行四个隔离级别 RU隔离级别,就是可以读到人家没提交的事务修改的数据,只能避免脏写问题; RC隔离级别,可以读到人家提交的事务修改过的数据,可以避免脏写和脏读问题。 RR是不会读到别的已经提交事务修改的数据,可以避免脏读、脏写和不可重复读的问题; 串行是让事务都串行执行,可以避免所有问题。

然后MySQL实现MVCC机制的时候,是基于undo log多版本链条+ReadView机制来做的,默认的RR隔 离级别,就是基于这套机制来实现的,依托这套机制实现了RR级别,除了避免脏写、脏读、不可重复 读,还能避免幻读问题。因此一般来说我们都用默认的RR隔离级别就好了

6、 多个事务更新同一行数据时,是如何加锁避免脏写的?

简单来说,脏读、不可重复读、幻读,都是别人在更新数据的时候,你怎么读的问题,读的不对,那就 有问题,读的方法对了,那就不存在一系列问题了。

而你要解决一系列的数问题,其实就是依靠之前我们给大家讲的那套,基于undo log版本链条以及 ReadView实现的mvcc机制。

现在开始我们接下来要用一系列的篇幅来研究另外一个问题了,那就是当有多个事务同时并发更新一行 数据的时候,不就是会有脏写的问题吗?

我们之前讲过,脏写是绝对不允许的,那么这个脏写是靠什么防止的呢?

说白了,就是靠锁机制,依靠锁机制让多个事务更新一行数据的时候串行化,避免同时更新一行数据, 今天我们就先对数据库的锁机制做一个初步的入门讲解。 在MySQL里,假设有一行数据摆在那儿不动,此时有一个事务来了要更新这行数据,这个时候他会先琢 磨一下,看看这行数据此时有没有人加锁? 一看没人加锁,太好了,说明他是第一个人,捷足先登了。 此时这个事务就会创建一个锁,里面包含了自己的trx_id和等待状态,然后把锁跟这行数据关联在一 起。 同时大家应该还记得,更新一行数据必须把他所在的数据页从磁盘文件里读取到缓存页里来才能更新 的,所以说,此时这行数据和关联的锁数据结构,都是在内存里的,大家要明确这一点,

,因为事务A给那行数据加了锁,所以此时就可以说那行数据已经被加锁了 那么既然被加锁了,此时就不能再让别人访问了!如果有朋友对加锁的概念不了解,可能是对编程语言 不太了解,其实这个就跟Java里的加锁是一个概念。 现在呢,有另外一个事务B过来了,这个事务B就也想更新那行数据,此时就会检查一下,当前这行数据 有没有别人加锁 然而他一下子发现,真是糟糕啊,事务A这家伙太不地道了,居然抢先给这行数据加锁了,这怎么办 呢? 事务B这个时候一想,那行,我也加个锁,然后等着排队不就得了,这个时候事务B也会生成一个锁数据 结构,里面有他的trx_id,还有自己的等待状态,但是他因为是在排队等待,所以他的等待状态就是 true了,意思是我在等着呢, 接着事务A这个时候更新完了数据,就会把自己的锁给释放掉了。锁一旦释放了,他就会去找,此时还 有没有别人也对这行数据加锁了呢?他会发现事务B也加锁了 于是这个时候,就会把事务B的锁里的等待状态修改为false,然后唤醒事务B继续执行,此时事务B就获 取到锁了

上述就是MySQL中锁机制的一个最基本的原理,大家可以先好好理解一下,其实是跟Java里的锁机制, 思路是完全类似的,从这种简单的锁里可以引申出很多其他的概念,比如读写锁,共享锁,独占锁,公 平锁,非公平锁,等等。Java里的锁,也同样具备这些锁的概念。

7、 MySQL 锁机制再深入一步,共享锁和独占锁到底是什么?

今天我们来稍微深入的讲一下MySQL里的共享锁和独占锁这两个概念,上次我们都讲过了,其实多个事 务同时更新一行数据,此时都会加锁,然后都会排队等待,必须一个事务执行完毕了,提交了,释放了 锁,才能唤醒别的事务继续执行。 那么在这多个事务运行的时候,他们加的是什么锁呢? 其实是X锁,也就是Exclude独占锁,当有一个事务加了独占锁之后,此时其他事务再要更新这行数据, 都是要加独占锁的,但是只能生成独占锁在后面等待。 那么这个时候我想问大家一个问题,当有人在更新数据的时候,其他的事务可以读取这行数据吗?默认 情况下需要加锁吗? 答案是:不用 因为默认情况下,有人在更新数据的时候,然后你要去读取这行数据,直接默认就是开启mvcc机制 的。 也就是说,此时对一行数据的读和写两个操作默认是不会加锁互斥的,因为MySQL设计mvcc机制就是 为了解决这个问题,避免频繁加锁互斥。 此时你读取数据,完全可以根据你的ReadView,去在undo log版本链条里找一个你能读取的版本,完 全不用去顾虑别人在不在更新。 就算你真的等他更新完毕了还提交了,基于mvcc机制你也读不到他更新的值啊!因为ReadView机制是 不允许的,所以你默认情况下的读,完全不需要加锁,不需要去care其他事务的更新加锁问题,直接基 于mvcc机制读某个快照就可以了。 那么假设万一要是你在执行查询操作的时候,就是想要加锁呢? 那也是ok的,MySQL首先支持一种共享锁,就是S锁,这个共享锁的语法如下:select * from table lock in share mode,你在一个查询语句后面加上lock in share mode,意思就是查询的时候对一行数 据加共享锁。 如果此时有别的事务在更新这行数据,已经加了独占锁了,此时你的共享锁能加吗? 当然不行了,共享锁和独占锁是互斥的!此时你这个查询就只能等着了。 那么如果你先加了共享锁,然后别人来更新要加独占锁行吗?当然不行了,此时锁是互斥的,他只能等 待。 那么如果你在加共享锁的时候,别人也加共享锁呢?此时是可以的,你们俩都是可以加共享锁的,共享 锁和共享锁是不会互斥的。 所以这里可以先看出一个规律,就是更新数据的时候必然加独占锁,独占锁和独占锁是互斥的,此时别 人不能更新;但是此时你要查询,默认是不加锁的,走mvcc机制读快照版本,但是你查询是可以手动 加共享锁的,共享锁和独占锁是互斥的,但是共享锁和共享锁是不互斥的,如下规律。

image.png
不过说实话,一般开发业务系统的时候,其实你查询主动加共享锁,这种情况较为少见,数据库的行锁 是实用功能,但是一般不会在数据库层面做复杂的手动加锁操作,反而会用基于redis/zookeeper的分 布式锁来控制业务系统的锁逻辑。 另外就是,查询操作还能加互斥锁,他的方法是:select * from table for update。 这个意思就是,我查出来数据以后还要更新,此时我加独占锁了,其他闲杂人等,都不要更新这个数据 了。 一旦你查询的时候加了独占锁,此时在你事务提交之前,任何人都不能更新数据了,只能你在本事务里 更新数据,等你提交了,别人再更新数据。 这一讲内容,就是给大家讲了默认情况下更新数据的独占锁,默认情况下查询数据的mvcc机制读快 照,然后通过查询加共享锁和独占锁的方式,共享锁和独占锁之间的互斥规则,大家都理解了就好。

1、在数据库里,哪些操作会导致在表级别加锁呢?

之前我们已经给大家讲解了数据库里的行锁的概念,其实还是比较简单,容易理解的,因为在讲解锁这 个概念之前,对于多事务并发以及隔离,我们已经深入讲解过了,所以大家应该很容易在脑子里有一个 多事务并发执行的概念。 在多个事务并发更新数据的时候,都是要在行级别加独占锁的,这就是行锁,独占锁都是互斥的,所以 不可能发生脏写问题,一个事务提交了才会释放自己的独占锁,唤醒下一个事务执行。 如果你此时去读取别的事务在更新的数据,有两种可能: 第一种可能是基于mvcc机制进行事务隔离,读取快照版本,这是比较常见的; 第二种可能是查询的同时基于特殊语法去加独占锁或者共享锁。 如果你查询的时候加独占锁,那么跟其他更新数据的事务加的独占锁都是互斥的;如果你查询的时候加 共享锁,那么跟其他查询加的共享锁是不互斥的,但是跟其他事务更新数据就加的独占锁是互斥的,跟 其他查询加的独占锁也是互斥的。 当然一般我个人从多年研发经验而言,不是太建议在数据库粒度去通过行锁实现复杂的业务锁机制,而 更加建议通过redis、zookeeper来用分布式锁实现复杂业务下的锁机制,其实更为合适一些。 为什么呢?因为如果你把分布式系统里的复杂业务的一些锁机制依托数据库查询的时候,在SQL语句里 加共享锁或者独占锁,会导致这个加锁逻辑隐藏在SQL语句里,在你的Java业务系统层面其实是非常的 不好维护的,所以一般是不建议这么做的。 比较正常的情况而言,其实还是多个事务并发运行更新一条数据,默认加独占锁互斥,同时其他事务读 取基于mvcc机制进行快照版本读,实现事务隔离。 今天我们要给大家在讲完行锁之后,继续讲一个新的概念,就是表级锁。 在数据库里,你不光可以通过查询中的特殊语法加行锁,比如lock in share mode、for update等等, 还可以通过一些方式在表级别去加锁。 有些人可能会以为当你执行增删改的时候默认加行锁,然后执行DDL语句的时候,比如alter table之类 的语句,会默认在表级别加表锁。这么说也不太正确,但是也有一定的道理,因为确实你执行DDL的时 候,会阻塞所有增删改操作;执行增删改的时候,会阻塞DDL操作。 但这是通过MySQL通用的元数据锁实现的,也就是Metadata Locks,但这还不是表锁的概念。因为表 锁其实是InnoDB存储引擎的概念,InnoDB存储引擎提供了自己的表级锁,跟这里DDL语句用的元数据 锁还不是一个概念。 只不过DDL语句和增删改操作,确实是互斥的,大家要知道这一点。

2、 表锁和行锁互相之间的关系以及互斥规则是什么呢?

今天我们接着讲,MySQL里是如何加表锁的。这个MySQL的表锁,其实是极为鸡肋的一个东西,几乎 一般很少会用到,表锁分为两种,一种就是表锁,一种是表级的意向锁,我们分别来看看。 首先说表锁,这个表锁,可以用如下语法来加: LOCK TABLES xxx READ:这是加表级共享锁 LOCK TABLES xxx WRITE:这是加表级独占锁 其实一般来讲,几乎没人会用这两个语法去加表锁,这不是纯属没事儿找事儿么,所以才说表锁特别的 鸡肋。 还有就是有另外两个情况会加表级锁。如果有事务在表里执行增删改操作,那在行级会加独占锁,此时 其实同时会在表级加一个意向独占锁;如果有事务在表里执行查询操作,那么会在表级加一个意向共享 锁。 其实平时我们操作数据库,比较常见的两种表锁,反而是更新和查询操作加的意向独占锁和意向共享 锁,但是这个意向独占锁和意向共享锁,大家暂时可以当他是透明的就可以了,因为两种意向锁根本不 会互斥。 为啥呢?因为假设有一个事务要在表里更新id=10的一行数据,在表上加了一个意向独占锁,此时另外 一个事务要在表里更新id=20的一行数据,也会在表上加一个意向独占锁,你觉得这两把锁应该互斥 吗? 明显是不应该互斥的啊,因为他们俩更新的都是表里不同的数据,你让他们俩在表上加的意向独占锁互 斥干什么呢?所以意向锁之间是根本不会互斥的。 同理,假设一个事务要更新表里的数据,在表级加了一个意向独占锁,另外一个事务要在表里读取数 据,在表级加了一个意向共享锁,此时你觉得表级的意向独占锁和意向共享锁应该互斥吗? 当然不应该了!一个人要更新数据,一个人要读取数据,俩人在表上加的意向锁,凭什么要互斥?没天 理啊! 锁类型 独占锁 意向独占锁 共享锁 意向共享锁 独占锁 互斥 互斥 互斥 互斥 意向独占锁 互斥 不互斥 互斥 不互斥 共享锁 互斥 互斥 不互斥 不互斥 意向共享锁 互斥 不互斥 不互斥 不互斥 所以说,大家有没有发现一点,这个所谓的表级的意向独占锁和意向共享锁,似乎是跟脱了裤子放屁一 样,多此一举? 但是我们接下来就要给大家讲讲,手动加表级共享锁和独占锁,以及更新和查询的时候自动在表级加的 意向共享锁和意向独占锁,他们之间反而是有一定的互斥关系,关系如下表所示。
image.png
大家看看上面表格,仔细看一下,上面说的是在表上面手动加的独占锁和共享锁,以及更新数据和查询 数据默认自动加的意向独占锁和意向共享锁,他们互相之间的互斥关系,大家一看就明白了。 其实更新数据自动加的表级意向独占锁,会跟你用 LOCK TABLES xxx WRITE 手动加的表级独占锁是互 斥的,所以说,假设你手动加了表级独占锁,此时任何人都不能执行更新操作了! 或者你用LOCK TABLES xxx WRITE手动加了表级共享锁,此时任何人也不能执行更新操作了,因为更新 就要加意向独占锁,此时是跟你手动加的表级共享锁,是互斥的! 具体其他实例就不举了,大家看看上面的表格就知道了,你如果手动加了表级的共享锁或者独占锁,此 时是会阻塞掉其他事务的一些正常的读写操作的,因为跟他们自动加的意向锁都是互斥的。 但是说实话,这一讲也就是给你讲明白这个表级锁如何加的,如何互斥的,其实一般来说,根本就不会 手动加表级锁,所以一般来说读写操作自动加的表级意向锁,互相之间绝对不会互斥。 一般来讲,都是对同一行数据的更新操作加的行级独占锁是互斥,跟读操作都是不互斥的,读操作默认 都是走mvcc机制读快照版本的!