多个事务更新同时并发一行数据时,是如何加锁避免脏写的?- 脏写的问题

依靠锁机制让多个事务更新一行数据的时候串行化,避免同时更新一行数据

当有一个事务要更新这行数据,会先看看这行数据此时有没有人加锁?
没人加锁,说明是第一个人,此时这个事务会创建一个锁,包含自己的trx_id 和 等待状态然后把锁和这行数据关联在一起。

更新一行数据必须把他所在的数据页从磁盘文件里读取到缓存页里来才能更新 的,所以说,此时这行数据和关联的锁数据结构,都是在内存里的,大家要明确这一点image.png
注意看上面的那个图,因为事务A给那行数据加了锁,所以此时就可以说那行数据已经被加锁了 ,就不能再让别人访问了。

此时另外一个事务B过来了,这个事务B就也想更新那行数据,此时就会检查一下,当前这行数据 有没有别人加锁 ,然后发现A 在事务B前面对这行数据加锁了。

事务B这个时候一想,那行,我也加个锁,然后等着排队不就得了,这个时候事务B也会生成一个锁数据 结构,里面有他的trx_id,还有自己的等待状态,但是他因为是在排队等待,所以他的等待状态就是 true了,意思是我在等着呢
image.png
当事务A更新完数据,就把自己的锁释放掉。锁一旦释放了,MySQL( 吗?)就会找还有人加锁吗,发现了事务B也加锁了。
于是,事务B的锁的等待状态 置为 false,然后唤醒事务B继续执行,此时事务B就获取到锁了。 上面就是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
意思是:我查出数据后还要更新,此时我加了独占锁,其他人都不要更新这行数据。

一旦查询的时候加了独占锁,此时在事务提交前,任何人都不能再更新数据了,只能等你提交之后,别人再更新数据。

表级别加锁

在多个事务并发更新数据的时候,都是要在行级别加独占锁的,这就是行锁。
一般研发业务系统,不太建议在数据库粒度去通过行锁实现复杂的业务锁机制,而是通过redis、zookeeper 来用分布式锁 实现复杂业务下的锁机制。

因为如果你把分布式系统里的复杂业务的一些锁机制依托数据库查询的时候,在SQL语句里 加共享锁或者独占锁,会导致这个加锁逻辑隐藏在SQL语句里,在你的Java业务系统层面其实是非常的 不好维护的,所以一般是不建议这么做的。

比较正常的情况而言,其实还是多个事务并发运行更新一条数据,默认加独占锁互斥,同时其他事务读 取基于mvcc机制进行快照版本读,实现事务隔离。

在数据库里,可以通过查询中特殊的语法加行锁,比如lock in share mode、for update等

CRUD的时候默认加行锁,执行DDL语句如alter table之类的语句。
因为确实你执行DDL的时 候,会阻塞所有增删改操作;执行增删改的时候,会阻塞DDL操作。不过DDL语句 和 增删查改确实是互斥的(可以解释为什么大表加索引要凌晨吗)。这是通过元数据锁实现的,Metadata Locks。

表级锁 是InnoDB存储引擎的概念,InnoDB存储引擎提供了自己的表级锁,

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

表锁是很鸡肋的东西,一种是表锁,一种是表级意向锁。

LOCK TABLES xxx READ:这是加表级共享锁
LOCK TABLES xxx WRITE:这是加表级独占锁 几乎没人用

如果有事务在表里执行增删改操作,会在行级加独占锁,此时同时在表级加意向独占锁
有事务在表里查询操作,会在表级加意向共享锁

平时我们操作数据库,比较常见的两种表锁,反而是更新和查询操作加的意向独占锁和意向共享 锁,但是这个意向独占锁和意向共享锁,大家暂时可以当他是透明的就可以了,因为两种意向锁根本不 会互斥。

为啥呢?因为假设有一个事务要在表里更新id=10的一行数据,在表上加了一个意向独占锁,此时另外 一个事务要在表里更新id=20的一行数据,也会在表上加一个意向独占锁,你觉得这两把锁应该互斥 吗?
明显是不应该互斥的啊,因为他们俩更新的都是表里不同的数据,你让他们俩在表上加的意向独占锁互 斥干什么呢?所以意向锁之间是根本不会互斥的。

同理,假设一个事务要更新表里的数据,在表级加了一个意向独占锁,另外一个事务要在表里读取数 据,在表级加了一个意向共享锁,此时你觉得表级的意向独占锁和意向共享锁应该互斥吗?
当然不应该了!一个人要更新数据,一个人要读取数据,俩人在表上加的意向锁,凭什么要互斥。

大家有没有发现一点,这个所谓的表级的意向独占锁和意向共享锁,似乎是跟脱了裤子放屁一 样,多此一举?
image.png

如果手动加了表级的共享锁或者独占锁,此时会阻塞掉其他事务的一些正常的读写操作,因为跟其他事务自动加的意向共享锁 和 意向独占锁是互斥的。

但一般来说,根本不会手动加表级锁,所以读写操作自动加的表级意向锁之间是绝对不会互斥的。

一般来讲,都是对同一行数据的更新操作加的行级独占锁是互斥,跟读操作都是不互斥的,读操作默认 都是走mvcc机制读快照版本的!