【MySQL(3)| 五分钟搞清楚MySQL事务隔离级别】

事务

什么是事务?数据库

数据库操做的最小工做单元,是做为单个逻辑工做单元执行的一系列操做;事务是一组不可再分割的操做集合(工做逻辑单元)编程

举个栗子:bash

事务最经典经常使用的栗子可能就是转帐:一个帐户少钱了,哪另外一个帐户确定要多钱,李永龙说过,亏本的买卖咱可不干,吃亏了不高兴!微信

因此,少钱和多钱这两个操做,要么同时成功,要么同时失败!session

MySQL中如何开启事务?并发

  • 手工开启:begin/start transaction性能

  • 事务提交或回滚:commit/rollback学习

  • 设定事务是否自动开启:set session autocommit = on/off测试

在JDBC中或者Spring事务AOP编程中均可以进行上述设置,你们能够查阅相关的文档,本文不在赘述。ui

事务ACID特性

  • 原子性(Atomicity

    最小的工做单元,要么一块儿成功,要么一块儿失败

  • 一致性(Consistency)

    一致性也称做是完整性,就是说事务的执行不能破坏数据库的一致性,一个事务在执行后,数据库必须从一个状态转变为另外一个状态

  • 隔离性(Isolation)

    并发的事务相互隔离,互不干扰

  • 持久性(Durability)

    持久性是指事务一旦提交,对数据库的状态就应该被永久保存

事务并发带来了哪些问题?

  • 脏读

脏读就是指当一个事务正在访问数据,而且对数据进行了修改,而这种修改尚未提交到数据库中,这时,另一个事务也访问这个数据,而后使用了这个数据。

举个栗子:

1.张三这我的工资是10000,财务将张三的工资改成了20000(可是事务未提交);

2.张三读取本身工资的时候,发现工资变成20000,很是开心;

3.而后财务发现操做有误,本身改错了,随之回滚了事务,张三的工资又变成了10000,结果小伙那叫个伤心欲绝啊。

以上:张三读取到的这个20000就是个脏数据

  • 幻读

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的所有数据行,同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,之后就会发生操做第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉同样。

举个栗子:

假设某公司工资为10000的有10我的

1.事务1,读取全部工资为10000的员工,会读取到10条记录;

2.事务2此时向工资表中插入了一条员工记录,工资正好也是10000;

3.事务1再次读物全部工资为10000的员工,会发现读到了11条记录。

以上:就产生了幻觉,若是咱们能作到在操做事务未完成数据处理以前,其余的任何事务都不能够添加新数据,则能避免该问题的发生。

  • 不可重复读

是指在一个事务内,屡次读同一数据。在这个事务尚未结束时,另一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,因为第二个事务的修改,那么第一个事务两次读到的的数据多是不同的。这样在一个事务内两次读到的数据是不同的,所以称为是不可重复读。

举个栗子:

1.在事务1中,张三 读取了本身的工资为10000,可是操做尚未完成;

2.在事务2中,正好财务人员修改了张三的工资为20000,并提交了事务;

3.在事务1中,张三再次读取本身的工资时,工资变为了20000。

以上:此时至关于发生了不可重复读,若是只有在修改事务彻底提交以后才能够读取数据,则能够避免该问题。

不可重复读的重点是修改 :
一样的条件 , 你读取过的数据 , 再次读取出来发现值不同了
幻读的重点在于新增或者删除
一样的条件 , 第 1 次和第 2 次读出来的记录数不同

为了解决上边所说的几个问题,下面介绍下MySQL对于事务处理的四种隔离级别

事务四种隔离级别

  • Read uncommitter(未提交读) : 没有解决任何问题

  • Read Committer(提交读) :解决了脏读问题

  • Repeatable Read(可重复读): 解决了不可重复读和脏读问题(ps:在Innodb状况下,也不可能发生幻读问题)

  • Serializable(串行化) :脏读、幻读、不可重复读三个问题所有解决了

为了更好的介绍以上四种状况,再举个栗子:

建一张表并插入两条数据:

-- 建表语句
CREATE TABLE `t_transaction` (
  `id` INT(5) NOT NULL AUTO_INCREMENT,
  `account` INT(5) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='事务隔离级别测试表';
-- 插入数据
INSERT INTO t_transaction (id,account) VALUES(1,1000);
INSERT INTO t_transaction (id,account) VALUES(2,1000);
复制代码

表结构以下:


打开两个会话,准备模拟环境。

Read uncommitter

而后在会话1执行以下语句:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM t_transaction;
复制代码

执行结果以下:

img

接下来在会话2执行以下语句,把id为1的记录 account 值增长 200,可是并没有提交事务

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE t_transaction SET account = account+200 WHERE id = 1;
复制代码

回过头咱们再在会话1执行一次查询:

SELECT * FROM t_transaction;
复制代码

执行结果以下:

结论

咱们将事务隔离级别设置为read uncommitted,即使是事务没有commit,在其余会话或者说事务中咱们仍然能读到未提交的数据,这是全部隔离级别中最低的一种,这种状况属于脏读

这样作的话就会产生一个问题:

那就是咱们在一个事务中能够随随便便读取到其余事务未提交的数据,这仍是比较麻烦的,咱们叫脏读。我不知道这个名字是怎么起的,为了加强你们的印象,能够这么想,这个事务好轻浮啊,饥渴到连别人没提交的东西都等不及,真脏,呸!

实际上咱们的数据改变了吗?

答案是否认的,由于只有事务commit后才会真正更新到数据库。

Read committed

咱们利用以下语句将会话2的事务隔离级别设置为read committed

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
复制代码

而后在会话1中执行以下语句:

update t_transaction set account=account-200 where id=1;
复制代码

咱们将id=1的用户account减200。而后查询,发现id=1的用户account变为800。


在会话2中查询:

select * from t_transaction;
复制代码



咱们会发现数据并无变,仍是1000。

接着在会话A中咱们将事务提交:commit

紧接着在会话2中查看结果以下:


结论

当咱们将当前会话的隔离级别设置为read committed的时候,当前会话只能读取到其余事务提交的数据,未提交的数据读不到。

咱们在会话2同一个事务中,读取到两次不一样的结果。这就形成了不可重复读,就是两次读取的结果不一样,这种现象称之为不可重复读

RepeatableRead

这是MySQL默认的隔离级别

在会话2中咱们当前事务隔离级别为repeatable read。具体操做以下:

set session transaction isolation level repeatable read;
start transaction;
复制代码

接着在会话2中查询数据:


咱们在会话1中为表account添加一条数据:

insert into t_transaction(id,account) value(3,1000);
commit;
复制代码

而后咱们查询看数据插入是否成功:


回到会话2,咱们查询结果:


会话2中想插入一条新数据id=3,value=1000。来咱们操做下:

insert into t_transaction(id,account) value(3,1000);
复制代码


执行结果:

结果,问题产生了,居然插不进去

结论

此处须要注意的是,由于咱们使用的Innodb引擎,因此此处不会产生幻读,其余引擎的话在这个隔离级别可能会产生幻读(至于为何说Innodb不会,且听下回分解,其实MySQL 利用锁机制和MVCC避免了这个问题,感兴趣的同窗能够自行查阅)

Serializable

一样,咱们将会话2的事务隔离级别设置为serializable并开启事务。

set session transaction isolation level serializable;
start transaction;
复制代码

在会话2中咱们执行下面操做:

select * from account;
复制代码



那咱们这个时候在用户A所在的会话中写数据呢?

咱们发现会话1所在的会话陷入等待,若是超时(这个时间能够进行配置),会出现Lock wait time out提示:


若是在等待期间咱们用户B所在的会话事务提交,那么用户A所在的事务的写操做将提示操做成功。

结论

当咱们将当前会话的隔离级别设置为serializable的时候,其余会话对该表的写操做将被挂起。能够看到,这是隔离级别中最严格的,可是这样作势必对性能形成影响。因此在实际的选用上,咱们要根据当前具体的状况选用合适的隔离级别。

小结

下面用一张图来简单总结一下


下一篇会深刻学习Innodb引擎提供的锁机制和MVCC机制,敬请期待!

欢迎关注微信公众号 程序猿杂货铺 公号ID zhoudl_l


相关文章
相关标签/搜索