MySQL 事务,是我们去面试中高级开发经常会问到的问题,很多同学虽然经常使用 MySQL,SQL 语句写得很6,但是很多时候,被问到这些问题的时候,总是不知从何说起,下面我们来系统的学习一下什么是 MySQL事务,让我们面对面试官的时候能够侃侃而谈,避免一问三不知的尴尬。
1. 什么是事务
释义:事务是数据处理的最小操作单元,是一组不可在分割的操作集合,这个操作单元里的一系列操作要么都成功,要么都失败。
最为典型的例子就是转账了。
情景再现:
同学A给同学B转了1000块钱,那么此时A的账户上肯定会-1000, 而B的账户上肯定会+1000,这两个操作必须同时成功,或者同时失败。 update account set accountA = accountA - 1000 where user_id = 1; update account set accountB = accountB + 1000 where user_id = 3;
我们可以通过 set session autoCommit = on/off
来设置mysql事务是否自动开启。
如果我们设置autoCommit为off的时候,我们需要手动开启mysql事务。
----开启事务(2选1) begin; start transaction; 通过 commit;或者 rollback;设置事务提交或者回滚;
2. 事务的四大特性
事务四大特性,通常我们称为ACID。
原子性(A
tomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都成功(commit),要么都失败(rollback)。
如图所示用户A给用户B转账1000,A账户-1000,B账户+1000,这个操作是一个原子操作,是不可分割的,要么同时成功,数据库中A-1000,B+1000,要么同时失败,操作回滚,数据库中数据不变。
一致性(C
onsistency)
事务前后数据的完整性必须保持一致。
事务执行的结果必须使数据库从一个一致性状态变到另一个一致性的状态。
从定义理解上来说 一致性和原子性的概念很容易混淆。
原子性其实并不能保证一致性。
再多个事务并行进行的情况下,即使保证每一个事务的原子性,任然可能导致数据不一致的结果。
上述操作,保证了原子性,但是数据库的一致性却没有得到保证。
隔离性(I
solation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
隔离性算是最有写点 的一个特性了,后面的问题都是围绕隔离性来展开,再次就不赘述了
持久性(D
urability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
3. 事务的四大隔离级别
隔离级别特性
未提交读(read uncommitted)
事务A对数据进行修改,但未提交。
此时开启事务B,在事务B中能读到事务A中对数据库进行的未提交数据的修改。
提交读(read committed)
事务A对数据进行修改,但还未提交。
此时开启事务B,在事务B中不能读到事务A中对数据库的修改。在事务B还没有关闭时,此时事务A提交对数据库的修改。这时候,我们在事务B中,可以查到事务A中对数据库的修改。这时存在一个问题,我们在同一个事务中,对数据库查询两次,但两次的结果是不一样的。
可重复读(repetition read)。
在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。
在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
串行化(serializable)
在开启事务A时,会产生锁表,此时别的事务会等待,等事务A结束时才会开启。
一般数据库都不会采用,因为无论做什么操作都不会加锁,不具备可用性。
不同的数据库采用的隔离级别也会不一样,oracle采用的提交读(RC),而mysql默认的存储引擎innodb采用的是可重复读(RR)。
在RC级别下,所有的读取都是不加锁的,只有增删改的情况会加锁。
4. 因为隔离级别造成大三大问题
什么是脏读:一个事务能读到另一个事务未提交的数据,称为脏读。
下图所示:
第一步,事务A获取到id=1的count=10,第二步,事务B将id=1的count修改成了15但是没有commit,第三步,事务A又获取了一遍id=1的count的值,发现此时count=15,第四步,事务B回滚后,count又变成了10,但是事务A却已经读到脏数据count=10。
什么是不可重复读:一个事务读取到另一个事务已经提交的数据,称为不可重复读。
仔细的同学会发现,这张图和上面的的图的唯一差别是事务B在执行update后commit。
所以说不可重复读和脏读的区别就是 读到的修改的数据是否提交。
什么是幻读:幻读和不可重复读都是一个事务读取到另一个事务的已经提交后的数据, 区别在于不可重复读是针对于update和delete,而幻读是针对于insert。
下图所示(id非唯一索引):
如图所示,事务A在第二次读取到了事务B新增并且已经commit的数据,所以在第二次事务A读到两条数据,这个结果就叫做幻读。
两个关键点①已提交②insert。
5. 为什么 MySQL 的 InnDB 引擎能解决幻读问题
在上面事务的隔离级别会产生的三大问题中,我们看到在RR级别下,依然会有幻读的问题,但是mysql的innodb存储引擎却很好的解决了这个问题。
我们知道,不可重复读和幻读最大的区别在于读到的提交的数据是update/delete还是insert。
在我看来,更深层次的区别应该是innodb采用了什么样的锁机制来解决了这两个问题。
关于锁,我前面也写了一篇文章详细说明了mysql的各种锁
有兴趣的同学可以看一下。
通过行锁算法我们可以知道,我们在读取id=1的数据时就可以给这条数据加上锁,这样就能避免在对这条数据进行其他操作(update/delete)。
但是insert还能继续操作,这样只能避免了不可重复读却无法避免幻读!
那么幻读是如何避免的呢,我们知道在行锁算法中存在一个临键锁,我们可以通过临键锁来解决幻读问题。
但是上述都是采用的悲观锁机制,不可避免的降低了性能,这是mysql不能接受的,在mysql中采用了以乐观锁为基础的MVCC(多版本并发控制)来解决幻读。
什么是乐观锁
也有详解。
6. 什么是MVVC
按照学习的锁的理论知识来说,如果我们update table set name='A' where id = 1;
我们知道这肯定会给id=1这行数据添加了一个X锁,那么我们select * from table where id = 1;
肯定是处于阻塞状态,但是为什么实际情况我们能够查询到数据呢?
这就是MVCC厉害的地方了!
在InnoDB中,给每行增加两个隐藏字段来实现MVCC,一个用来记录数据行的创建时间,另一个用来记录行的过期时间(删除时间)。
在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。
在RR级别下:
SELECT
读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本号的记录。这样可以保证在读取之前记录是存在的。
INSERT
将当前事务的版本号保存至行的创建版本号
UPDATE
新插入一行,并以当前事务的版本号作为新行的创建版本号,同时将原记录行的删除版本号设置为当前事务版本号
DELETE
将当前事务的版本号保存至行的删除版本号
如下图所示:
此时距离我们查出我们需要的数据只剩最后一步,别急,我们先来说下数据查询规则(都是数据库内部控制,无需我们操作):
1.查找数据行版本早于当前事务版本的数据行(行的系统版本号小于或者等于当前事务版本号)这样可以确保事务读取的行要么在事务开始之前就已经存在了,要么是事务本身插入或者修改的。
2.查找事务删除版本号要么为null,要么大于当前事务版本号。确保取出来的行记录在事务开始前没有被删除。
最后查出来的数据为:
上述就是整个mysql中MVCC的新增,修改,删除的逻辑流程,可能比较复杂,需要好好体会。
7. 总结
事务机制是我们日常开发中必备的知识。无论是spring事务还是数据事务,可能我们在开发中接触不到这么深层次理论知识。但是强大的理论知识是我们日常解决bug的重要后盾。而且也是面试中征服面试官的必备条件。所以,加油!