MySQL 事务特性四种隔离级别 (图示每一步的操作)和产生的问题
Posted 猎人在吃肉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 事务特性四种隔离级别 (图示每一步的操作)和产生的问题相关的知识,希望对你有一定的参考价值。
文章目录
本文实验的测试环境:Windows 10+cmd+mysql5.6.36+InnoDB
一、事务的特性(ACID)
-
原子性(Atomicity):
事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。
事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。
也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。 -
一致性(Consistency):
事务开始前和结束后,数据库的完整性约束没有被破坏 。
比如A向B转账,不可能A扣了钱,B却没收到。 -
隔离性(Isolation):
一个事务的执行,不能被其他的事务干扰。(如果 隔离级别 的过低时,会受影响的)
比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。 -
持久性(Durability):
一个事务一旦提交,它对数据的影响的永久的,接下来的其他操作和数据库故障不会对其有任何的影响。
二、MySQL事务的隔离级别和产生的问题
2.1、隔离级别
隔离级别 | 描述 |
---|---|
读未提交( read-uncommitted ) | 一个事务可以读取另一个事务未提交的变更(数据)。 |
读已提交( read-committed ) | 只允许事务读取已经被其他事务提交的变更(数据)。 可以避免脏读,但不可重复读,幻读依然可能出现 |
可重复读( repeatable-read ) | 确保事务可以多次从一个字段中读取相同的值, 在这个事务持续期间,禁止其他事务对这个字段进行更新。 可以避免脏读、不可重复读,但是幻读的问题依然存在。 |
串行化 ( serializable ) | - |
2.1.1、查看隔离级别
select @@tx_isolation;
select @@global.tx_isolation;
mysql 默认的事务隔离级别为 repeatable-read
。
oracle 默认的事务隔离级别为 read-committed
。
2.1.2、设置隔离级别
set transaction isolation level repeatable read;
set global transaction isolation level repeatable read;
隔离级别设置的参数:
read uncommitted
(读未提交)read committed
(读已提交)repeatable read
(可重复读)serializable
(串行化)
2.2、隔离级别产生的脏读
、不可重复读
、幻读
的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交( read-uncommitted ) | 是 | 是 | 是 |
读已提交( read-committed ) | - | 是 | 是 |
可重复读( repeatable-read ) | - | - | 是 |
串行化 ( serializable ) | - | - | - |
2.2.1、脏读:
事务A读取了事务B未提交的更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
2.2.2、不可重复读:
事务 A 多次读取同一数据,
在事务A读取的过程中,事务 B 对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
2.2.3、幻读:
系统管理员A 将数据库中所有学生的成绩从具体分数改为ABCDE等级,
但是系统管理员B 就在这个时候插入了一条具体分数的记录,
当系统管理员A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
2.3、不可重复读的 和 幻读 的区别
不可重复读 侧重于 修改,
幻读 侧重于 新增 或 删除。
2.3.1、解决方法:
不可重复读 的解决方法 是 锁住满足条件的行,
幻读 的解决方法是 锁表 。
三、举例说明不同的隔离级别和产生的问题(图)
创建表:
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`balance` decimal(10,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
插入数据:
insert into `account` (`id`, `name`, `balance`) values('1','lilei','450');
insert into `account` (`id`, `name`, `balance`) values('2','hanmei','16000');
insert into `account` (`id`, `name`, `balance`) values('3','lucy','2400');
3.1、读未提交(出现脏读):
(1)打开一个客户端A,并设置当前事务模式为 read uncommitted(读未提交),查询表 account 的初始值。
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表 account 。
(3)这时,虽然客户端B的事务还没提交,但是 客户端A 就可以查询到B已经更新的数据。
(4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那么,客户端A 查询到的数据其实就是 脏数据。
(5)在客户端A执行更新语句 update account set balance = balance - 50 where id =1
,
lilei 的balance没有变成350,居然是400,是不是很奇怪,数据不一致。
如果你这么想就太天真 了,在应用程序中,我们会用 400-50=350
,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别。
3.2、读已提交(出现不可重复读)
(1)打开一个客户端A,并设置当前事务模式为 read committed
(未提交读),查询表account的所有记录。
(2)在客户端A的事务提交之前,打开另一个 客户端B,更新表 account
。
(3)这时,客户端B 的事务还没提交,客户端A 不能查询到B已经更新的数据,解决了脏读问题。
(4)客户端B的事务提交。
(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题。
3.3、可重复读(出现幻读)
(1)打开客户端A,并设置当前事务模式为 repeatable read
,查询表 account 的所有记录。
(2)在客户端A的事务提交之前,打开 客户端B,设置事务模式为 repeatable read,开启事务,更新表 account ,并提交。
(3)在客户端A查询表 account 的所有记录,与 第1步 查询结果一致,没有出现不可重复读的问题。
(4)在客户端A,接着执行 update account set balance = balance - 50 where id = 1
,
balance 值不是 450-50=400,balance 值用的是 第2步 中的400来算的,减去50,结果是350,数据的一致性没有被破坏。
可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。
(5)重新打开客户端B,开启事务,插入一条新数据,并提交。
(6)客户端A,查询表account,没有查出 新增数据 ,提交事务后,又查询, 查询出新增的数据,这就是 幻读 。
4.4、串行化
(1)打开客户端A,设置当前事务模式为 serializable,开启事务,查询表 account 的数据。
(2)打开客户端B,并设置当前事务模式为serializable,开启事务,插入一条数据,等待一段时间后报错。
因为表被锁,造成插入失败。
mysq l中事务隔离级别为 serializable 时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。
四、知识扩展:
1、事务隔离级别为读提交时,写数据只会锁住相应的行
2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
3、事务隔离级别为串行化时,读写数据都会锁住整张表
4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
5、MYSQL MVCC实现机制参考链接:https://blog.csdn.net/whoamiyang/article/details/51901888
6、关于next-key 锁可以参考链接:https://blog.csdn.net/bigtree_3721/article/details/73731377
五、参考文章:
以上是关于MySQL 事务特性四种隔离级别 (图示每一步的操作)和产生的问题的主要内容,如果未能解决你的问题,请参考以下文章