MySQL 事务

155 阅读6分钟

最经典的例子就是转账:假如说你要给别人转 100 块钱,简单点就两步操作:

(1)你的余额减 100

(2)他的余额加 100

假如没有事务,(1) 执行完了后,系统挂了,这样就会造成你的钱扣了,但是他的钱并没有增加,就凭空少了 100 块。

简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的。

InnoDB 引擎支持事务,MyISAM 引擎不支持事务,这也就是 MyISAM 引擎为什么逐渐被 InnoDB 所替代的一个很重要的原因。

整体大纲

image.png

事务的特性

(1)原子性(atomicity)

一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作

(2)一致性(consistency)

事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态

如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态。

(3)隔离性(isolation

在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰

不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。

(4)持久性(durability

一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中

即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态。

MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性

MySQL InnoDB 引擎通过 锁机制MVCC 等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ )。

保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障

隔离性与隔离级别

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了隔离级别的概念。

你隔离得越严实,效率就会越低。

SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。

  • 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

(1)在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。

(2)在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。

需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。

Oracle 数据库的默认隔离级别其实就是读提交MySQL 的默认隔离级别是可重复读

什么时候需要“可重复读”的场景呢

假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。

事务隔离的实现

MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制MVCC)。

系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。

事务的启动方式

  1. 显式启动事务语句, beginstart transaction。配套的提交语句是 commit,回滚语句是 rollback

  2. 关闭自动提交。set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commitrollback 语句,或者断开连接。

视图

MySQL 里,有两个“视图”的概念:

  • 一个是 view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,而它的查询方法与表一样。
  • 另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RCRead Committed,读提交)和 RRRepeatable Read,可重复读)隔离级别的实现。

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。