如有侵权请联系本人删除

首先了解acid与mysql

MySQL事务之ACID实现原理(全方位解读)

mysql 可重复执行建表 mysql可重复读的原理_mysql


二者很相似,不可重复读指的是对同一条记录(可以理解为对同一行)前后两次的读取结果是不一样的。

幻读指的是一个事务读取两次,得到的记录条数不一致。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读可能发生在update操作中,而幻读发生在insert,delete操作中。

mysql 可重复执行建表 mysql可重复读的原理_mysql_02

mysql解决不可重复读的方法:

mysql中,默认的事务隔离级别是可重复读(repeatable-read),为了解决不可重复读,innodb采用了mvcc(多版本并发控制)来解决这一问题。(mvcc顺便也解决了一部分幻读:快照读)

mvcc是利用在每条数据后面加了隐藏的两列(创建版本号和删除版本号),每个事务在开始的时候都会有一个递增的版本号

rr rc中的s,x锁
读已提交(Read Committed):解决了脏读问题。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。这可以通过“瞬间共享读锁”和“排他写锁”实现, 即事物需要对某些数据进行修改必须对这些数据加 X 锁,读数据时需要加上 S 锁,当数据读取完成后立刻释放 S 锁,不用等到事物结束。

可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。Mysql默认使用该隔离级别。这可以通过“共享读锁”和“排他写锁”实现,即事物需要对某些数据进行修改必须对这些数据加 X 锁,读数据时需要加上 S 锁,当数据读取完成并不立刻释放 S 锁,而是等到事物结束后再释放。

两种锁的sql语句(当前读) MySQL 共享锁 (lock in share mode),排他锁 (for update)

mysql 可重复执行建表 mysql可重复读的原理_mysql 可重复执行建表_03

其实在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决时不彻底的。例子:

mysql 可重复执行建表 mysql可重复读的原理_数据库_04


mysql 可重复执行建表 mysql可重复读的原理_数据_05


mysql 可重复执行建表 mysql可重复读的原理_mysql_06


mysql 可重复执行建表 mysql可重复读的原理_mysql 可重复执行建表_07

图源:https://www.php.cn/mysql-tutorials-460111.html

在MySQL中,通过多版本并发控制(MVCC)去避免幻读的问题,但是只是在select的时候可以避免幻读,update之后再select还是可能会出现幻读现象。
Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了 next-key 锁,就是记录锁和间隙锁的组合。
普通的查询是快照读,是不会看到别的事务插入的数据的。

可重复读隔离级是由
MVCC(多版本并发控制)实现的,实现的方式是启动事务后,在执行第一个查询语句后,会创建一个视图,然后后续的查询语句都用这个视图,「快照读」读的就是这个视图的数据,视图你可以理解为版本数据,这样就使得每次查询的数据都是一样的。

MySQL
里除了普通查询是快照度,其他都是当前读,比如update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。

这很好理解,假设你要 update 一个记录,另一个事务已经 delete 这条记录并且 提交事务了,这样不是会产生冲突吗,所以
update 的时候肯定要知道最新的数据。

另外,select … for update 这种查询语句是当前读,每次执行的时候都是读取最新的数据。

因此,要讨论「可重复读」隔离级别的幻读现象,是要建立在「当前读」的情况下。
所以,Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了 next-key 锁,就是记录锁和间隙锁的组合。
记录锁,锁的是记录本身; 间隙锁,锁的就是两个值之间的空隙,以防止其他事务在这个空隙间插入新的数据,从而避免幻读现象。

需要注意的是,next-key lock 锁的是索引,而不是数据本身,所以如果 update 语句的 where 条件没有用到索引列,那么就会全表扫描,在一行行扫描的过程中,不仅给行加上了行锁,还给行两边的空隙也加上了间隙锁,相当于锁住整个表,然后直到事务结束才会释放锁。

所以在线上千万不要执行没有带索引的 update 语句,不然会造成业务停滞,然后被老板教育了一波