【MySQL】MySQL事务隔离级别(读书笔记)

        什么是事务呢,简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事 务支持是在引擎层实现的。MySQL原生的MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代 的重要原因之一。

        这篇文章以InnoDB引擎为例。

隔离性与隔离级别

        提到事务,你肯定会想到ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一 致性、隔离性、持久性),今天我们就来说说其中I,也就是“隔离性”。

        当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non- repeatable read)、幻读(phantomread)的问题,为了解决这些问题,就有了“隔离级别”的概 念。

        事务隔离级别越高,效率就越低,所以我们要平衡隔离性和效率。SQL标准定义了四种隔离级别:

  • 读未提交:一个事务还没有提交,它的修改别的事务都可以看到。就像你还没交卷,别人都可以偷看你的答案一样。
  • 读提交:一个事务提交之后,它的修改才能被其他事务看到。就像你交了卷,别人才可以看你的答案。这避免了脏读。
  • 可重复读:同一个事务中,多次读取的数据是一致的,不会被其他事务改变。就像考试时你眼前的试卷,考完前内容不变。这避免了不可重复读。
  • 串行化:所有事务只能顺序执行,不可能并发,每次访问都会加锁。就像只有一个考场,学生必须依次入场考试,排队打字,完全隔离。

总结一下:

        级别越高,事务之间越独立,但是效率越低。读未提交隔离性最差,串行化隔离性最好。所以要根据需要在性能和隔离性间找到平衡。

        数据库会使用“视图”这种机制来实现事务的隔离。

  • 可重复读级别下,这个视图在事务开始时创建,就像电影开始时戴上3D眼镜,整场电影你看到的场景不会改变。
  • 读提交级别下,视图在每次查询前创建,就像看连续剧时,每集开始时重新戴上眼镜,只能看到最新剧情。
  • 读未提交级别没有视图,直接看数据库当前值,就像直接用肉眼看现实生活中的变化。
  • 串行化级别下用锁来完全隔离事务,就像购票进电影院要排队,直到你出来我才能进去。

        总之,数据库使用视图这种技术手段来实现不同程度的事务隔离。视图内容根据隔离级别的不同而有所变化。

        我想你可能会问那什么时候需要“可重复读”的场景呢?我们来看一个数据校对逻辑的案例。 假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时 候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。 你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。 这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务 更新的影响。

事务隔离的实现

这里我们展开说明“可重复 读”。

        

在MySQL里,数据的更新是这样实现可重复读隔离的:

        每条数据都像书页一样,数据库会同时留一份前一个版本的“草稿”。当你更新这条数据时,就像在书页上做修改一样,原来的内容并不会消失,还可以通过“撤销修改”这个操作来恢复。

        所以某个事务第一次读取到的数据,通过这种“撤销修改”就可以获取到原始版本,这样第二次读取就是一致的了。

        用通俗的说法,就是数据库会记录一个数据的修改历史,你每次查看的数据都是基于第一个版本,中间的更新对你不可见,这样就实现了可重复读。

        而这种通过“备份”然后“撤销”来读取旧版本数据的技术,就是数据库系统实现可重复读隔离的内部机制。

        基于上面的说明,我们来讨论一下为什么建议你尽量不要使用长事务。    

        长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数 据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占 用存储空间。

        在MySQL 5.5及以前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终 提交,回滚段被清理,文件也不会变小。我见过数据只有20GB,而回滚段有200GB的库。最终 只好为了清理回滚段,重建整个库。 除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库,这个我们会在后面讲锁的时候 展开。    

事务的启动方式

        使用事务可以确保数据库操作要么全部成功执行,要么全部失败回滚,这对保证数据一致性非常重要。但是长时间未提交的长事务会带来风险,可能导致存储空间占用、系统锁资源占用、阻塞其他操作等问题。所以我们要注意避免长事务的产生。

在MySQL中启动一个事务有两种主要方式:

  1. 显式地使用BEGIN或START TRANSACTION语句启动一个事务。这种方式启动的事务需要执行COMMIT显式提交,或ROLLBACK回滚。
  2. 设置autocommit=0,这样每条SQL语句都会自动启动一个单独的事务,需要手动提交。这种方式容易在长连接时不知不觉进入长事务状态。

        为明确事务的生命周期,我建议使用autocommit=1,且通过BEGIN语句显式启动事务,并及时提交或回滚。

        但是,对于需要频繁启动事务的场景,可使用“COMMIT AND CHAIN”语句,该语句提交当前事务的同时会自动开始一个新的事务,这样可以避免重复地书写BEGIN语句。

        如果需要查询检测数据库中长时间未提交的事务,可以在information_schema库的innodb_trx表中,使用时间条件过滤持续时间过长的事务记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值