Mysql数据库中事物的四种隔离级别
发布日期:2021-10-02 15:13:29 浏览次数:2 分类:技术文章

本文共 4070 字,大约阅读时间需要 13 分钟。

目录


数据库的四种隔离级别


前言

首先要推荐这个数据库的文章,感觉写的真好:


简介

引用原文中关于数据库四种隔离级别的介绍。

现代数据库不会使用纯粹的隔离作为默认模式,因为它会带来巨大的性能消耗。SQL一般定义4个隔离级别:

  • 串行化(Serializable,SQLite默认模式):最高级别的隔离。两个同时发生的事务100%隔离,每个事务有自己的『世界』。

  • 可重复读(Repeatable read,MySQL默认模式):每个事务有自己的『世界』,除了一种情况。如果一个事务成功执行并且添加了新数据,这些数据对其他正在执行的事务是可见的。但是如果事务成功修改了一条数据,修改结果对正在运行的事务不可见。所以,事务之间只是在新数据方面突破了隔离,对已存在的数据仍旧隔离。

    举个例子,如果事务A运行”SELECT count(1) from TABLE_X” ,然后事务B在 TABLE_X 加入一条新数据并提交,当事务A再运行一次 count(1)结果不会是一样的。
    这叫幻读(phantom read)。

  • 读取已提交(Read committed,Oracle、PostgreSQL、SQL Server默认模式):可重复读+新的隔离突破。如果事务A读取了数据D,然后数据D被事务B修改(或删除)并提交,事务A再次读取数据D时数据的变化(或删除)是可见的。

    这叫不可重复读(non-repeatable read)。

  • 读取未提交(Read uncommitted):最低级别的隔离,是读取已提交+新的隔离突破。如果事务A读取了数据D,然后数据D被事务B修改(但并未提交,事务B仍在运行中),事务A再次读取数据D时,数据修改是可见的。如果事务B回滚,那么事务A第二次读取的数据D是无意义的,因为那是事务B所做的从未发生的修改(已经回滚了嘛)。

    这叫脏读(dirty read)。

多数数据库添加了自定义的隔离级别(比如 PostgreSQL、Oracle、SQL Server的快照隔离),而且并没有实现SQL规范里的所有级别(尤其是读取未提交级别)。

默认的隔离级别可以由用户/开发者在建立连接时覆盖(只需要增加很简单的一行代码)。


实验

看到这个介绍简直也是一头雾水。直接做实验来理解吧。


0#准备工作

建立一个balance表,里面有一列数据money,咱们就玩id=1这一行的money,初始值为2960。

然后打开两个远程登录的窗口,都打开MySQL数据库,开始实验。

从隔离级别低到高,分别是:读取未提交、读取已提交、可重复读、串行化。


1#读取未提交

  • 查看窗口1的会话隔离级别,发现MySQL的默认隔离级别是Repeatable read。
select @@tx_isolation;   
1

MySQL默认隔离级别

  • 设置会话隔离级别为实验的read uncommitted:
set session transaction isolation level read uncommitted;   
1

设置隔离级别

  • 在窗口1开始一个事务并查看money。
-- 窗口1begin;select money from balance where id = 1;   
1
2
3

这里写图片描述

  • 然后在窗口2开始一个事务并修改money。
-- 窗口2begin;update balance set money = money - 1000 where id = 1;select money from balance where id = 1;   
1
2
3
4

这里写图片描述

可见在窗口2这个事务中money已经被修改,但是还没提交。

  • 回到窗口1,再查看money
-- 窗口1select money from balance where id = 1;   
1
2

这里写图片描述

可见,虽然窗口2的事务还没提交,但是窗口1的事务已经可以读到还没提交的数据,所以这就叫做 读取未提交 。可以看到,两个事务的隔离性很低,这是四种隔离级别中最低的级别。

  • 那么,如果窗口2的事务发生错误,将数据回滚,money变回原来的值,实际上money不应该发生变化,可是咱们的窗口1的事务还是读到了 错误回滚前 的1960,这就叫 脏读
-- 窗口2rollback;select money from balance where id = 1;commit;   
1
2
3
4

这里写图片描述

