我们平时在开发中是离不开MySQL的事务的,那什么是事务?事务就是逻辑上的一组操作,要么一荣俱荣,要么一损俱损,谁都别活。事务的目的就是一个,保证数据的一致性。
事务我们常说有四个特性,ACID,那这些的含义是什么?
A(Atomicity)原子性:事务是最小的执行单位,不允许分割,要么全部完成,要么完全失去作用。
C(Consistency)一致性:执行事务前后,数据保持一致,我给你,那么我就减少,你增加,咱俩的钱的总量是不变的。
I(Isolation)隔离性:每个事务之间的隔离的,互不影响。
D(Durability)持久性:一个事务提交之后,对数据库的改变是持久的。
而原子性,隔离性,持久性都是手段,究极目的都是为了一致性。
因为存在并发的事务,提升了性能,也会出现一些问题。而这些问题也是通过很多手段进行解决,我们细细道来~
脏度一个事务读取到了另一个事务还没有提交的数据,这就是脏读。举个栗子,比如,A事务和B事务同时进行,A事务将数据a从10改成了20,B事务读取了a的值是20,当A回滚了的话,B事务还是用20进行的后续处理,这就是脏读,也是比较严重的问题。
丢失修改还是举个栗子,AB两个事务同时进行,A事务读取a的值为20,然后进行-1得到19,B事务读取a的值也是20然后进行-1也是19,两个存储库之后发现结果为19,丢失了率先提交的事务的数据。
不可重复读当一个事务对一个数据进行多次查询的时候,因为别的事务进行了修改并提交,那么可能会导致,这个事务中多次读取到的数据是不一致的,这就叫不可重复读。
幻行读和不可重复读的场景类似,区别就是,读取几行数据的时候,比如范围查询,age50,读到了5行数据,然后其他事务也插入了age小于50的数据,那么这个事务再次执行这个查询的时候会发现数据多了,这就是幻行读。
为了解决这些问题,MySQL也弄出了很多的方案,首当其冲的就是隔离级别
SQL的隔离级别读未提交:最低的隔离级别,可以读取未提交的数据,脏读,不可重复读,幻行读都可能会发生。
读已提交:允许读取已经提交的数据,避免了脏度的出现,但是不可重复读和幻行读仍然可能出现。
可重复度:对同一个字段的多次的读取结果是一致的,除非自己修改的,避免了脏读和不可重复读,但是仍然存在幻行读的情况。
可串行化:最高的隔离级别,都特么别玩了,事务给我一个一个走,三种问题都避免了,效率直接拉低到谷底。
InnoDB存储引擎默认的隔离级别是可重复度,也就是RR,为什么呢?这就和MySQL的机制有关系了,MySQL我们很常见的特性就是集群部署,读写分离,主从复制,而主从复制是依赖于binlog的,我在之前的文章讲解binlog的时候说过,binlog记录数据是是以逻辑SQL的形式的,有三种模式,分别是statement,row,和mixed,具体的大家翻我前面的文章,statement记录的是SQL的原文。好了举个栗子。在RC的隔离级别下
这样的执行过程之后因为事务****是先提交的,那么binlog里面就先记录了insert,然后再记录delete,那么同步从库的时候就全给删了,这属于幻行读的一部分,所以MySQL默认隔离级别是RR,那么总不能允许幻行读的存在吧,所以就依赖两种机制来避免,就是锁+MVCC机制。我们先说锁
锁MySQL同时支持表级锁和行级锁,表级锁就是锁整张表,加锁快,不会出现死锁,但是并发的场景下效率是非常低的。
共享锁和排他锁落实到数据库层面都是加的这两种锁,其实也就是读锁和写锁。而且都是在SQL上有所体现的。
共享锁可以通过select。。。。lockinshamode(Mysql5.7或8)/select。。。forsha来显示指定。
排他锁可以通过select。。。。forupdate来显示指定。
意向锁意向锁其实就是一种标志,我们上面说的锁都是对于表内数据的锁,而数据是有很多行了,如何判定表中的数据有没有锁呢?我们总不能遍历所有的数据然后找吧,所以就增加了意向锁,分为意向共享锁(IS),和意向独占锁(IX),也就是当我们想对数据增加锁的时候都会对表增加意向意向锁。
行级锁行级锁不是单纯的锁一行,而是一个概念,是以行为单位的锁,在MySQL中,数据是以索引树的形式组织的,锁的相关都和索引相关,关于索引的问题,我会单独抽出一篇文章进行讲解。当delete或update的时候的whe条件没有命中索引或者索引不生效的情况下,会加表锁。
行级锁主要就是三种,记录锁(cordlock),间隙锁(gaplock),和临键锁(next-keylock),临键锁就是记录锁和间隙锁的组合,在RR的隔离级别下,默认的行级锁都是临键锁,但是啊,如果你操作的索引是唯一索引或者是主键,那么加的就是记录锁了。所以关键点还是锁一下这个临键锁,我会非常详细的推演整个锁的过程,看看是怎么敲定锁的范围的。
MySQL版本,8.0.34,表数据示例
1、主键等值查询,并且数据存在的情况下select*fromtestwheaid=5forupdate;select*fromperformance_schema.data_locks
此时第一行加的是表锁,加的一个IX,就是意向独占锁,第二行是加的行级锁,类型是独占锁,锁的数据是主键为5的数据。
同理使用forsha就是加的IS和S。X,REC_NOT_GAP:行独占锁,锁定1行
所以当查询使用主键,并且数据存在的情况下,使用的是记录锁。
2、主键等值查询,但是数据不存在的情况select*fromtestwheaid=7forupdate;select*fromperformance_schema.data_locks
此时第一行加的是表锁,加的一个IX,就是意向独占锁,第二行是加的行级锁,类型是独占锁+间隙锁,数据是10,因为10是不等于查询的7的
所以是开区间,然后得到的区间就是(5,10),5没有显示是因为5是第一条数据了,其实也就是起点;
开启另一个事务执行:
insertintotestvalues(4,4,4,4);成功!
insertintotestvalues(8,8,8,8);失败
X,GAP:独占锁,开区间
所以当查询使用主键,但是数据不存在的情况下,使用间隙锁,锁定范围就是小于查询数据最近和大于查询数据最近的范围,左开右开区间。
3、使用主键范围查询两个值都能查到的情况select*fromtestwheaid=5andaid=10forupdate;