MySQL系列之隔离级别

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 MySQL系列之隔离级别,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

隔离级别是为事务服务的。

ACID

数据库事务的4个特性:

  • 原子性(Atomic):事务中的多个操作不可分割,要么都成功,要么都失败; All or Nothing
  • 一致性(Consistency):事务操作之后,数据库所处的状态和业务规则是一致的
  • 隔离性(Isolation):多个事务之间就像是串行执行一样,不相互影响
  • 持久性(Durability):事务提交后被持久化到永久存储

持久性

只能从事务本身的角度来保证结果的永久性,但若不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。因此持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availability)。对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合来完成。

隔离级别

查看db的隔离级别:

SELECT @@tx_isolation;
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;

通过执行SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别,新设置的隔离级别将在下一个事务开始时生效。

隔离级别有四种:

  • READ UNCOMMITTED:RU,读未提交,一个事务可以读取到其他事务未提交的数据,未提交的数据称为脏数据。此时存在幻读,不可重复读和脏读问题。数据库一般都不会用,而且任何操作都不会加锁。
  • READ COMMITTED:RC,读取已提交,一个事务只能读取到其他事务已经提交的数据;可能导致同一个事务中多次搜查结果不一样。可解决脏读问题,但依然存在幻读,不可重复读问题。在此级别中,数据的读取都是不加锁的,但数据的写入、修改和删除是需要加锁的。Oracle、PostgreSQL、SQL Server默认模式
  • REPEATABLE READ:RR,可重复读,一个事务中多次执行同一个select都能读取到相同的数据,存在幻读问题。是InnoDB默认隔离级别。
  • SERIALIZABLE:序列化,多个事务串行化一个个按照顺序执行,不存在并发情况,可以避免所有事务并发问题。SQLite默认级别

这四个级别逐步加重。到最后的Serializable,事务和事务之间彻底什么都看不到,而且每次事务执行还要把其他事务全部卡起,全部串行。

RR

面试题:大多数数据库的默认隔离级别是RC?MySQL InnoDB的默认隔离级别是什么?或为什么是RR?而不是RC?RR和RC区别是什么?

在MySQL5.0之前,binlog只支持statement。执行命令show variables like 'binlog_format';查看当前DB的binlog格式。

statement会导致主备不一致?
statement只是记录SQL,不能保证保证主从复制不出问题。statement记录的是SQL语句原文,而row格式(MySQL 5.1才引入)记录的是执行的逻辑过程。statement占用空间少,但是会出现主备不一致;row占用空间多,不会出现主备不一致问题。

MySQL 5.5.5版本后默认的隔离级别为RR?
所以,MySQL默认RR级别是历史原因。现在有row类型的binlog,当然在不严格要求不准出现不可重复读的情况下,选用RC+row!

RC & RR

InnoDB默认隔离级别为RR,但实际情况是使用RC 和 RR隔离级别的都不少。


  1. 显然 RR 支持 gap lock(next-key lock),而RC则没有gap lock。因为MySQL的RR需要gap lock来解决幻读问题。而RC隔离级别则是允许存在不可重复读和幻读的。所以RC的并发一般要好于RR;
  2. RC 隔离级别,通过 where 条件过滤之后,不符合条件的记录上的行锁,会释放掉(虽然这里破坏了“两阶段加锁原则”);但是RR隔离级别,即使不符合where条件的记录,也不会是否行锁和gap lock;所以从锁方面来看,RC的并发应该要好于RR;另外insert into t select ... from s where语句在s表上的锁也是不一样的;

SERIALIZABLE

一个事务在执行期间,彻底排他。比如a事务执行SELECT * FROM order,此时a事务会把order表的所有数据全部锁定,而不允许任何其他事务进行insert或update。这个级别强调的是对数据的范围(range)的排他锁定。只会锁定查询范围内的数据。如SELECT * FROM order WHERE status='CLOSED' ,只会锁定状态为CLOSED的数据。

在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为SERIALIZABLE。

3个问题

不同的隔离级别有不同的问题:

  • 脏读:a事务读取到b事务还没有commit的数据。RC可解决此问题
  • 不可重复读:a事务在整个事务内多次执行某个查询获取(某一条)数据,会出现查询结果不一致性的情况。可重复读面向的具体的某一条数据的前后一致性。RR隔离级别可解决此问题
  • 幻读:在一个事务内,两次读取到不一样的数据集。第一次读取到3条数据,第二次却读到4条数据。幻读面向的是数据集,即range。SERIALIZABLE可解决此问题,但一般不会使用SERIALIZABLE隔离级别

解决幻读

RR隔离级别配合GAP间隙锁已经避免幻读

不可重复读 & 幻读

不可重复读的重点是修改,同一个select,两次查询得到的某条数据不一致。
幻读的重点在于新增或删除,同一个select,两次查询得到的记录数不一样。

从结果上来看,两者都是为多次读取的结果不一致。但如果你从实现的角度来看, 它们的区别就比较大:
对于前者,在RC下只需要锁住满足条件的记录,就可以避免被其它事务修改,也就是select for update, select in share mode;RR隔离下使用MVCC实现可重复读;
对于后者,要锁住满足条件的记录及所有这些记录之间的gap,也就是需要gap lock。

第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。另,一个事务先后读取一个范围的记录,但两次读取的纪录数不同,称之为幻读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)

幻读和不可重复读很像,但幻读侧重点在于新增和删除,而不可重复读侧重点在于更改,共同之处都是一个事务中两次查询得到的数据结果不一致。

总结:

事务隔离级别 脏读Dirty Read 不可重复读NonRepeatable read 幻读Phantom
RU Y Y Y
RC Y Y
RR Y
SERIALIZABLE

Y表示次隔离级别存在对应的问题。

隔离级别与锁

在RU,RC,RR三种事务隔离级别下,普通select语句使用快照读(snpashot read),不加锁,并发非常高;
在Serializable这种事务隔离级别下,普通select会升级为select ... in share mode;

MVCC

非锁定读,Multi-Version Concurrency Control,多版本并发控制。基于对并发性能的考虑,MySQL的大多数事务型存储引擎都实现多版本并发控制,可简单认为MVCC是行级锁的一个变种,但是它在很多情况下避免加锁操作,降低开销。

InnoDB的MVCC是通过在每行记录后添加两个隐藏列来实现的,创建时间、过期时间(或删除时间),这两个时间在实际存储时,存储的是系统版本号。每开始一个新事务,系统版本号都将递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。MVCC只能在Read Committed和Repeatable Read隔离级别下正常工作。

MVCC能够实现读写不阻塞!

两种实现方式
纵览各种数据库的MVCC实现,主要有两种实现方式。

  1. 通过保存多个版本的数据,然后通过gc的方式清理那些不再被使用的数据,如PostgreSQL、Firebird、Interbase。SQL Server也采用类似的方式,略微不同的是,SQL Server把老版本的数据保存在tempdb数据库中(一个有别于主数据库的数据库)中。
  2. 通过数据结合undo log的方式。这种方式只会保存最新版本的那一份数据,然后通过undo log来进行重新构造需要的老版本数据。采用这种方式的数据库有Oracle 、MySQL(Innodb)。

一次封锁or两段锁?
因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。

数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
解锁阶段:当事务释放一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。

这种方式虽然无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。

为何有些公司会把RR更改为RC??

可重复读和幻读的区别?
可序列化,如何实现的?

MVCC与幻读

MVCC能否解决幻读

参考

MySQL-RR隔离与RC隔离
Innodb中的事务隔离级别和锁的关系
MySQL为什么默认隔离级别为可重复读?
MySQL中隔离级别RC与RR区别

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/142104.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!