三、MySQL事务和锁

InnoDB数据库引擎支持事务。事务具有ACID(原子性、一致性、隔离性和持久性),还有不同的隔离级别(具有不同的隔离性)。事务的隔离级别是同通过锁的机制来实现的。

锁在计算机中是协调多个进程或线程并发访问某一资源的一种机制。在数据库中,除了传统的计算资源(CPU、RAM、I/O等)争用之外,数据也是一种供许多用户共享访问的资源。数据库在进行并发访问的时候会自动对相应的对象进行加锁,以保证数据并发访问的一致性。InnoDB存储引擎既支持行级锁,也支持表级锁,但默认情况下采用行级锁。

一、MySQL事务概述

事务可以只包含一条SQL语句,也可以包含多条复杂的SQL语句。事务中的所有SQL语句被当作一个操作单元,换句话说,事务中的SQL语句被当作一个整体进行操作。

我们来看一个场景,这个场景就是“转账”。例如,“天都银行”有很多用户,目前A用户账上的余额为9000元,B用户账上的余额为4000元,现在A用户要向B用户转账2000元。当转账结束以后,A用户账户上的余额应该为7000元,B用户账户上的余额应该为6000元。

上述过程在数据库中应该转换为如下操作:

  • 操作1:修改A用户账户对应的余额记录,即9000-2000。
  • 操作2:修改B用户账户对应的余额记录,即4000+2000。

上述操作好像没有问题,如果数据库刚刚完成操作1,很不凑巧停电了,过了三分钟,来电了,当我们再次查看数据库时,会发现A用户余额为7000,比停电之前少2000,B用户的账户余额仍然为4000,与停电之前一样。出现这种情况是因为数据库只完成了操作1,而没有来得及完成操作2,2000元就凭空消失了。我们应该防止这样的惨剧发生,解决方法就是使用事务。

我们之前说过,事务中的所有SQL语句都被当作一个整体,要么全部执行成功,要么全部执行成功,要么在其中某些操作执行失败后回滚到最初状态,就好像什么都没有发生过一样。利用事务这个特性,就可以解决之前的问题。我们可以把转账的SQL语句写入到事务中,具体如下:

  • begin事务开始
  • update A用户余额-2000
  • update B用户余额+2000
  • commit提交事务(事务结束)

利用事务完成上述操作,即使数据库刚刚将A用户账户余额减去2000时停电了,由于事务的特性,当再次使用数据库时,也不会出现A用户余额变为7000、B用户余额仍然为4000的情况。为什么呢?事务其实和一个操作没有什么太大的区别,它是一系列数据库操作(可以理解为SQL)的集合,如果事务不具备原子性,就没有办法保证同一个事务中所有操作都被执行或者未被执行了,整个数据库系统就既不可用也不可信了。

想要保证事务的原子性,就需要在异常发生时对已经执行的操作进行回滚。在MySQL中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到回滚日志中,然后对数据库中的对应行进行写入。这个过程其实非常好理解,为了能够在发生错误时撤销之前的全部操作,肯定是需要将之前的操作都记录下来的,这样在发生错误时才可以回滚。回滚日志除了能够在发生错误或用户执行ROLLBACK时提供回滚相关的信息,还能够在整个系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘,是我们需要先写日志后写数据库的主要原因。

MySQL中,InnoDB存储引擎是支持事务的,而且完全符合ACID的特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  • 原子性:整个事务中的所有操作要么全部执行成功,要么全部执行失败后回滚到最初状态。
  • 一致性:数据库中的数据在事务操作前和事务处理后必须都满足业务规则约束,比如A和B账户总金额在转账前和转账后必须保持一致。
  • 隔离性:一个事务在提交之前所作出的操作是否能为其他事务可见。由于不同的场景需求不同,因此针对隔离性来说有不同的隔离级别

你可能感兴趣的:(MySQL进阶)