Mysql隔离级别
一、前置知识-数据库事务
1.1 何为事务?
我们设想一个场景,这个场景中我们需要插入多条相关联的数据到数据库,不幸的是,这个过程可能会遇到下面这些问题:
数据库中途突然因为某些原因挂掉了。
客户端突然因为网络原因连接不上数据库了。
并发访问数据库时,多个线程同时写入数据库,覆盖了彼此的更改。
…
上面的任何一个问题都可能会导致数据的不一致性。为了保证数据的一致性,系统必须能够处理这些问题。事务就是我们抽象出来简化这些问题的首选机制。事务的概念起源于数据库,目前,已经成为一个比较广泛的概念。
何为事务? 一言蔽之,事务是逻辑上的一组操作,要么都执行,要么都不执行。
1.2 何为数据库事务?
把多条语句作为一个整体进行操作的功能,被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败,那么效果就和没有执行这些SQL一样,不会对数据库数据有任何改动。
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
如下A给B转钱的例子很形象的说明了事务的概念:
1.3 事务的操作
开启事务:start transaction;
回滚:rollback;
提交:commit;
1.4 事务的四大特征(ACID)
1)原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败;
2)持久性:事务一旦提交或回滚,数据表的数据将被持久化的保存;
3)隔离性:多个事务之间相互独立;
4)一致性:表示事务操作前后,数据总量不变。
扩展思考:如何保证这四大特征呢?
可以参考此文章:
深入理解数据库事务
这里要额外补充一点:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
(此扩展知识来自:此文章)
二、事务 4 种隔离级别
MySQL 事务隔离级别是为了解决并发事务互相干扰的问题的,MySQL 事务隔离级别总共有以下 4 种:
1、读未提交
2、读已提交
3、可重复读
4、序列化
2.1 事务出现的三种问题
2.1.1、脏读
一个事务读取到另一个事务中没有提交的数据;
eg:假设A修改了一条数据,B能在A还没有被提交的时候就能查看到A的数据,我们把这个问题称为脏读。
2.1.2、不可重复读
在同一个事务中两次读取到的数据不一样;
eg:B在多次读取A的事务时,在其间,A修改了自己的数据,B访问到不同的结果
2.1.3、幻读
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
不可重复读和幻读有什么区别?
参考文章
不可重复读:
重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改
幻读:
重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。
幻读其实可以看作是不可重复读的一种特殊情况,单独把区分幻读的原因主要是解决幻读和不可重复读的方案不一样。
举个例子:执行 delete 和 update 操作的时候,可以直接对记录加锁,保证事务安全。而执行 insert 操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁(Gap Lock)。
也就是说执行 insert 操作的时候需要依赖 Next-Key Lock(Record Lock+Gap Lock) 进行加锁来保证不出现幻读。
2.2 事务的四个隔离级别以及原理
说到隔离级别,大家都知道四个隔离级别,但同时我们也需要思考它的原理。
为了解决上述问题,MySQL 事务隔离级别总共有以下 4 种:
1、读未提交
2、读已提交
3、可重复读
4、序列化
2.2.1 读未提交
【会产生的问题】:脏读、不可重复读、幻读
读未提交隔离机制原理?
思考:
为什么会出现事务没提交就能读到数据的情况呢?
这个问题理解的核心在于,数据库事务的提交和数据的修改提交根本不是一回事。
参考文章:
读未提交-为什么可以读到别人修改的数据
2.2.2 读已提交
【会产生的问题】:不可重复读、幻读
假设A修改了一条数据,B只能看到A已经提交的数据,可以解决脏读问题。
2.2.3 可重复读
【会产生的问题】:幻读
读已提交与可重复读隔离机制原理?
读已提交和可重复读可以放在一起说,这里用到了一个很重要的机制叫 MVCC,MVCC 的实现机制是通过 undo log 来做的 Read view实现的。
参考文章:【数据库】事务 ACID 实现原理 - 隔离性
2.2.4 序列化/串行化(Serializable)
【可以解决所有问题】
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。
像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。
三、数据库常用的隔离级别?
不同的数据库厂商对 SQL 标准中规定的 4 种隔离级别的支持不一样,有的数据库只实现了其中几种隔离级别
MySQL 虽然支持 4 种隔离级别,但是与SQL 标准中规定的各级隔离级别允许发生的现象却有些出入。
在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),所以 MySQL 并不会使用「串行化」隔离级别来避免幻读现象的发生,因为使用「串行化」隔离级别会影响性能。
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:
参考文章:MySQL 可重复读隔离级别,完全解决幻读了吗?
三、查看的命令:
参考文章:数据库事务的四大特性以及事务的隔离级别
在MySQL数据库中查看当前事务的隔离级别:
select @@tx_isolation;
MySQL数据库中设置事务的隔离 级别:
set [glogal | session] transaction isolation level 隔离级别名称;
set tx_isolation=’隔离级别名称;’
四、数据库事务的使用
参考文章:一文读懂什么是数据库事务
数据库事务分为两类:
1、隐式事务
2、显示事务
对于单条SQL语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。
要手动把多条SQL语句作为一个事务执行,使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务,例如,把上述的转账操作作为一个显式事务:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
很显然多条SQL语句要想作为一个事务执行,就必须使用显式事务。
COMMIT是指提交事务,即试图把事务内的所有SQL所做的修改永久保存。如果COMMIT语句执行失败了,整个事务也会失败。
有些时候,我们希望主动让事务失败,这时,可以用ROLLBACK回滚事务,整个事务会失败:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ROLLBACK;
数据库事务是由数据库系统保证的,我们只需要根据业务逻辑使用它就可以。