MySQL 事务隔离级别-北极之北

原创
10/27 15:38
阅读数 15

MySQL 事务隔离级别

本文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。

事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)四个特性,简称 ACID,缺一不可。今天要说的就是隔离性

脏读

 

脏读指的是读到了其他事务未提交的数据。

—解决脏读的方法正常来说,读写分割开即可

不可重复读

 

不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。

—解决不可重复度的方法一般锁住当前行即可

幻读

 

幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

—由于幻读主要针对插入和删除操作,,可能需要锁表。

 

 

针对以上可能出现的问题,MYSQL有四种事物隔离级别

 

读未提交

可能产生脏读、不可重复度、幻读

不可重复度 (读提交)

解决了脏读,但是还是会出现不可重复读、幻读

可重复读

解决了脏读,不可重复读,但是可能产生幻读

串行化

 

解决了脏读、不可重复读和幻读

MySQL事物隔离的实现

读未提交

最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。

串行化

读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。(喘息规划会在读取的每一行数据上都加锁,但是不会锁表)

 

不可重复度

不可重复读解决了脏读,MySQL 采用了 MVVC (多版本并发控制) 的方式。

 

可重复读

为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。

MVCC

 

MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

了解MVCC前,先介绍一下MySQL innoDB的当前读和快照读

  • 当前读 像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

  • 快照读 像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现,如前文说的锁表操作

数据库并发场景有三种,分别为:

  • 读-读:不存在任何问题,也不需要并发控制

  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读

  • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

 

在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读Repeatable reads事务隔离级别下:

SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。

INSERT时,保存当前事务版本号为行的创建版本号。

DELETE时,保存当前事务版本号为行的删除版本号。

UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。

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

读-读

一个快照来说,它能够读到那些版本数据,要遵循以下规则:

  1. 当前事务内的更新,可以读到;

  2. 版本未提交,不能读到;

  3. 版本已提交,但是却在快照创建后提交的,不能读到;

  4. 版本已提交,且是在快照创建前提交的,可以读到;

写-写

两个事物同时更新一行数据,最后结果应该是哪个事务的结果呢。肯定要是时间靠后的那个对不对。并且更新之前要先读数据,这里所说的读和上面说到的读不一样,更新之前的读叫做“当前读”,总是当前版本的数据,也就是多版本中最新一次提交的那版。

 

不可重复读(读提交)如何解决脏读(MVCC)

 

而不可重复度(读提交)每次执行语句的时候都重新生成一次快照。这样事物就不会读到其他事物未提交的数据

 

可重复读如何解决不可重复度(MVCC+行锁)

 

可重复读是在事务开始的时候生成一个当前事务全局性的快照,如果有其他事物去更改该事物的数据,发现该数据被加了行锁,所以其他事物无法去更新它,最终,在该事物的时间内,多次去读取数据,都是一致的,这就是可重复读。

 

幻读如何解决(MVCC+间隙锁)

虽然可重复读解决不可重复读的问题,但是有可能产生幻读。因为仅仅使用行锁,如果在行与行之间,被插入/删除了数据,造成事物对间隙数据的操作未生效(事物两次读取数据,第一次读取10行,第二次读取11行,也被归纳为幻读范畴)

所以,mysql使用MVCC+间隙锁的方式,锁住事物操作的数据及他们之间的间隙,确保不会有其他事物操作该数据

 

MVCC 实现原理

InnoDBMVCC 的实现方式为:每一行记录都有两个隐藏列:DATA_TRX_IDDATA_ROLL_PTR(如果没有主键,则还会多一个隐藏的主键列)。

DATA_TRX_ID

记录最近更新这条行记录的事务 ID,大小为 6 个字节

DATA_ROLL_PTR

表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。

DB_ROW_ID

行标识(隐藏单调自增 ID),大小为 6 字节,如果表没有主键,InnoDB 会自动生成一个隐藏主键,因此会出现这个列。另外,每条记录的头信息(record header)里都有一个专门的 bitdeleted_flag)来表示当前记录是否已经被删除。

 

在多个事务并行操作某行数据的情况下,不同事务对该行数据的 UPDATE 会产生多个版本,然后通过回滚指针组织成一条 Undo Log

undo-log 为了简化分析,关于undo-log本文只介绍如下这种情况,也是最普遍的一种情况: 一条update语句,它根据主键进行查找,并且不修改主键的值。 首先记录undo-log,把本次修改的字段原始值记录下来: 然后在本条记录上进行修改: 修改后写redo-log,redo-log是单独存放的,存放在名为ib_logfile的一组文件中:可以看出以上大体的流程就是先写undo-log,然后本地修改,最后写redo-log。

写undo-log的函数btr_cur_upd_lock_and_undo最终会调用函数trx_undo_page_report_modify(或insert)。

把旧版本的事务id写入undo-log:

把该行的标识字段写入undo-log:

将旧值保存的undo-log中

 

 

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部