MySQL事务

现在的很多软件都是多用户,多程序,多线程的,对同一个表可能同时有很多人在用,为保持数据的一致性,所以提出了事务的概念。


事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  • 原子性(atomicity): 一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
  • 一致性(consistency): 事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation): 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability): 持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

我们之前在show engines查看数据库所支持的引擎时,就得知了只有InnoDB才支持事务,另外我们还可以使用 show variables like '%storage_engine%'; 来查看mysql当前默认的存储引擎。
MySQL事务



原子性(atomicity)

一个事务必须被视为一个不可分割的最小单元,整个事务中的所有操作要么全部提交成功,要么全部失败,对于一个事务来说,不可能只执行其中的一部分操作。


比如银行转账问题,账号A给账户B转账1000元,账户A减去1000元, B的账户就要加上1000元,这两个操作必须作为一个整体来执行,不然账户A扣钱了,而账户B没有加钱这种情况很难处理。



一致性(consistency)

一致性是指事务将数据库从一种一致性转换到另外一种一致性状态,在事务开始之前和事务结束之后数据库中数据的完整性没有被破坏。


还是我们银行转账问题,账号A给账户B转账1000元,账户A转账完之后,减少了1000元,账户B的账户上应该增加1000元,在这个过程中,其他访问数据的事务看到的应该是还没有进行转账或者已经成功转账后的情况,而不能访问到中间的那个状态,就是账号A已经减去了转账金额,但是账户B正准备增加金额,但是还没有加的情况。



持久性(durability)

一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,已经提交的修改数据也不会丢失。


但是我们需要注意的是,这并不是数据库的角度完全能解决,我们还应进行一些其他的处理如热备等。



隔离性(isolation)

隔离性要求一个事务对数据库中数据的修改,在未提交完成前对于其他事务是不可见的

  • 未提交读(READ UNCOMMITED)  脏读
  • 已提交读 (READ COMMITED)  不可重复读
  • 可重复读(REPEATABLE READ)  幻读
  • 可串行化(SERIALIZABLE)

MySQL默认的事务隔离级别为:可重复读(REPEATABLE READ),其他数据库大部分默认为已提交读 (READ COMMITED),我们可以通过 show variables like '%tx_isolation%'; 查看数据库事务的默认级别,另外可以通过 set global transaction isolation level xxx 来设置系统的事务隔离级别,也可以通过 set session transaction isolation level xxx 来设置会话的事务隔离级别。
MySQL事务


事务并发问题

  • 脏读: 事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
  • 不可重复读: 事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
  • 幻读: 系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

其中不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。



脏读

下面我们以例子进行介绍,首先我们先来看一下什么是脏读,我们将可以使用 set session transaction isolation level read uncommitted; 将一个会话事务隔离级别设施为未提交读
MySQL事务

现在我们开始两个会话,全部设置为未提交读,然后进行操作,我们在一个会话中开启一个事务,并且将其第一条记录的金额减去1000
MySQL事务

但是我们上述还未提交事务,然后我们在另一个会话中进行查看,发现我们上述事务还未提交,我们其他的会话就可以查询到该结果,但是万一之前的事务不进行提交,而是回滚,这里我们查询到的数据就是脏数据。
MySQL事务


不可重复读

这里为了解决脏读的问题,我们使用 set session transaction isolation level read committed; 将事务级别修改为已提交读
MySQL事务

然后我们再去另一个会话中进行查询,不同的时候我们在查询去开启了一个事务,是为了演示不可重复读的问题
MySQL事务

上述我们发现我们查询到的还是5000,和之前未提交读的事务级别查询到的4000是不同的,就避免了脏读的问题,那么不可重复读是什么意思呢?

这里我们直接将之前的减去1000的事务进行提交,然后在查询,如下:
MySQL事务

然后我们再去另一个会话的事务中进行查询,发现其查询的结果发生了变化,但是我们这个事务还没有结果,在这个事务之中查询的结果应该保持一致。
MySQL事务



这里为了解决不可重复读的问题,我们使用 set session transaction isolation level repeatable read; 将事务级别修改为可重复读,然后再进行测试
MySQL事务
MySQL事务

然后我们提交事务后,再进行查询
MySQL事务
MySQL事务

发现查询还是原来的值,然后我们将该事务进行结束,再进行查询
MySQL事务


幻读

我们同样来演示一个例子来解释幻读的概念,上述account表中只有一条信息,现在我们就去查询,如果只有一条记录,我们就新增一条,否则就不进行操作。
MySQL事务

查询发现只有一条记录,那么我们进行新增一条新的记录
MySQL事务

这里我们还未提交事务,然后我们再开一个新的会话,做同样的查询,如果只有一条记录则新增
MySQL事务
MySQL事务

然后我们将两个事务都进行提交,在进行查询,发现数据变成了3条,这就是所谓的幻读
MySQL事务



为了解决所谓的幻读,我们可以使用 set session transaction isolation level serializable; 将事务的隔离级别调整为可串行化
MySQL事务
MySQL事务

然后我们在另一会话中进行同样的操作,发现在查询的时候,就无法查询了,一直在等待,因为事务隔离级别为可串行化时,读写数据都会锁住整张表
MySQL事务



事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。




另外我们来看一下事务的语法

  • 开启事务
    1. begin
    2. start transaction
    3. begin work
  • 事务回滚   rollback
  • 事务提交   commit
  • 还原点   savepoint

最后我们来演示一下还原点的使用,首先看一下表中的数据
MySQL事务

然后执行下列语句,如下:
MySQL事务

这时再进行查询
MySQL事务

然后我们进行回滚,回滚至 s2 点,然后再进行查询:
MySQL事务
MySQL事务