mysql 事务隔离的研究
带着问题去研究:why-what-how
事务的概念是什么?
为什么会出现事务隔离,是解决什么问题?
mysql 的事务四种隔离级别? 及其不同的隔离级别解决了并发中事务的什么问题?
事务隔离的实现方式是什么?
接下来就让我们一步步解决上述的问题,我们在解决问题中去学习到知识;
一.事务的概念是什么?
事务就是要保证一组数据库操作,要么全部成功,要么全部失败;
例如我们银行转账的时候,A 向 B 转账 100 元分为几步:
查询账号金额;
自己银行金额减少 100 元;
对方银行金额增加 100 元;
我们需要对这三步,要不全部做,如果做到一半失败,那就要回滚,之前做的也要作废;这就是事务;
事务具备四个基本特性:ACID
原子性(Atomicity):事务中包含的各项操作要么全部执行,要么全部不执行。
一致性(Consistency):隔离执行事务时(没有并发时),保持数据库的一致性。
隔离性(Isolation):事务的隔离性是指在并发执行中,系统保证,对于两个事务 A,B。在 A 看来,A 或者在 B 开始前完成,或者在 B 完成后开始。让事务感受不到系统中有其他事务在并发执行。
持久性(Durability):事务一旦提交(commit),对数据库中对应数据的状态变更就应该是永久性的。
图一:事务的四个特性
在 mysql 数据库中,有两种引擎可以选择,一种是 MyISAM,另外一种 InnoDB,其中 MyISAM 引擎不支持事务,InnoDB 支持;以下都是用的 mysql 的 InnoDB 引擎的。
二. 为什么会出现事务隔离,是解决什么问题?
在事务的在并发执行可能会存在一些问题;下面我们通过两个事务 A,B 的并发执行,看看会出现哪些问题;
图二:事务 A 和事务 B 并发执行
脏读(dirty read)读到其他事务未提交的数据:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据;如上图,事务 B 更新 age =21,后并没有 commit 而是 rollback,但是 A 读取的还是事务 B 修改的,age2 为 21,这就是脏读。
不可重复读(non-repeatable read)前后读取的记录内容不一致:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。
幻读(phantom read)后读取的记录数量不一致:一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。
三.mysql 的事务四种隔离级别及其解决了并发中事务中的问题
因为多个事务在并发操作中可能会存在脏读,不可重复读,幻读,所以在 mysql 中规定了一些隔离方法来解决上述问题,目前存在四个隔离级别,分别是:
图三:不同事务隔离级别对于解决的并发事务问题
读未提交(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
读提交(Read Committed):只能读取到已经提交的数据。Oracle 等多数数据库默认都是该级别 (不重复读)
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB 默认级别。在 SQL 标准中,该隔离级别消除了不可重复读,但是还存在幻读
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
这里是 mysql 里面支持的四种隔离级别,我们可以在 mysql 去配置不同级别;
通过 select @@tx_isolation; 查看当前事务的隔离级别,可到看到默认的事务隔离级别是可重复读的;
可以通过 set tx_isolation = 'read-uncommitted'; 来设置事务隔离级别;
下面让我们通过图二的例子,来看看不同隔离级别对应的各个查询结果的不同,我们现在打开两个客户端,一个执行事务 A,一个执行事务 B;我们来看看 age1,age2,age3 的值;
读未提交:就是在事务 B 还没有提交,事务 A 就读到了,这样 A 在执行的过程中,age 的值变了,所以 age1=21,age2=21,age3=21;
读提交:只有提交了,才能读到,所有我们看到只有当事务 B commit 后,age 的值才变了,所有 age1 还是保持原来的值,是 20,age2=21, age3=21;
可重复读:一个事务中,查询的同一条记录,都是不变的,所有在事务 A 的整个过程中,age1 和 age2 的值都没有变化,都是 20;事务结束后,age3=21;
串行读:在事务要执行改成 age 的时候,会被锁住,所有事务 A 的整个执行过程中,age1 和 age2 为 20,age3 是另外一个事务了,所以值改变了,age3=21;
四.事务隔离的实现方式是什么?
读未提交
它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。
读提交
MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。但有利就有弊,这基本上就相当于裸奔啊,所以它连脏读的问题都没办法解决。
任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交。
可重复读
为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。可以可以看看这里:https://blog.csdn.net/whoamiyang/article/details/51901888
串行化。
读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读,读写数据都会锁住整张表。
参考资料:
https://tech.meituan.com/2014/08/20/innodb-lock.html
https://www.cnblogs.com/wyaokai/p/10921323.html
版权声明: 本文为 InfoQ 作者【硬核编程】的原创文章。
原文链接:【http://xie.infoq.cn/article/507dcb2d069cc74d57fd2a118】。文章转载请联系作者。
评论