MySQL 事务隔离实验-认识:脏读、不可重复读、幻读

0x00 前言

你们也许据说过 MySQL 的事务在高并发执行的时候可能会发生脏读不可重复读幻读等问题。对于有处理高并发经验的老鸟,可能认知会更深一些因此以为 so easy~「老鸟请点红叉离开,或者发起友好评论O(∩_∩)O哈哈~」,不过对于像我这种难以接触到高并发业务场景的初学者来讲,也就只能看几篇博文,了解一下概念,纸上谈兵/(ㄒoㄒ)/~~。不过本着「打破砂锅问到底」的精神,决定经过作实验来提升对其理解,顺便加强记忆(起码找工做被问到还能说两句)。mysql

0x01 MySql 事务隔离级别

MySql 事务隔离级别和容许并发反作用,分别以下表:sql

事务隔离级别 脏读 不可重复读 幻读
读未提交(read uncommitted)
不可重复读(read committed)
可重复读(repeatable read)
串行化(serializable)

由上表可知,MySQL 共支持四种事务隔离级别。表由上到下容许并发反作用愈来愈弱,彷佛咱们只要选择串行化(serializable)的事务隔离级别就不会发生脏读不可重复读幻读等问题了,可是选择串行化(serializable)却会带来必定的性能降低。因此关于如何选择事务隔离级别咱们须要对脏读不可重复读幻读有必定认知,并肯定这几种反作用对应用的影响,而后选择合适的隔离级别。数据库

MySQL 的默认事务隔离级别为 可重复读(repeatable read) 因此咱们不用担忧「脏读」和「不可重复读」。后端

查询 MySQL 事务隔离级别的语句以下:session

select @@tx_isolation;
/* 输出结果: +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ */
复制代码

设置事务隔离级别:并发

-- 设置事务隔离级别为 read committed,仅在本次会话中生效
set session transaction isolation level read committed;
复制代码

或者能够修改 my.cnf 配置文件使其永久生效。高并发

[mysqld]
transaction-isolation = REPEATABLE-READ
复制代码

0x02 实验环境

本次实验采用 MySql 5.7.21 版本(储存引擎为 Innodb),测试数据表结构以下:性能

/* +-------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | <null> | auto_increment | | name | char(20) | NO | | <null> | | | money | float | NO | | 0 | | +-------+----------+------+-----+---------+----------------+ */
复制代码

0x03 脏读

脏读的概念以下:学习

事务中的修改,即便没有提交,对其余事务也都是可见的。事务能够读取未提交的数据,这也被称做脏读。测试

我的认为脏读的反作用是最大的,如今经过实验证实脏读的危害。

实验users表以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
复制代码

实验步骤表:

时间 客户端 A 客户端 B
T1 设置事务隔离级别为 read uncommitted 设置事务隔离级别为 read uncommitted
T2 开始事务 A
begin;
T3 小王转款给小明 500 元
update users set money=money-500 where id = 1;
update users set money=money+500 where id = 2;
T4 开始事务 B
begin;
T5 查询小明帐户余额
select * from users where id = 2;
查询结果为 500 元,余额充足则执行支付逻辑
T6 小明帐户扣款 100 元
update users set money=money-100 where id = 2;
本条语句将会阻塞
T7 事务 A 回滚
rollback;
语句执行完毕
T8 事务 B 提交
commit;

最后咱们查询users表,结果以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | -100.0 | +----+------+--------+ */
复制代码

使人惊讶的结果,小明的余额变成了 -100 元!这就是脏读的危害,咱们重点看上表的 T5,发如今事务 A 还未提交之时事务 B 便已经读取到了事务 A 更新后的结果,这直接致使了咱们程序判断余额充足从而执行了扣款的逻辑。若是事务 A 成功提交那么程序结果就是正确的,可是事务 A 最后没有成功提交而是进行了回滚,这就致使了用户余额被扣款为负数的灾难。

0x04 不可重复读

不可重复读的概念以下:

一个事务开始时,只能看见已经提交的事务所作的修改。换句话说,一个事务从开始直到提交以前,所作的任何修改对其余事务都是不可见的。可是两次执行一样的查询,可能会获得不同的结果。

实验users表以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
复制代码

实验步骤表:

时间 客户端 A 客户端 B
T1 设置事务隔离级别为 read committed 设置事务隔离级别为 read committed
T2 开始事务 A
begin;
T3 查询小明余额
select * from users where id = 2;
余额为 0 元
T4 开始事务 B
begin;
T5 小明帐户充值100元
update users set money=money+100 where id = 2;
T6 事务 B 提交
commit;
T7 查询小明余额
select * from users where id = 2;
余额为 100 元
T8 事务 A 提交
commit;

不可重复读表如今于在同一个事务之中,两个相同的查询获得的查询结果却不一样。这是因为两个查询结果之间,出现另一个事务修改了包含以前查询结果的记录,致使第二次查询与第一次查询结果不一样。它与脏读的区别在于修改记录的事务 B 必须提交成功,查询事务 A 才能读取到修改后的记录,若是事务 B 回滚了,事务 A 的查询结果仍是同样的。

0x05 幻读

幻读概念以下:

所谓幻读,指的是当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当以前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎经过多版本并发控制(MVCC)解决了幻读的问题。

通过本人测试发如今 可重复读(repeatable read)的事务隔离级别下,MySQL 不会产生幻行可是能够经过写入一行数据来证实幻读问题的存在。

实验users表以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
复制代码

实验步骤表:

时间 客户端 A 客户端 B
T1 设置事务隔离级别为 repeatable read 设置事务隔离级别为 repeatable read
T2 开始事务 A
begin;
T3 开始事务 B
begin;
T4 插入一行
insert into users(id, name, money) values (3, "小红",1000);
T5 事务 B 提交
commit;
T6 查询users
select * from users;
并没有 id 为 3 的记录
T7 插入一行
insert into users(id, name, money) values (3, "小红",1000);
T8 出现报错:(1062, u"Duplicate entry '3' for key 'PRIMARY'")

对于事务 A 来讲出现的报错就像见鬼了同样,由于事务 A 在查询 users 表的结果并不存在 id 为 3 的行!而在插入该行时却出现了该行已存在的报错……也许这就是叫幻读的缘由吧。

0x06 总结

网上已有不少这种类型的文章,本文也参考了许多内容,之因此还要「老调重弹」是由于「纸上得来终觉浅,绝知此事要躬行」,实践才是检验真理的惟一标准,固然本文也可能出现谬误,欢迎指正。心里 OS:数据库真的后端的一块大头,不想成天 CRUD 就要更深刻的学啊。感受《高性能 MySQL》这本书不错,有空要研读一下,最后以为颇有必要学习关于 MySQL 锁相关的内容。

相关文章
相关标签/搜索