MySQL学习系列之InnoDB下事务隔离机制

栏目: 数据库 · 发布时间: 6年前

内容简介:MySQL中存在四种隔离等级:这里思考一下,为啥阻止幻读的隔离级别比不可重复读的高,因为InnoDB是行级锁,不可重复读是针对于对于正在修改或删除的数据行加锁,但还是可以对表进行插入,所以可能出现幻读,要避免幻读就要把表的读写都变成表级锁,才能避免幻读,也因此变成了隔离等级为“可串行读”。PS:以上内容以

MySQL中存在四种隔离等级:

隔离级别 脏读 不可重复读 幻读
未提交读(Read uncommitted) 可能 可能 可能
已提交读(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行读(Serializable) 不可能 不可能 不可能
  • 未提交读:允许脏读,可能会读取到别的事务中未提交的临时数据
  • 已提交读:只已经提交的数据,Oracle等数据库默认是这个级别
  • 可重复读:可重复读,InnoDB默认的等级是这个,可是还是出现幻读
  • 可串行读:完全串行化的读,表示每次加锁都是表级锁,而且读写相互阻塞,读是共享锁,写是排他锁

这里思考一下,为啥阻止幻读的隔离级别比不可重复读的高,因为InnoDB是行级锁,不可重复读是针对于对于正在修改或删除的数据行加锁,但还是可以对表进行插入,所以可能出现幻读,要避免幻读就要把表的读写都变成表级锁,才能避免幻读,也因此变成了隔离等级为“可串行读”。

PS:以上内容以 悲观锁 的概念可以更好理解,不过实际中出于性能考虑,是用以 乐观锁 为理论基础的MVCC(多版本并发控制,Multi-Version Concurrency Control )

什么是悲观锁,什么是乐观锁

悲观锁:

悲观锁,正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

即在事务A中数据在进行读取(共享锁)的时候,其他事务不能进行修改(排他锁),当在事务A的数据进行修改(排他锁)的时候,不能进行读取(共享锁)。

优点:可以独自占有,直到执行操作完成,然后释放锁,为数据处理的安全提供了保障。

缺点:但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会

乐观锁:

悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

而乐观锁就很好的解决了这个问题,乐观锁可以有两种方式实现,一种是version,一种是时间戳,这里以version为例,假设数据一般情况下不会发生冲突,只有在提交的时候,才会进行加锁,并判断这提交的事务的版本与当前数据库的版本的对比,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据,则把请求驳回。

优点:

乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。

缺点:

如果是在高并发下,很多用户容易出现冲突,即请求容易驳回

InnoDB中MVCC的实现:

MVCC的实现没有固定的规范,每个数据库中都会有不同的实现,这里讨论InnoDB的MVCC,其内部原理是通过乐观锁,在InnoDB中,会在每行数据后面添加两个额外的隐藏的值来实现MVCC,一个是这个数据何时被创建,一个是这数据何时过期。在实际中,这里存储的是版本号,每开启一个新的事务,事务的版本号就回增加。在可重读Repeatable reads事务隔离级别下:

  1. SELECT时,读取创建的版本号<=当前数据库的版本。删除版本为空或>当前当前事务版本的
  2. INSERT时,保存当前事务版本为行的创建版本
  3. DELETE时,保存当前版本为行的删除版本
  4. UPDATE时,插入一条新数据。保存当前事务版本为行创建版本,同一时候保存当前事务版本到原来删除的行
  5. 通过MVCC,尽管每行记录都须要额外的存储空间,很多其它的行检查工作以及一些额外的维护工作。但能够降低锁的使用,大多数读操作都不用加锁,读数据操作非常easy,性能非常好,而且也能保证仅仅会读取到符合标准的行。也仅仅锁住必要行。

通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。

我们不管从数据库方面的教课书中学到,还是从网络上看到,大都是上文中事务的四种隔离级别这一模块列出的意思,RR级别是可重复读的,但无法解决幻读,而只有在Serializable级别才能解决幻读。于是我就加了一个事务C来展示效果。在事务C中添加了一条teacher_id=1的数据commit,RR级别中应该会有幻读现象,事务A在查询teacher_id=1的数据时会读到事务C新加的数据。但是测试后发现,在 MySQL 中是不存在这种情况的,在事务C提交后,事务A还是不会读到这条数据。可见在MySQL的RR级别中,是解决了幻读的读问题的。参见下图

MySQL学习系列之InnoDB下事务隔离机制

MVCC中可能读取的之前版本的数据,要如何读取当前数据呢?

这里又引申出两个概念:快照读和当前读

快照读:就是普通的select

  • select * from table ...;

当前读:特殊的读操作(是要获取锁的),例如插入/更新/删除就是当前读

  • select * from table where ? lock in share mode;(共享锁)
  • select * from table where ? for update;(排他锁)
  • insert;
  • update;
  • delete;

事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。事务的隔离只定义了读数据的要求,而写的要求当然是"当前读"。

这里注意,InnoDB默认的是行级锁,行级锁都是基于索引的。在当前读的查询语句中,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

在MySQL中,insert、update、delete语句默认会对涉及的数据集加排他锁,在Read committed等级下select语句默认是不会加的,如果要加的话,则需要显示在后面加 lock in share mode。在Repeatable read以及在Serializable隔离机制下,select是加共享锁的。

我是MySQL菜鸟,如果对于本文中有什么疑问或者问题,欢迎互相讨论提高


以上所述就是小编给大家介绍的《MySQL学习系列之InnoDB下事务隔离机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Defensive Design for the Web

Defensive Design for the Web

37signals、Matthew Linderman、Jason Fried / New Riders / 2004-3-2 / GBP 18.99

Let's admit it: Things will go wrong online. No matter how carefully you design a site, no matter how much testing you do, customers still encounter problems. So how do you handle these inevitable bre......一起来看看 《Defensive Design for the Web》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试