1月阅读周·MySQL数据库入门:事务的隔离级别之脏读篇

举报
叶一一 发表于 2025/01/24 11:37:16 2025/01/24
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十二个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScr...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十二个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》、《JavaScript异步编程设计快速响应的网络应用》、《编写可测试的JavaScript代码

当前阅读周书籍MySQL数据库入门

事务的隔离级别

数据库是多线程并发访问的,所以很容易出现多个线程同时开启事务的情况,这样就会出现脏读、重复读以及幻读的情况,为了避免这种情况的发生,就需要为事务设置隔离级别。在MySQL中,事务有4种隔离级别,接下来将针对这4种隔离级别进行详细的讲解。

1.READ UNCOMMITTED

READ UNCOMMITTED(读未提交)是事务中最低的级别,该级别下的事务可以读取到另一个事务中未提交的数据,也被称为脏读(Dirty Read),这是相当危险的。由于该级别较低,在实际开发中避免不了任何情况,所以一般很少使用。

2.READ COMMITTED

大多数的数据库管理系统的默认隔离级别都是READ COMMITTED(读提交)(如Oracle),该级别下的事务只能读取其他事务已经提交的内容,可以避免脏读,但不能避免重复读和幻读的情况。重复读就是在事务内重复读取了别的线程已经提交的数据,但两次读取的结果不一致,原因是查询的过程中其他事务做了更新的操作。幻读是指在一个事务内两次查询中数据条数不一致,原因是查询的过程中其他的事务做了添加操作。这两种情况并不算错误,但有些情况是不符合实际需求的,后面会具体讲解。

3.REPEATABLE READ

REPEATABLE READ(可重复读)是MySQL默认的事务隔离级别,它可以避免脏读、不可重复读的问题,确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。但理论上,该级别会出现幻读的情况,不过MySQL的存储引擎通过多版本并发控制机制解决了该问题,因此该级别是可以避免幻读的。

4.SERIALIZABLE

SERIALIZABLE(可串行化)是事务的最高隔离级别,它会强制对事务进行排序,使之不会发生冲突,从而解决脏读、幻读、重复读的问题。实际上,就是在每个读的数据行上加锁。这个级别,可能导致大量的超时现象和锁竞争,实际应用中很少使用。

上述的4种级别可能会产生不同的问题,如脏读、重复读、幻读、耗时的操作等。

下面着重介绍脏读。

脏读

所谓的脏读就是指一个事务读取了另外一个事务未提交的数据。试想一下,a账户要给b账户转账100元购买商品,如果a账户开启了一个事务,执行了下面的UPDATE语句做了转账的工作。

UPDATE account SET money=money-100 WHERE name='a';
UPDATE account SET money=money+100 WHERE name='b';

如果a账户先不提交事务,通知b账户来查询,由于b的隔离级别较低,此时就会读到a事务中未提交的数据,发现a确实给自己转了100元,然后给a发货,等b发货成功后a就将事务回滚,此时,b就会受到损失,这就是脏读造成的。

为了演示上述情况,首先需要开启两个命令行窗口(相当于开启两个线程),分别模拟a账户和b账户,然后登录到MySQL数据库,并将操作的数据库切换为chapter06,如图1-1和图1-2所示。

图1-1 a账户

1-1.jpg

图1-2 b账户

1-2.jpg

需要注意的是,下面所有的操作都是在这两个窗口中进行的,为了方便查看代码,将这两个窗口中的操作代码直接复制出来。

1、设置b账户中事务的隔离级别

大家都知道MySQL的默认隔离级别是REPEATABLE READ(可重复读),该级别是可以避免脏读的,因此需要将b账户中事务的隔离级别设置为READ UNCOMMITTED(读未提交),具体语句如下:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

上述语句中,SESSION表示当前会话,TRANSACTION就表示事务,ISOLATION表示隔离,LEVEL表示级别,READ UNCOMMITTED表示当前的隔离级别,该语句执行成功后,使用SELECT语句查询事务的隔离级别,结果如下:

mysql>SELECT @@tx_isolation;
+--------------------+
| @@tx_isolation   |
+--------------------+
| READ-UNCOMMITTED |
+--------------------+
1 row in set (0.00 sec)

从上述结果可以看出,b账户事务的隔离级别已经被修改为READ UNCOMMITTED,接下来就可以演示脏读的情况。

2、演示脏读

b账户:为了证明出现脏读的情况,首先在b账户中开启一个事务,并在该事务中查询当前账户的余额信息,查询结果如下:

mysql>START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql>SELECT * FROM account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 |   a     |  1000 |
|  2 |   b     |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)

a账户:在a账户中开启一个事务,并在当前窗口中执行转账功能,具体语句如下:

START TRANSACTION;
UPDATE account SET money=money-100 WHERE name='a';
UPDATE account SET money=money+100 WHERE name='b';

需要注意的是,此时不要提交事务,如果提交事务就无法演示出现脏读的情况。

b账户:a账户执行完转账语句后,b账户查询当前账户,此时的查询结果如下:

mysql>SELECT * FROM account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 |   a     |     900 |
|  2 |   b     |  1100 |
+----+------+-------+
2 rows in set (0.00 sec)

从查询结果可以看出,a账户已经成功给b账户转账了100元钱,这是由于b账户的事务隔离级别较低,因此读取了a账户中还没有提交的内容,出现了脏读的情况,这时b误以为a账户已经转账成功了,便会给a发货,当b发货后a如果不提交事务将事务回滚,此时b就会受到损失。上述情况演示完,最后还需将a账户中的事务回滚,将b账户中的事务提交。

3、设置b账户中事务的隔离级别

为了防止脏读发生,可以将b账户中事务的隔离级别设置为READ COMMITED(读提交),该级别可以避免脏读,具体语句如下:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

上述语句执行成功后,b账户的隔离级别已经被设置为READ COMMITTED。

4、验证是否出现脏读

b账户:为了说明没有出现脏读的情况,首先要在b账户中开启一个事务,并在该事务中查询各账户的余额信息,查询结果如下:

mysql>START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql>SELECT * FROM account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 |   a     |  1000 |
|  2 |   b     |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)

a账户:在a账户中重新开启一个事务,实现转账功能,具体语句如下:

START TRANSACTION;
UPDATE account SET money=money-100 WHERE name='a';
UPDATE account SET money=money+100 WHERE name='b';

b账户:当a账户转账成功后,可以在b账户中再次查询各账户的余额信息,查询结果如下:

mysql>SELECT * FROM account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 |   a     |  1000 |
|  2 |   b     |  1000 |
+----+------+-------+
2 rows in set (0.00 sec)

通过对比两次查询结果可以发现,b账户在同一个事务中的查询结果是一致的,并没有查询到a账户中未提交的内容,因此可以说明READ COMMITTED隔离级别可以避免脏读。最后分别将a账户中的事务和b账户中的事务回滚。

总结

数据库是多线程并发访问的,所以很容易出现多个线程同时开启事务的情况,这样就会出现脏读、重复读以及幻读的情况,为了避免这种情况的发生,就需要为事务设置隔离级别。在MySQL中,事务有4种隔离级别,分别是READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

这4种级别可能会产生不同的问题,如脏读、重复读、幻读、耗时的操作等。

所谓的脏读就是指一个事务读取了另外一个事务未提交的数据。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。