有一个经典的例子: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开始工作前的视图,可以拿到最新提交的事务的修改元素??)。这里注意,读未提交直接返回记录上的最新值,没有视图的概念;而串行化直接使用加锁的方式避免访问。
事务隔离的实现
主要是对“可重复读”的事务隔离实现作更进一步的理解。
有一个叫做read-view的东西,也就是上面说的视图了。它配合回滚段可以达成某个视图下仅能看到某种情况的数据。这就是数据库的多版本并发控制MVCC。比如视图A,它看到的值是1,B看到的值是2,C看到的值是4,它们之前互不干扰,即使又来一个事务D,把4改成5,也不影响,只要把它放入回滚链中。
这里似乎也会有一个问题,这种回滚日志保留到什么时候?答案是保留到不需要的时候,系统会判断,当没有事务需要用到这些回滚日志时,它们会被删除,也就是没有比这个回滚日志更早的read-view的时候。
因此,也可以发现,我们尽量不要用长事务,因为它会存储很多老的事务视图,导致占用大量的存储空间。
小疑问
两个RR级别的事务,操作同一个字段,会怎样呢?
比如,A、B分别开启事务,A对student表中id=6的学生姓名进行修改,B也对它进行修改,这时会发生什么?
似乎与行锁有关,会阻塞。
(从这里可以看到RR的视图,开启事务)
然后,我让右边也对其名字进行修改
“卡住了”!,这是加锁了。
A事务一提交,B这边马上更新成功!
最后结果是晚提交事务的B所修改的名字。
总的来说,就是A修改了id=6这行,然后B也想修改,但此时被A上了行锁,不能修改,因此只有当A提交后B才能提交,因此可以看到查询结果,先是查到名字为A name,后面被B改成B name。