有一个经典的例子:A给B转账100块,这个步骤分为两步,第一步是A的账户减少¥100,第二步是B的账户增加¥100。但是在第一、二步之间,如果发生一些意外,A的钱是不是白白地少了?因此,这里引入一个事务的概念。

事务就是保证一组数据库操作要么全部成功,要么全部失败。

事务的支持是在引擎层实现的,比如InnoDB就支持事务,而MyISAM不支持事务。

下面说说一些与事务有关的概念。

隔离性与隔离级别

说起事务,肯定会想起它有四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)

当数据库上有多个事务同时执行时,可能出现脏读、不可重复读、幻读这些问题,为了解决它们,就有了隔离级别的概念。

SQL标准的事务隔离级别包括:读未提交(read uncommited)、读已提交(read commited)、可重复读(repeatable read)和串行化(serializable)。这个级别按顺序依次提高,级别越高,效率越低,因此需要在效率与隔离级别间找到一个平衡。

下面说说几个隔离级别的意思:

  • 读未提交:一个事务还没提交时,它做的变更能被别的事务看到。
  • 读已提交:它做的变更只有在事务提交之后才能被别的事务看到。
  • 可重复读:一个事务执行过程中看到的数据,总是和这个事务启动时看到的数据是一致的。
  • 串行化:对于同一行记录,写操作会加“写锁”,读操作会加“读锁”,当出现锁冲突时,后访问的事务必须等待前一个事务执行完成,才能继续执行。

记得以前有学习过这个,还挺理解的,这里我不再作更多详细的描述,dddd。

在实现上,数据库会创建一个视图,访问时以视图的逻辑结果为准,在可重复读的隔离级别下,这个视图是在事务启动时创建的,整个事务期间都用这个视图。在读已提交的隔离级别下,这个视图是在每个SQL语句开始执行时创建的(就是每条sql开始工作前的视图,可以拿到最新提交的事务的修改元素??)。这里注意,读未提交直接返回记录上的最新值,没有视图的概念;而串行化直接使用加锁的方式避免访问。

事务隔离的实现

主要是对“可重复读”的事务隔离实现作更进一步的理解。
d9c313809e5ac148fc39feff532f0fee.png

有一个叫做read-view的东西,也就是上面说的视图了。它配合回滚段可以达成某个视图下仅能看到某种情况的数据。这就是数据库的多版本并发控制MVCC。比如视图A,它看到的值是1,B看到的值是2,C看到的值是4,它们之前互不干扰,即使又来一个事务D,把4改成5,也不影响,只要把它放入回滚链中。

这里似乎也会有一个问题,这种回滚日志保留到什么时候?答案是保留到不需要的时候,系统会判断,当没有事务需要用到这些回滚日志时,它们会被删除,也就是没有比这个回滚日志更早的read-view的时候。

因此,也可以发现,我们尽量不要用长事务,因为它会存储很多老的事务视图,导致占用大量的存储空间。

小疑问

两个RR级别的事务,操作同一个字段,会怎样呢?

比如,A、B分别开启事务,A对student表中id=6的学生姓名进行修改,B也对它进行修改,这时会发生什么?

似乎与行锁有关,会阻塞。
image20220120195047415.png

(从这里可以看到RR的视图,开启事务)

然后,我让右边也对其名字进行修改

image20220120195132340.png

“卡住了”!,这是加锁了。
image20220120195211040.png

A事务一提交,B这边马上更新成功!
image20220120195334048.png
最后结果是晚提交事务的B所修改的名字。

总的来说,就是A修改了id=6这行,然后B也想修改,但此时被A上了行锁,不能修改,因此只有当A提交后B才能提交,因此可以看到查询结果,先是查到名字为A name,后面被B改成B name。