一文搞懂MySQL事务隔离级别

853 阅读8分钟

目录

前言

这个是面试必问了吧....虽然目前在实际工作种我基本上还没有过实际的应用,但是在学习MySQL的时候还是专门进行一些学习,这里做一点记录.

并发与锁

在学习事务隔离级别级别之前,我们首先需要知道为什么要有事务隔离级别.隔离级别是为了做好并发控制.

当多个查询同时发生的时候,就产生了并发问题,对于大多数涉及到数据的应用程序来说,操作主要有两种,一种是"读取",一种是"写入"(会对数据产生修改).那么当一个读取操作和一个写入操作同时发生,会出现什么结果呢?结果不确定,有可能会产生异常导致某个操作的退出,也有可能操作都完成了但是结果是错误的,比如读取到了修改后的数据等异常.

解决并发问题一般要进行并发控制,常用的是建立锁,当一个操作对数据进行操作时,对系统进行加锁操作,操作完成之后释放锁,这期间其他操作由于无法获取锁,因此无法执行成功.

锁类型:读写锁

如果对任何操作都将数据进行完全的锁死,那么系统的并发处理能力就会非常弱,因此MySQL的锁系统中有两种锁:读锁和写锁.

读锁: 读锁是一种共享锁,多个申请获取读锁的操作可以同时获得同时进行.

写锁: 写锁则是一种排它锁,即写锁会排斥其他的读锁和写锁.

为什么这样设计呢?想一下,当多个操作都是读取操作时,他们同时进行并不会造成数据的混乱或者操作的失败,因此可以同时进行.而多个写操作或者一个读取操作和一个写入操作同时发生,结果将不可控,因此写入操作的锁需要是排它锁.

锁的粒度

在设计了锁的类型之后,并发程度其实还是不高,每次进行一个操作都要都所有数据加锁吗?假如修改的数据和要读取的数据完全没有关系,也不会造成冲突,也需要等待写入操作完成吗?这时候就划分了锁的粒度.

表锁: 表锁时一种开销较小的锁操作,他将操作设计到的数据表进行加锁.

行级锁: 对操作设计到的所有数据行进行加锁,开销较大.

死锁

加锁就会有死锁问题,当两个操作各自占有自己的资源切不释放,然后不断请求对方持有的资源,就会产生死锁.死锁一旦产生,没有外力的介入是无法打破的,就像是囚徒困境一样.

而死锁又是很难完全避免的,所以MySQL提供了比较充足的死锁检测策略,当检测到死锁后,Innodb会将持有最少行级排它锁的事务进行回滚,以此来打破死锁.这是一种简单的策略,还有其他更加复杂的解决死锁的算法,这里不做研究了.

小结

可以看到,排它锁对并发性能的提升更有作用,但是他不能解决写入的问题,表级锁开销较小但是并发性能不够好,锁住了更多的数据,其中可能包括很多无用的数据,因此在实际的应用过程中,需要按需取用.

事务

理解什么是事务最经典的就是转账的栗子,相信大家也都了解,这里就不再说一边了.

事务是一系列的操作,他们要符合ACID特性.最常见的理解就是:事务中的操作要么全部成功,要么全部失败.但是只是这样还不够的.

A=Atomicity

原子性,就是上面说的,要么全部成功,要么全部失败.不可能只执行一部分操作.

C=Consistency

系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态.

I=Isolation

隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的.注意前面的通常来说加了红色,意味着有例外情况.

D=Durability

持久性,一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果.

就想锁系统的升级会带来系统的额外开销一样,事务实现的完整度也是逐渐增减系统的消耗的,而MySQL给我们提供了以上四种特性的多种选项,可以让我们根据自己的实际情况,选取更加适合的策略,以此来提高性能,这就是MySQL提供的四种事务隔离级别.

事务隔离级别

MySQL的四种隔离级别如下:

未提交读(READ UNCOMMITTED)

这就是上面所说的例外情况了,这个隔离级别下,其他事务可以看到本事务没有提交的部分修改.因此会造成脏读的问题(读取到了其他事务未提交的部分,而之后该事务进行了回滚).

这个级别的性能没有足够大的优势,但是又有很多的问题,因此很少使用.

已提交读(READ COMMITTED)

其他事务只能读取到本事务已经提交的部分.这个隔离级别有 不可重复读的问题,在同一个事务内的两次读取,拿到的结果竟然不一样,因为另外一个事务对数据进行了修改.

REPEATABLE READ(可重复读)

可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),但是仍然有一个新问题,就是 幻读,当你读取id> 10的数据行时,对涉及到的所有行加上了读锁,此时例外一个事务新插入了一条id=11的数据,因为是新插入的,所以不会触发上面的锁的排斥,那么进行本事务进行下一次的查询时会发现有一条id=11的数据,而上次的查询操作并没有获取到,再进行插入就会有主键冲突的问题.

这个隔离级别也是Innodb存储引擎默认的隔离级别.

SERIALIZABLE(可串行化)

这是最高的隔离级别,可以解决上面提到的索引问题,因为他强制将所以的操作串行执行,这会导致并发性能极速下降,因此也不是很常用.

Mysql中实施的是自动提交,也就是说默认一个语句未一个事务,当然你可以通过设置AUTOCOMMIT变量来关闭自动提交,也可以通过begin来显式的开启一个事务.

MVCC

加锁是一种控制并发的方式,但是加锁毕竟是一个比较消耗资源的操作,因此MySQL也实现了MVCC(Multi-Version Concurrency Control ),核心思想是未每一条数据加上两个版本号,一个是当前的数据版本号,一个是该数据的删除版本号.通过版本的控制,在一定程度上尚避免加锁也可以实现并发控制.

在MySQL中,MVCC的大致工作原理如下:

select

查询语句指挥获取符合下面两个条件的数据:

  1. 数据的版本号小于等于当前事务的版本号,这样可以保证查到的数据要么是之前就存在的,要么是本事务操作的.
  2. 数据的删除版本号要么为空,要么大于事务当前的版本号.这样可以保证在此事务之前,该行数据没有被删除.

insert

插入数据的当前版本号等于当前事务的版本号.

delete

将删除行的删除版本号设置为当前事务的版本号.

update

对原数据进行删除操作,然后插入新数据,所以相当于上面两个操作的合集.

小结

虽然我真的还没有找到需要使用MySQL事务隔离级别相关知识的场景,可能因为MySQL做的太好了吧.但是我们还是应该了这一部分的知识,这样在MySQL因为并发出现问题问题的时候,我们不至于完全的束手无策.

参考文章

书籍<高性能MySQL>


完。



ChangeLog

2019-06-09 完成

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客------>呼延十