本文将弄明白以下几个问题。
一、思考
问题一:事务是什么?为了解决什么问题?有哪些特性?
问题二:各种事务的隔离级别的区别?
二、分析
这篇文章先真正搞清楚事务的特性,并不分析事务的实现原理,如果想了解事务的实现原理,请移步【MYSQL---事务实现原理】。
2.1事务是什么
概念
来自于维基百科:
数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
目的
来自于维基百科:
事务包含了一个序列的对数据库的读写操作。其主要目的在于:
- 为数据库操作序列提供了一个从失败恢复到正常状态的方法,同时提供了数据库即使在异常状态下也能保持一致性的方法。
- 在多个应用程序并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作相互干扰。
2.2事务特性
事务要实现上文所介绍的目的,必然要具备一定的特性(ACID):
原子性(Atomicity)
数据库事务作为一个整体,其包含的所有操作,要么全部被执行,要么全部不执行。
一致性(Consistency)
事务应确保数据库从一个一致性状态转变为另一个一致性状态。
隔离性(Isolation)
多个事务执行时,一个事务的执行不影响其他事务的执行。
持久性(Durability)
已提交的事务对数据库所做的更改,将永远保留在数据库中。
数据库的原子性可以通过回滚来控制(undolog)。一致性需要我们的业务逻辑支持,保证数据一致性,在数据库层面,只要事务执行过程中不发生异常,就认为一致。持久性通过确保对数据库的更改一定会被持久化到数据库文件(redolog)。这三点对于开发人员来说干涉较少,而对于隔离性,数据库提供了四种隔离级别,供我们选择,各有优缺点,请看下文。
2.3事务的隔离级别
事务共有四种隔离级别,在不同的隔离级别下可能会存在各种问题,比如脏读,不可重复读,幻读等。
相关名词
脏读
假设有事务A,B和数据D。
事务A读取了事务B修改的数据D,此时事务B还未提交,然后事务B又对修改进行了回滚或者再次修改,导致事务A读取到的数据是脏数据,称为脏读。
不可重复读
同一个事务内两次读取同一行数据的结果不一致。
幻读
同一事务内两个相同的查询语句得到的数据集不一致。
隔离级别设置
InnoDB提供了以下四种隔离级别:读未提交( READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)、可串行化(SERIALIZABLE)。
而默认的隔离级别为可重复读(RR)。我们可以通过以下参数重新设置事务的隔离级别:
隔离级别比较
假设有表t,字段(id,name);事务A和B。我们模拟不同隔离级别下的场景。
- 读未提交( READ UNCOMMITTED)
未提交的事务数据也能被其他事务读到,举例
初始数据(1,‘zhangsan’),事务A和B执行顺序如下:
事务A 事务B SET autocommit=0;
START TRANSACTION;SET autocommit=0;
START TRANSACTION;//步骤一
UPDATE t SET name='a' WHERE id=1; //name='a'
//步骤二(脏读)
SELECT name FROM t WHERE id=1; //name='a';
//步骤三
....一些其他操作
//步骤四
UPDATE t SET name='b' WHERE id=1; // name='b'
COMMIT;
//步骤五(不可重复读)
SELECT name FROM t WHERE id=1; //name='b';
//步骤六
...一些其他操作
COMMIT;
问题
由上图可知,由于隔离级别为“读未提交”,事务A在步骤二中可读取到事务B未提交的数据,此时事务A认为name为a,便拿到这个值进行步骤三的其它业务逻辑操作,但是事务B在步骤四中又修改了name的值为b,导致事务A在步骤二中得到的数据变为脏数据,产生“脏读”现象,步骤三种的业务逻辑就可能出错。接下来步骤五中又读取name的值为b,发现和第一次读取的值不一样,导致步骤六可能出错,又产生了“不可重复读”现象。
综上所述,读未提交隔离级别,会产生“脏读”和“不可重复读”现象。
- 读已提交(READ COMMITTED)
已经提交的事务数据才能被其他事务读到,举例
初始数据(1,‘zhangsan’),事务A和B执行顺序如下:
事务A 事务B SET autocommit=0;
START TRANSACTION;SET autocommit=0;
START TRANSACTION;//步骤一
UPDATE t SET name='a' WHERE id=1; //name='a'
//步骤二(无脏读)
SELECT name FROM t WHERE id=1; //name='zhangsan';
//步骤三
....一些其他操作
//步骤四
...一些其他操作
COMMIT;
//步骤五(不可重复读)
SELECT name FROM t WHERE id=1; //name='a';
//步骤六
...一些其他操作
COMMIT;
问题
由上图可知,由于隔离级别为“读已提交”,故而事务A在步骤二中无法读取到事务B未提交的数据,所以事务A认为name为zhangsan,这个时候已经解决了“读未提交”中的“脏读”。但是当事务B提交事务之后,事务A中又在步骤五查询了name值,此时事务A读取到name为a,在事务A内两次读取到的name值不一致,导致接下来步骤六中的操作可能会出错,这便产生了“不可重复读”现象。
综上所述,在“读已提交”隔离级别下,虽然解决了“读未提交”中的脏读,但是“不可重复读”现象依然存在。
- 可重复读(REPEATABLE READ)
只读取当前事务数据,举例
初始数据(1,‘zhangsan’),事务A和B执行顺序如下:
事务A 事务B SET autocommit=0;
START TRANSACTION;SET autocommit=0;
START TRANSACTION;//步骤一
UPDATE t SET name='a' WHERE id=1; //name='a'
//步骤二(无脏读)
SELECT name FROM t WHERE id=1; //name='zhangsan';
//步骤三
SELECT COUNT(*) FROM t WHERE id>=1; //count=1;
//步骤四
....一些其他操作
//步骤五
UPDATE t SET name='b' WHERE id=1; // name='b'
INSERT INTO t (id,name) VALUES (2,'lisi');
COMMIT;
//步骤六(无不可重复读)
SELECT name FROM t WHERE id=1; //name='zhangsan';
//步骤七(幻读)
SELECT COUNT(*) FROM t WHERE id>=1; //count=2;
COMMIT;
问题
由上图可知,由于隔离级别为“可重复读”,事务A无法读取到事务B“修改”的数据,所以不存在“脏读”和“不可重复读的情况”,但是对于事务“B”的插入操作,事务A却能读到(这个为什么能读到,等到我们后面博文分析事务的实现原理时再分析),此时对于事务A来说,两次读取的count值不一样,如同产生了幻觉一样,所以称为“幻读”现象。
综上所述,“可重复读”隔离级别解决了“脏读”、“不可重复”问题,但是存在“幻读”现象。
上面理论是这样,但是在博文【MYSQL---锁】中有提到,由于InnoDB采用了“间隙锁”,从而避免了在“可重复读”隔离级别下的幻读现象。即在“可重复读”隔离级别下是不存在幻读情况的。
- 可串行化(SERIALIZABLE)
所有事务都按照顺序执行,不存在并发情况,所以当然就不存在“脏读”、“不可重复读”、“幻读”问题。
隔离级别选择
读完上述内容,或许你已经掌握了各种隔离级别的差异,那么你是否想过,为什么要设置这么多隔离级别,真实项目中如何选择使用哪个隔离级别,InnoDB引擎为什么要把“可重复读”作为默认隔离级别?
带着这个疑问,搜索了如下资料,觉得讲的很好,大家有兴趣可以仔细阅读:https://www.cnblogs.com/rjzheng/p/10510174.html。
我从中做了如下总结(如果有疑问的还是请看上文链接地址):
- 为什么InnoDB默认采用“可重复读”隔离级别?
由于历史存在的主从复制的一个bug,而当时的MYSQL版本通过“读已提交”隔离级别无法解决,所以采用“可重复读”隔离级别。而且毕竟“可重复读”解决了“不可重复读”和“幻读问题”。
- 那真实项目中选用哪种隔离级别比较合适?
答案是“可重复读”隔离级别(建议,具体还得视情况而定)。原因如下:
首先,对于“读未提交”和“可串行化”我们一般不予考虑,因为前者会造成业务逻辑混乱,后者性能太差。这里主要讨论“读已提交”和“可重复读”,那为什么推荐“读已提交”,因为:
- “可重复读”存在“间隙锁”,提高了死锁发生的可能性。且由于“间隙锁”的存在,使得并发插入操作性能下降。
- 在“可重复读”隔离级别下,查询条件未命中,会加间隙锁;而“读已提交”隔离级别,不加锁。
- 在“读已提交”隔离级别下,半一致性读增加了update操作并发性。
那“读已提交”情况下的“不可重复读”和“幻读”问题,怎么办?
答案是既然已经提交的数据看到无所谓,但是需要我们业务逻辑的实现自行处理这种情况,否则可能出现逻辑错误。出于性能考虑还是“读已提交”更好。
三、其他
如何查询正在执行的事务?
通过语句:SELECT * FROM information_schema.INNODB_TRX;
可重复读和读已提交差异的底层原理
请看博文:https://mp.csdn.net/postedit/102211569。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
至此,事务的基本内容已讲完,如果不对之处,还请指出探讨。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------参考博文:
- 互联网项目中MYSQL应该选用什么隔离级别:https://www.cnblogs.com/rjzheng/p/10510174.html。