前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【MySQL】MySQL解决事务问题:事务隔离机制

【MySQL】MySQL解决事务问题:事务隔离机制

作者头像
硬核项目经理
发布2024-04-12 14:16:08
1460
发布2024-04-12 14:16:08
举报

MySQL解决事务问题:事务隔离机制

在了解完事务可能出现的问题之后,我们就来学习数据库系统中为了解决这些问题所提供的策略,那就是 事务隔离机制 。其实从名字中就可以看出来,这个功能的主要作用就是隔离不同的事务,从而达到最终的事务一致性。

事务隔离级别

为了解决脏读、不可重复读、幻读问题,SQL标准规范中定义了4个事务的隔离级别,不同的隔离级别对事务的处理不同。它们分别是:

  • Read Uncommitted 未提交读(未授权读),允许脏读,但不允许更新丢失(上回我们没讲,因为这个是最基本的),也可以通过“排他写锁”实现。
  • Read Committed 已提交读(授权读取),允许不可重复读,但不允许脏读,也可以通过“瞬间共享读锁”和“排它写锁”实现。
  • Repeatable Read 可重复读,禁止不可重复读和脏读,但可能出现幻读。
  • Serializable 序列化,要求事务序列化执行,不能并发执行,杜绝全部问题。

上面四个级别是从低到高的,越是高级的事务隔离级别,对于数据的完整和一致性越能进行有效的保证,但是,越高的级别并发性能越差。从定义中就可以看出,在序列化级别,基本就是舍弃了并发能力,让事务像普通的单线程程序流一样一个一个的执行。显然,这样的隔离级别对性能的影响还是非常大的。

在 MySQL 以及大部分的数据库应用中,默认的事务隔离级别都是 Repeatable Read ,也就是可重复读。注意,在定义中,可重复读是“可能”出现幻读,它不是百分百保证,但大部分情况下我们使用这个隔离级别就足够了,毕竟,它还是能保证不错的并发性能的。

如果你的数据库中事务操作并不多,也可以将事务隔离级别再降低为 Read Committed ,这个并发性能更好,但有重复读的问题。再往下的话,也就是你的数据库是完全不需要考虑事务隔离级别的,那么其实你也可以考虑直接使用 MyISAM 完全放弃事务相关的操作。当然,InnoDB 也不止是事务这一块和 MyISAM 有不同,这个我们可以根据业务情况再进行详细的选择。

解决上篇文章中的问题

还记得我们在上篇文章中一上来就设置了一个参数吧。

代码语言:javascript
复制
-- my.cnf
[server]
transaction-isolation = READ-UNCOMMITTED

在 MySQL 中,就是通过 transaction-isolation 这个系统变量来设置事务的隔离级别的。我们可以直接使用上面的英文名称来设置,可以不在乎大小写,但中间的分隔符号一定是一个中划线。

我们一级一级向上设置,先设置为 Read Committed 。

代码语言:javascript
复制
-- my.cnf
[server]
transaction-isolation = READ-Committed

然后进行之前我们测试过的脏读问题。

代码语言:javascript
复制
-- 事务A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test_user set username = 'aaa' where id = 2199993;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 事务B
mysql> select username from test_user where id = 2199993;
+----------+
| username |
+----------+
| bbb      |
+----------+

可以看到,脏读问题已经解决了,剩下的其实不用我再多说了,在当前 已提交读 的级别下试试不可重复读。肯定还是失败的,这时我们就可以将事务隔离级别再升级到 重复读 Repeatable Read 级别,然后再次测试 不可重复读 的问题。

代码语言:javascript
复制
-- 事务B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select username from test_user where id = 2199993;
+----------------------+
| username             |
+----------------------+
| aaa                  |
+----------------------+
1 row in set (0.00 sec)

-- 事务A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test_user set username = 'bbb' where id = 2199993;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 1 row affected (0.00 sec)

-- 事务B
mysql> select username from test_user where id = 2199993;
+----------+
| username |
+----------+
| aaa      |
+----------+
1 row in set (0.00 sec)

可以看到不可重复读的问题也被解决了,事务B 前后两次读取的数据结果是一致的,都是在 事务A 提交之前的原始数据。

在 重复读 Repeatable Read 这个级别下,你可以再试试我们上篇文章中幻读的例子,你会发现幻读的问题也不再出现了。其实这就是说明,重复读 Repeatable Read 其实是可以解决基本幻读问题的,但并不是全部的幻读问题,比如说如果涉及到更新操作。

代码语言:javascript
复制
-- tt表就一个 c 字段,varchar类型
-- 事务B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_user;
+------+
| c    |
+------+
| 我   |
+------+
1 row in set (0.01 sec)

-- 事务A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test_user values('您');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

-- 事务B
mysql> select * from test_user;
+------+
| c    |
+------+
| 我   |
+------+
1 row in set (0.00 sec)

-- 上面查询没问题,虽然事务A新增加了一条数据,但是查询出来的还是一条数据,没有发生幻读,但是如果我们在事务B中再更新一下
mysql> update test_user set username='他' where username='您';
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

看出来问题了吗?有两条数据受到了影响,也就是说,两条数据被修改了。在事务B中,我们期望的结果其实应该是只有前面查出的那一条数据发生了修改,但是在修改之前,事务A插入了新的数据,于是两条数据被修改了。

这个例子比较极端,因为没有任何 WHERE 条件,但是,如果涉及到真实的业务开发,也会有不只是针对主键或唯一索引的批量修改操作,这种情况下,WHERE 条件也可能是多条数据的,这样就会出现幻读问题。

当然,这种问题和业务条件还是有关系的,比如我们可以在 WHERE 条件中再指定一个日期条件或者范围条件,保证新插入的数据不在更新的范围内。这样就可以在不改变事务隔离级别,也不用其它任何操作的情况下解决这种更新幻读问题。除了业务上的操作之外,最后一个隔离级别 序列化 Serializable 也可以解决这个问题。但是,它真的会完全撇弃并发,不信你拿上面的例子试试,在 事务B 没有提交之前,事务A 是会一直卡在 insert 操作上的,其实就是上了一个完全的排它读写锁,当前事务没有完成前,别的事务就根本不给写入操作的机会。

总结

对于事务这一块的内容我们就学习的差不多了,除了事务隔离级别之外,其实有一个 MVVC 的概念,它是基于乐观锁理论实现隔离级别的方式,用于 已提交读 和 可重复读 隔离级别的实现。在 重复读 的级别下,SELECT 会执行快照读,也就是以事务中第一个 SELECT 的结果为缓存,从而解决读取数据的幻读问题。但是,更新、添加、删除操作则不会走快照读,它们都需要知道最新的数据情况,所以 MVVC 和 重复读 无法解决这种情况下的 幻读 问题。这一块的内容大家可以再自行查阅相关的资料进行更深入的学习。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-07,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 码农老张 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MySQL解决事务问题:事务隔离机制
    • 事务隔离级别
      • 解决上篇文章中的问题
        • 总结
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com