
# 目录
  * [事务的基本要素（ACID）](#事务的基本要素（acid）)
  * [事务的并发问题](#事务的并发问题)
  * [MySQL事务隔离级别](#mysql事务隔离级别)


本文转自:https://blog.csdn.net/sinat_27143551/article/details/80876127

本系列文章将整理到我在GitHub上的《Java面试指南》仓库，更多精彩内容请到我的仓库里查看
> https://github.com/h2pl/Java-Tutorial

喜欢的话麻烦点下Star哈

本也将整理到我的个人博客：
> www.how2playlife.com

更多Java技术文章将陆续在微信公众号【Java技术江湖】更新，敬请关注。

本文是《重新学习MySQL数据库》系列其中一篇，本文部分内容来源于网络，为了把本文主题讲得清晰透彻，也整合了很多我认为不错的技术博客内容，引用其中了一些比较好的博客文章，如有侵权，请联系作者。

该系列博文会告诉你如何从入门到进阶，从sql基本的使用方法，从MySQL执行引擎再到索引、事务等知识，一步步地学习MySQL相关技术的实现原理，更好地了解如何基于这些知识来优化sql，减少SQL执行时间，通过执行计划对SQL性能进行分析，再到MySQL的主从复制、主备部署等内容，以便让你更完整地了解整个MySQL方面的技术体系，形成自己的知识框架。

如果对本系列文章有什么建议，或者是有什么疑问的话，也可以关注公众号【Java技术江湖】联系作者，欢迎你参与本系列博文的创作和修订。

<!-- more -->


## 事务的基本要素（ACID）

　　1、原子性（Atomicity）：事务开始后所有操作，要么全部做完，要么全部不做，不可能停滞在中间环节。事务执行过程中出错，会回滚到事务开始前的状态，所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体，就像化学中学过的原子，是物质构成的基本单位。

　　2、一致性（Consistency）：事务开始前和结束后，数据库的完整性约束没有被破坏 。比如A向B转账，不可能A扣了钱，B却没收到。

　　 3、隔离性（Isolation）：同一时间，只允许一个事务请求同一数据，不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱，在A取钱的过程结束前，B不能向这张卡转账。

　　 4、持久性（Durability）：事务完成后，事务对数据库的所有更新将被保存到数据库，不能回滚。

## 事务的并发问题

　　1、脏读：事务A读取了事务B更新的数据，然后B回滚操作，那么A读取到的数据是脏数据

　　2、不可重复读：事务 A 多次读取同一数据，事务 B 在事务A多次读取的过程中，对数据作了更新并提交，导致事务A多次读取同一数据时，结果 不一致。

　　3、幻读：系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级，但是系统管理员B就在这个时候插入了一条具体分数的记录，当系统管理员A改结束后发现还有一条记录没有改过来，就好像发生了幻觉一样，这就叫幻读。

　　小结：不可重复读的和幻读很容易混淆，不可重复读侧重于修改，幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行，解决幻读需要锁表

## MySQL事务隔离级别

事务隔离级别	脏读	不可重复读	幻读
读未提交（read-uncommitted）	是	是	是
读已提交（read-committed）	否	是	是
可重复读（repeatable-read）	否	否	是
串行化（serializable）	否	否	否
默认事务隔离级别：



1、 读未提交例子

（1）打开客户端A，设置事务模式为read uncommitted，查询表



（1）在A提交事务之前，打开客户端B，更新表





（3）此时，B的事务没提交，但是A已经可以查到B更新的数据



（4）一旦B的事务因某原因回滚，则A查到的数据就是脏数据。



（5）在A执行更新语句，在不知道有其他事务回滚时，会发现结果好奇怪。要解决这个问题可以采用读已提交的事务隔离级别。



2、 读已提交

（1）在客户端A设置事务模式为read committed;



（2）在客户端A事务提交之前，打开客户端B，起事务更新表



（3）B的事务还未提交，A不能查到已经更新的数据，解决了脏读问题：



（4）此时提交客户端B的事务



（5）A执行与上一步相同的查询，结果发现与上一步不同，这就是不可重复读的问题：



3、 可重复读

（1）打开客户端A，设置事务模式为repeatable read。



（2）在A事务提交之前，打开客户端B，更新表account并提交：



（3）在客户端A执行步骤1的查询，zhangsan的balance依然是450与步骤（1）查询结果一致，没有出现不可重复读的问题；接着执行update balance = balance – 50 where id=4;balance没有变成450-50=400；zhangsan的balance值用的是步骤 （2）中的400来算的，所以是350。数据的一致性没有被破坏。



（4）在客户端A提交事务



（5）在客户端A开启事务，随后在客户端B开启事务，新增一条数据。提交



（6）在A计算balance之和，值为350+16000+2400=18750，没有把客户端B新增的数据算进去，客户端A提交后再计算balance之和，居然变成了19350，这时因为把客户端B的600算进去了。站在客户的角度，客户是看不到客户端B的，他会觉得天上掉馅饼了，多了600块，这就是幻读，站在开发者的角度，数据的一致性没有破坏。但是在应用程序中，我们的代码可能会把18750提交给用户了，如果一定要避免这种小概率状况的发生，那么就要采取“串行化”的事务隔离级别了。



4、 串行化

（1）打开客户端A，设置事务隔离级别为serializable并开启事务。



（2）打开客户端B，同样设置事务隔离级别为serializable，开启事务插入数据，报错。表被锁了，插入失败，mysql中事务隔离级别为serializable时会锁表，因此不会出现幻读的情况，这种隔离级别并发性很低，开发中很少用到。



　　补充：

　　1、SQL规范所规定的标准，不同的数据库具体的实现可能会有些差异

　　2、mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行

　　3、事务隔离级别为读提交时，写数据只会锁住相应的行

　　4、事务隔离级别为可重复读时，如果有索引（包括主键索引）的时候，以索引列为条件更新数据，会存在间隙锁间隙锁、行锁、下一键锁的问题，从而锁住一些行；如果没有索引，更新数据时会锁住整张表。

　　5、事务隔离级别为串行化时，读写数据都会锁住整张表

　　6、隔离级别越高，越能保证数据的完整性和一致性，但是对并发性能的影响也越大，鱼和熊掌不可兼得啊。对于多数应用程序，可以优先考虑把数据库系统的隔离级别设为Read Committed，它能够避免脏读取，而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题，在可能出现这类问题的个别场合，可以由应用程序采用悲观锁或乐观锁来控制。
