一文读懂 MySQL 事务隔离机制
MySQL 事务隔离机制
- 隔离性与隔离级别
- 四个案例看懂 MySQL 事务隔离级别
- 查看隔离级别
- READ_UNCOMMITTED
- 脏读
- 不可重复读
- 幻读
- READ_COMMITTED
- REPEATABLE_READ
- SERIALIZABLE
- 事务隔离的实现
- 总结
提到事务,大家肯定不陌生。最经典的例子就是银行转账。
比如,A 账户给 B 账户转账 100。在这种交易的过程中,有几个问题值得思考:
- 如何同时保证上述交易中,A 账户总金额减少 100,B账户总金额增加 100?
- A 账户如果同时在和 C 账户交易(T2),如何让这两笔交易互不影响?
- 如何在支持大量交易的同时,保证数据的合法性(没有钱凭空产生或消失) ?
- 如果交易完成时数据库突然崩溃,如何保证交易数据成功保存在数据库中?
要保证交易正常可靠地进行,数据库就得解决上面的四个问题,这也就是事务诞生的背景,它能解决上面的四个问题。
简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL 中,事务支持是在引擎层实现的。
我们知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。
比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。
下面,配合实例,我们分析 InnoDB 存储引擎在事务支持方面对于隔离性的实现。
隔离性与隔离级别
我们知道事务的四个特性:ACID(Atomicity
、Consistency
、Isolation
、Durability
,即原子性、一致性、隔离性、持久性)。
当数据库上有多个事务同时执行的时候,就可能会出现问题:
- 脏读(dirty read)
- 不可重复读(non-repeatable read)
- 幻读(phantom read)
为了解决这些问题,就有了隔离级别的概念。
在谈隔离级别之前,我们首先要知道,隔离的越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。
SQL标准的事务隔离级别包括:
- 读未提交(
read uncommitted
):一个事务还没提交时,它做的变更就能被别的事务看到; - 读提交(
read committed
):一个事务提交以后,它做的变更才会被其它事务看到; - 可重复读(
repeatable read
):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其它事务也是不可见的; - 串行化(
serializable
):对于同一行记录,「写」会加「写锁」,「读」会加「读锁」。当出现读写锁冲突的时候,后访问的事务必须等待前一个事务执行完成,才会继续执行。
在 MySQL 数据库中,默认的事务隔离级别是 RR。
四个案例看懂 MySQL 事务隔离级别
查看隔离级别
MySQL8 之前的查询命令是:
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
MySQL8 开始查询命令是:
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
根据上图,可以看到,默认的隔离级别为 REPEATABLE-READ
,「全局隔离级别」和「当前会话隔离级别」是相同的。
我们可以通过如下命令修改隔离级别(建议在修改时,仅修改当前 session 隔离级别即可,不用修改全局的隔离级别):
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
上面这条 SQL 表示,仅把当前 session
的数据库隔离级别设置为 READ UNCOMMITTED
,设置成功后,再次查询隔离级别,发现当前 session
的隔离级别已经变了,如图:
注意,这里只是修改了当前 session
的隔离级别,换一个 session
之后,隔离级别又会恢复到默认的隔离级别,如果使用的是 Navicat 的话,不同的查询窗口就对应了不同的 session
。
READ_UNCOMMITTED
READ UNCOMMITTED
是最低隔离级别,这种隔离级别中存在脏读、不可重复读以及幻读问题。
我们通过这个隔离级别,搞懂这三个问题到底是怎么回事。
建表语句:
CREATE TABLE account (id INT NOT NULL AUTO_INCREMENT,name VARCHAR (50) NOT NULL,balance BIGINT NOT NULL,PRIMARY KEY (id),UNIQUE KEY idx_name (name)
) ENGINE = INNODB
预设两条数据,如下: zhangsan
和 zhaowu
两个用户,两个人的账户各有 1000 块。
下面通过模拟这两个用户之间的一个转账操作,借此分析这三个问题到底是怎么回事。
脏读
一个事务读到另外一个事务还没有提交的数据,称之为脏读。
具体操作如下:
- 首先打开两个 SQL 操作窗口,假设分别为 A 和 B,在 A 窗口中输入如下几条 SQL (输入完成后不用执行):
START TRANSACTION;
UPDATE account set balance=balance+100 where name='zhangsan';
UPDATE account set balance=balance-100 where name='zhaowu';
COMMIT;
- 在 B 窗口执行如下 SQL,修改默认的事务隔离级别为
READ UNCOMMITTED
,如下:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
- 接下来在 B 窗口中输入如下 SQL,输入完成后,首先执行第一行开启事务(注意只需要执行一行即可):
START TRANSACTION;
SELECT * FROM ACCOUNT;
COMMIT;
- 接下来执行 A 窗口中的前两条 SQL,即开启事务,给
zhangsan
这个账户添加 100 元。 - 进入到 B 窗口,执行 B 窗口的第二条查询 SQL(
SELECT * FROM ACCOUNT;
),结果如下:
可以看到,A 窗口中的事务,虽然还未提交,但是 B 窗口中已经可以查询到数据的相关变化了。
这就是脏读问题。
不可重复读
不可重复读是指一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
具体操作步骤如下(操作之前先将两个账户的钱都恢复为1000):
- 首先打开两个查询窗口 A 和 B ,并且将 B 的数据库事务隔离级别设置为
READ UNCOMMITTED
。 - 在 B 窗口中输入如下 SQL,然后只执行前两条 SQL 开启事务并查询
zhangsan
的账户:
START TRANSACTION;
SELECT * FROM ACCOUNT WHERE NAME='zhangsan';
COMMIT;
- 在 A 窗口中执行如下 SQL,给
zhangsan
这个账户添加 100 块钱,如下:
START TRANSACTION;
update account set balance=balance+100 where name='zhangsan';
COMMIT;
- 再次回到 B 窗口,执行 B 窗口的第二条 SQL 查看
zhangsan
的账户,结果如下:
zhangsan
的账户已经发生了变化,即前后两次查看 zhangsan
账户,结果不一致,这就是不可重复读。
不可重复读和脏读的区别在于:脏读是看到了其它事务未提交的数据,而不可重复读是看到了其它事务已经提交的数据(由于当前 SQL 也是在事务中,因此有可能并不想看到其它事务已经提交的数据)。
幻读
幻读和不可重复读非常像。幻读指的,一个事务里面,后一个请求看到的比之前相同请求看到的,多了记录出来。 幻读仅专指「新插入的行」。
我来举一个简单例子。
在 A 窗口中输入如下 SQL:
START TRANSACTION;
insert into account(name,balance) values('wangliu',1000);
COMMIT;
然后在 B 窗口输入如下 SQL:
START TRANSACTION;
SELECT * from account;
delete from account where name='wangliu';
COMMIT;
执行步骤如下:
- 首先打开两个查询窗口 A 和 B ,并且将 B 的数据库事务隔离级别设置为
READ UNCOMMITTED
。 - 执行 B 窗口的前两行,开启一个事务,同时查询数据库中的数据,此时查询到的数据只有
zhangsan
和zhaowu
。 - 执行 A 窗口的前两行,向数据库中添加一个名为
wangliu
的用户,注意不用提交事务。 - 执行 B 窗口的第二行,由于脏读问题,此时可以查询到
wangliu
这个用户。 - 执行 B 窗口的第三行,去删除 name 为
wangliu
的记录,这个时候删除就会出问题,虽然在 B 窗口中可以查询到wangliu
,但是这条记录还没有提交,是因为脏读的原因才看到了,所以是没法删除的。此时就产生了幻觉,明明有个wangliu
,却无法删除。
这就是幻读。
看了上面的案例,大家应该明白了脏读、不可重复读以及幻读各自的含义了。
READ_COMMITTED
和 READ UNCOMMITTED
相比,READ COMMITTED
主要解决了脏读的问题,对于不可重复读和幻读则未解决。
将事务的隔离级别改为 READ COMMITTED
之后,重复上面关于脏读案例的测试,发现已经不存在脏读问题了;重复上面关于不可重复读案例的测试,发现不可重复读问题依然存在。
上面那个案例不适用于幻读的测试,我们换一个幻读的测试案例。
继续两个窗口 A 和 B,将 B 窗口的隔离级别改为 READ COMMITTED
,然后在 A 窗口输入如下测试 SQL:
START TRANSACTION;
insert into account(name,balance) values('wangliu',1000);
COMMIT;
在 B 窗口输入如下测试 SQL:
START TRANSACTION;
SELECT * from account;
insert into account(name,balance) values('wangliu',1000);
COMMIT;
执行步骤如下:
- 首先执行 B 窗口的前两行 SQL,开启事务并查询数据,此时查到的只有 zhangsan 和 zhaowu 两个用户。
- 执行 A 窗口的前两行 SQL,插入一条记录,但是并不提交事务。
- 执行 B 窗口的第二行 SQL,由于现在已经没有了脏读问题,所以此时查不到 A 窗口中添加的数据。
- 执行 B 窗口的第三行 SQL,由于 name 字段唯一,因此这里会无法插入。此时就产生幻觉了,明明没有 wangliu 这个用户,却无法插入 wangliu。
REPEATABLE_READ
和 READ COMMITTED
相比,REPEATABLE READ
进一步解决了不可重复读的问题,但是幻读则未解决。
REPEATABLE READ
中关于幻读的测试和上一小节基本一致,不同的是第二步中执行完插入 SQL 后记得提交事务。
由于 REPEATABLE READ
已经解决了不可重复读,因此第二步即使提交了事务,第三步也查不到已经提交的数据,第四步继续插入就会出错。
SERIALIZABLE
SERIALIZABLE
提供了事务之间最大限度的隔离,在这种隔离级别中,事务一个接一个顺序的执行,不会发生脏读、不可重复读以及幻读问题,最安全。
如果设置当前事务隔离级别为 SERIALIZABLE
,那么此时开启其它事务时,就会发生阻塞,必须等当前事务提交了,其它事务才能开启成功,因此前面的脏读、不可重复读以及幻读问题这里都不会发生。
事务隔离的实现
在 InnoDB 中事务隔离性是由「锁」来实现的。
首先说 READ UNCOMMITTED
,它是性能最好,也可以说它是最野蛮的方式,但是它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。
再来说 SERIALIZABLE
。读的时候加共享锁,也就是其它事务可以并发读,但是不能写。写的时候加排它锁,其它事务不能并发写也不能并发读。
最后说 READ COMMITTED
和 REPEATABLE READ
。这两种隔离级别是比较复杂的,既要允许一定的并发,又想要兼顾的解决问题。
为了实现可重复读,MySQL 采用了 MVCC (多版本并发控制) 的方式。
有关锁和MVCC的分析,我们在后面文章进行详细说明。
这里我们只需要知道,MVCC 只在 READ COMMITTED
和 REPEATABLE READ
这两个隔离级别下工作。
最后,「幻读」InnoDB 通过引入间隙锁的方式解决。
总结
本文我们分析了并发访问引发的三个问题:
- 脏读:脏读指的是读到了其它事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了不一定最终存在的数据,这就是脏读。
- 不可重复读:它对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其它事务的影响,比如其它事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
- 幻读:幻读则是针对数据插入(INSERT)操作来说的。
针对这三个问题,SQL 标准定义了四种隔离级别:
- 读未提交(READ UNCOMMITTED)
- 读提交 (READ COMMITTED)
- 可重复读 (REPEATABLE READ)
- 串行化 (SERIALIZABLE)
从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。
事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度。
最后我们简单介绍了下事务隔离的实现。
事务隔离性主要是由「锁」来实现的,为了解决可重复读,MySQL 采用了 MVCC (多版本并发控制) 的方式。
好了,有关事务隔离机制的就先介绍到这了,我们下篇见。
一文读懂 MySQL 事务隔离机制相关推荐
- 一文读懂MySQL事务锁、事务级别
锁 性能分:乐观(比如使用version字段比对,无需等待).悲观(需要等待其他事务) 乐观锁,如它的名字那样,总是认为别人不会去修改,只有在提交更新的时候去检查数据的状态.通常是给数据增加一个字段来 ...
- mysql 默认事务隔离级别_一文读懂MySQL的事务隔离级别及MVCC机制
回顾前文: <一文学会MySQL的explain工具> <一文读懂MySQL的索引结构及查询优化> (同时再次强调,这几篇关于MySQL的探究都是基于5.7版本,相关总结与结论 ...
- Mysql事务隔离机制
SQL隔离机制: 所谓隔离机制,指的是读与写之间的隔离,指的是在多事务并行的时候,A事务的读与B事务的写之间的隔离,也就是说B事务的写对A事务的可见性. 多事务并发运行的时候,同时读写一个数据,可能会 ...
- 一文读懂 MySQL Explain 执行计划
一.前言 上周老周的一个好朋友让我出一篇教你读懂 SQL 执行计划,和我另一位读者反馈的面试题如何排查慢 SQL 的强相关,索性先出一篇一文读懂 MySQL Explain 执行计划.Explain ...
- 一文彻底读懂MySQL事务的四大隔离级别
前言 之前分析一个死锁问题,发现自己对数据库隔离级别理解还不够深入,所以趁着这几天假期,整理一下MySQL事务的四大隔离级别相关知识,希望对大家有帮助~ 事务 什么是事务? 事务,由一个有限的数据库操 ...
- 一文读懂mysql主从复制机制
作为一个关系型数据库,MySQL内建地提供数据复制机制,这使得在使用时,可以基于其复制机制实现高可用架构等高级特性,从而使得MySQL无需借助额外的插件或其他工具就具备适用于生产环境.这是MySQL得 ...
- 一文读懂三种并发控制机制(封锁、时间戳、有效性确认,大量例子+证明)
文章目录 并发控制 概述 事务特性 定义 并发控制机制 串行调度和可串行调度 调度 串行调度 可串行化调度 事务和调度的记法 冲突可串行化 冲突 优先图 证明 使用锁的可串行化实现 锁 封锁调度器 两 ...
- 上个厕所的功夫,搞懂MySQL事务隔离级别,Java学习视频百度云盘
| 14 | 朱志鹏 | 男 | 25 | 技术1部 | 5000 | 看小说 | | 19 | 李昂 | 男 | 27 | 技术1部 | 7000 | 看片儿 | ±-±----------±--- ...
- 什么是MVCC,一文搞懂MySQL的MVCC机制
MVCC是什么 MVCC,即Multi-Version Concurrency Control (多版本并发控制).它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中 ...
- 从实际蹲坑中涨姿势之——彻底搞懂Mysql事务隔离级别
一.业务背景 最近在做一个类似于任务分发的平台,分为任务管理平台和作业机,任务管理平台负责接收作业机的请求,并为作业机分配任务,所有的任务都存在表t_task_info,其使用一个字段task_sta ...
最新文章
- SAP WM 业务部门Unplanned工单消耗导致WM层面单据异常问题之分析
- 【Leetcode】刷题的开始
- 多种IP网络技术的原理和特点
- JavaScript---事件详解
- 黄聪:走进wordpress 详细说说template-loader.php
- Kali Linux与Ubuntu的ssh服务
- 20165309 实验三 敏捷开发与XP实践
- HttpClient实现通过url下载文件
- MongoDB 之聚合函数查询统计
- nest.js 使用express需要提供多个静态目录的操作
- linux下文件打包、压缩详解
- 详细介绍如何在ubuntu20.04中安装ROS系统,超快完成安装(最新版教程)
- word中批量修改上角标、下角标
- 重温经典,续写传奇,迈巴赫S600改铱银色加铁灰色双拼喷漆
- 万里汇WorldFirst人民币提现,1天内到账,太快了!
- 冷血格斗场和热血格斗场
- 云计算技术基础【12】
- 8.千峰教育os与窗口控制与内存修改与语言----自制随堂笔记
- python少儿编程008:海龟绘图画出奥运五连环!
- 爬虫之scrapy框架的数据持久化存储/保存为scv,json文件