五分钟搞懂MySQL事务、ACID特性保证以及并发事务可能带来的问题

392 阅读7分钟

MySQL事务

事务是逻辑上的一组操作,要么都执行,要么都不执行。

举个通俗的例子:

有A和B两个人有转账往来,两人此时都有100。

A向B转账50,数据库层面的实现就是A先减少50,然后B增加50。

如果先执行了A减少50的操作,然后由于各种原因数据库服务重启或者宕机导致操作中断,这就会导致数据丢失。

为了保证数据的一致性,系统必须能够处理这些问题。事务就是我们抽象出来简化这些问题的首选机制。

所有关系型数据都有ACID的特性

ACID

  • 原子性Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

  • 一致性Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

  • 隔离性Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

  • 持久性Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

需要注意的是,原子性,隔离性,持久性的目的就是为了一致性。

ACID特性是如何保证的?

从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。

隔离性的保证

先说说隔离性,首先是四种隔离级别。

隔离级别

  • 读未提交

    • 一个事务还没提交时,它做的变更就能被别的事务看到
  • 读提交

    • 一个事务提交之后,它做的变更才会被其他事务看到
  • 可重复读

    • 一个事务中,对同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。InnoDB默认级别。
  • 串行化

    • 事务串行化执行,每次读都需要获得表级共享锁,读写相互都会阻塞,隔离级别最高,牺牲系统并发性。

不同的隔离级别是为了解决不同的问题。也就是脏读、幻读、不可重复读。

  • 脏读:有回滚

一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。


事务A:a=20--->a-1=19--->回滚

事务B:在回滚之前读取之后进入 对数据进行读取 此时读取a=19

但是此事务A已经回滚了 a=20 所以事务B读取的a=19是错误的 这就是脏读

  • 丢失修改:没有回滚

在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。


事务A,B同时进入数据读取数据 都是a=20

然后A先进行修改a-1=19 B后进行修改a-1=19 两者都顺利提交事务

但是A先修改的结果丢失了 这就是丢失修改

  • 不可重复度:没有回滚

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。


指的是一个事务对数据库多次查询 导致的两次读取不一样

事务A,B同时进入数据库读取数据a=20,

然后A对数据库修改a-1=19, 此时B再次读取数据a=19

两次读取数据不一致

  • 幻读

幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。


两个事务AB同时进入,

B在查询的时候,A插入了新的数据

此时B发现了一些原本不存在的记录

保证隔离性

有以下的方法可以保证隔离性

  • MVCC

原子性的保证

接着说说原子性。前文有提到 undo log ,回滚日志。隔离性的MVCC其实就是依靠它来实现的,原子性也是。实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。

当事务对数据库进行修改时,InnoDB会生成对应的 undo log;如果事务执行失败或调用了 rollback,导致事务需要回滚,便可以利用 undo log 中的信息将数据回滚到修改之前的样子。undo log 属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB 会根据 undo log 的内容做与之前相反的工作:

对于每个 insert,回滚时会执行 delete;

对于每个 delete,回滚时会执行insert;

对于每个 update,回滚时会执行一个相反的 update,把数据改回去。

以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。

持久性的保证

持久性保证了事务提交后,其对数据库的修改将永久保存,即使系统遇到故障,也可以通过日志等方式将数据恢复到事务提交前的状态。

实现MySQL持久性的方法包括以下几种:

  • 将所有更改记录到日志中,以便系统故障时可以恢复。
  • 使用Write-Ahead Logging(WAL)技术,即先将更改写入日志中,再写入磁盘(通常是SSD)。如果系统故障,可以使用日志中的数据进行恢复。
  • 使用预写日志(write-ahead-log,WAL)技术,即每次发生更改时都先写入日志,等到系统空闲时再将更改写入磁盘。
  • 使用数据镜像(data mirroring),即在多个磁盘上创建相同的数据镜像。如果其中一个磁盘故障,可以从其他磁盘上的镜像中进行恢复。

总之,MySQL通过持久性机制确保了数据的可靠性和可恢复性,使得数据即使在系统故障的情况下也不会丢失。这是保证数据库安全和稳定运行的重要手段。