money变回2960了。

  • 所以,如果窗口2的事务代表转账,money从2960转走1000变为1960,然后转账出错,回滚回2960。
  • 数据库的隔离级别如果是read uncommitted的话,其他的事务(窗口1 的事务)就有可能再中间读到1960这个错误值。这就叫 脏读 啊亲。

2#读取已提交

  • 先将窗口1的事务结束掉(commit),然后设置隔离级别为read committed。再开始一个新事务,读取money。
-- 窗口1commit;set session transaction isolation level read committed;begin;select money from balance where id = 1;   
1
2
3
4
5

这里写图片描述

  • 窗口2开始新事务修改money
-- 窗口2begin;update balance set money = money - 1000;select money from balance where id = 1;   
1
2
3
4

这里写图片描述

  • 回到窗口1,再读money
-- 窗口1select money from balance where id = 1;   
1
2

这里写图片描述

  • 哈哈,这回窗口1中的money没被修改了吧。

  • 然后将窗口2的修改提交。

-- 窗口2commit;select money from balance where id = 1;   
1
2
3

这里写图片描述

  • 回到窗口1再读money,可以读到已提交的money了。
-- 窗口1select money from balance where id = 1;commit;   
1
2
3

这里写图片描述

  • 窗口1的事务可以读到窗口2的已提交的事务,这就叫 读取已提交

3#可重复读

  • 所以如果要再提高隔离性,那是怎么样呢?那就是窗口2的事务就算提交了数据修改,我窗口1的事务也不管,还是读取到原来的数据。
-- 窗口1-- 设置隔离级别为repeatable readset session transaction isolation level repeatable read;begin;select money from balance where id = 1;   
1
2
3
4
5

这里写图片描述

-- 窗口2-- 修改money并提交begin;update balance set money = money - 1000;select money from balance where id = 1;commit;   
1
2
3
4
5
6
7

这里写图片描述

-- 窗口1-- 窗口1的事务还是视而不见。select money from balance where id = 1;   
1
2
3
4

这里写图片描述

  • 窗口2的事务已经提交了,数据库的数据money已经真正被修改了,可是窗口1的事务还是视而不见,仍然隔离了,读取的数值仍然不变,重复读的数值不会变,这就是 可重复读

4#串行化

  • 难道还有更变态的更强的隔离级别,答案是肯定的,那就是 串行化 。串行化是怎么再增强隔离性的呢?回到一开始文章中对 可重复读串行化 的解释。
  • 可重复读:

    每个事务有自己的『世界』,除了一种情况。如果一个事务成功执行并且添加了新数据,这些数据对其他正在执行的事务是可见的。但是如果事务成功修改了一条数据,修改结果对正在运行的事务不可见。所以,事务之间只是在新数据方面突破了隔离,对已存在的数据仍旧隔离。

  • 串行化:

    最高级别的隔离。两个同时发生的事务100%隔离,每个事务有自己的『世界』。

  • 也就是说,如果是在可重复读的情况下,插入新数据这个事情是没有被隔离的,但是在串行化的情况下,插入新数据也被隔离了。好吧,还是很抽象,还是实验最好。

-- 窗口1-- 先是继续用可重复读的隔离级别。begin;select count(*) from balance;   
1
2
3
4
5

这里写图片描述

好的,表里有579条数据。然后我们在窗口2的事务中插入新数据。

-- 窗口2-- 在窗口2的事务中插入新数据。begin;insert into balance (money) values (999);commit;select count(*) from balance;   
1
2
3
4
5
6

这里写图片描述

好了,数据表中的记录数已经达到了580条。那么回到窗口1,按照可重复读的定义,应该是580条记录,然后串行化的设置才是579条记录。可是!

这里写图片描述

  • 真是万万没想到啊,这不是打自己脸吗,啪啪响。上网搜了一下,找到这么一句话:

REPEATABLE READ:在mysql中,不会出现幻读。mysql的实现和标准定义的RR隔离级别有差别。

好吧有兴趣的同学可以看看这位大神的详细解释:。

转载地址:https://blog.csdn.net/jinpengncu/article/details/79824993 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:windows下安装和配置多个版本的JDK
下一篇:java中实现多线程的三种方式(有/无返回值)

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月22日 14时08分20秒