常见的InnoDB是支持事务的,但是MyISAM是不支持事务的
当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。举例如下(以账户余额表为例):
在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读,读取到了其它事务已提交的数据。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。举例如下:
在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。举例如下:
三者严重性排序:
因此,要解决脏读现象,就要升级到 读已提交 以上的隔离级别;要解决不可重复读现象,就要升级到 可重复读 的隔离级别,要解决幻读现象不建议将隔离级别升级到 串行化;
MySQL InnoDB 引擎的默认隔离级别虽然是可重复读,但是它很大程度上避免幻读现象(但并不是完全解决了),解决的方案有两种:
快照读:普通的查询select就是快照读。
当前读:MySQL 里除了普通查询是快照读,其他都是当前读,比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
Multi-Version Concurrency Control 多版本并发控制。
看到这里时,可以先看后文,ReadView部分
假如某一张表t_text,有如下4个数据:
同一个事物,两次查询之间对于其他事物新增的数据进行了修改
在可重复读的隔离级别下面,开启了两个事务。一个是事务A,另外一个是事务B,其事务ID如下
两个事务的操作如下:
如果在t4时刻,当前事务A不修改id为5的值的话,那么事务A就会沿着版本链,找到小于事务A的Read View的事务id的第一条记录,也就没有产生幻读。
使用范围查询的时候,不用select...for update来查询
两次查询语句之间,另外一个事物提交了新增的数据。
由于在T4时刻,使用的是当前读,那么这个Read View对于当前读来说就失效了。也就是说,select...for update读取的是最新的数据。并且事务B已经提交了,因此就不会阻塞事务A使用select...for update进行读取。
要避免这类幻读,就尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录
因此可以说,快照读是一种一致性不加锁的读,是InnoDB并发如此之高的核心原因之一,但没有完全解决幻读现象。
当前读可以说完全解决了幻读问题,因此每次读的都保证是最新的数据,但是是通过加锁实现的。
readView是MVCC多版本并发控制的一种实现手段。这个也叫快照读
Read View是一个数据库的内部快照,保存着数据库某个时刻的数据信息。Read View会根据事务的隔离级别决定在某个事务开始时,该事务能看到什么信息。通过Read View,事务可以知道此时此刻能看到哪个版本的数据记录(有可能不是最新版本的,也有可能是最新版本的)。读已提交隔离级别是在每个语句执行前重新生成一个 Read View,而可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
注意:max_trx_id 并不是m_ids中的最大值,事务id是递增分配的。比如,现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括还活跃的事务1和2,min_trx_id的值就活跃事务 中事务 id 最小的事务,即1,max_trx_id的值就是4。
在创建 Read View 后,可以将记录中的 trx_id 划分这三种情况:
MVCC的实现方式:Read View + undolog
可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,那这两个事务创建的 Read View 如下:
事务 A 和 事务 B 的 Read View 具体内容如下:
假设在可重复读隔离级别下,事务 A 和事务 B 按顺序执行了以下操作:
分析执行过程:
事务 B 第一次读zhangsan的账户余额记录,在找到记录后,它会先看这条记录的 trx_id,此时发现 trx_id 为 50,比事务 B 的 Read View 中的 min_trx_id 值(51)还小,这意味着修改这条记录的事务早就在事务 B 启动前提交过了,所以该版本的记录对事务 B 可见的,也就是事务 B 可以获取到这条记录。
接着,事务 A 通过 update 语句将这条记录修改了(还未提交事务),将zhangsan的余额改成 200 万,这时这条记录的trx_id 为事务A的事务id(trx_id = 51), 并且MySQL 会记录相应的 undo log,并以链表的方式串联起来,形成版本链,如下图:
接着事务 B 第二次读取该记录,发现这条记录的 trx_id 值为 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,则需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 小于 事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是小林余额是 100 的这条记录。
最后,当事物 A 提交事务后,由于隔离级别时 可重复读,所以事务 B 第三次读取记录时,还是基于启动事务时创建的 Read View 来判断当前版本的记录是否可见。所以,即使事务 A 将zhangsan余额修改为 200 并提交了事务, 事务 B 第三次读取记录时,读到的记录都是zhangsan余额是 100 的这条记录。
读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。
假设事务 A (事务 id 为51)启动后,紧接着事务 B (事务 id 为52)也启动了,接着按顺序执行了以下操作:
事务B第一次读时创建的Read View:
此时,在找到记录后,它会先看这条记录的 trx_id,此时发现 trx_id 为 50,比事务 B 的 Read View 中的 min_trx_id 值(51)还小,这意味着修改这条记录的事务早就在事务 B 启动前提交过了,所以该版本的记录对事务 B 可见的,也就是事务 B 可以获取到这条记录。因此读取到的zhangsan的账户余额为 100 ;
接着事务A修改数据(还没提交事务):
事务B第二次读时创建的Read View:
显然,此时事务B读不到事务 A (还未提交事务)修改的数据。
事务 B 在找到这条记录时,会看这条记录的 trx_id 是 51,在事务 B 的 Read View 的 min_trx_id 和 max_trx_id 之间,接下来需要判断 trx_id 值是否在 m_ids 范围内,判断的结果是在的,那么说明这条记录是被还未提交的事务修改的,这时事务 B 并不会读取这个版本的记录。而是,沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 小于 事务 B 的 Read View 中的 min_trx_id 值的第一条记录,所以事务 B 能读取到的是 trx_id 为 50 的记录,也就是zhangsan的账户余额为 100 的这条记录。
接着事务A提交了事务:
事务B第三次读时创建的Read View:由于隔离级别是 读提交 ,所以事务 B 在每次读数据的时候,会重新创建 Read View
此时事务 B 在找到这条记录时,会发现这条记录的 trx_id 是 51,比事务 B 的 Read View 中的 min_trx_id 值(52)还小,这意味着修改这条记录的事务早就在创建 Read View 前提交过了,所以该版本的记录对事务 B 是可见的。
正是因为在读提交隔离级别下,事务每次读数据时都重新创建 Read View,那么在事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务,也就造成了不可重复读的问题。
事务的 ACID四大特性,原子性、一致性、隔离性、持久性;原子性、隔离性、持久性都是为了保证最终的一致性
当多个事务并发执行的时候,会引发脏读、不可重复读、幻读这些问题。
要解决脏读现象,就要将隔离级别升级到读已提交以上的隔离级别,要解决不可重复读现象,就要将隔离级别升级到可重复读以上的隔离级别。
对于读已提交和可重复读隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同:
这两个隔离级别实现是通过事务的Read View 里的四个字段和 记录中的两个隐藏列(事务id和指针) 的版本比对,来控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。
本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top
参与评论
手机查看
返回顶部