事务:基础篇
事务的主要内容可以从以下几个问题来解决:
- 什么是事务?为什么需要事务?如何开启事务呢?
- 事务有哪些特性呢?为什么需要这些特性呢?这些特性是如何实现的?
- 事务的隔离性有哪些隔离级别?不同的隔离级别分别可以解决哪些问题?
- 隔离性具体是如何实现的?锁机制如何实现?MVCC 如何实现?
事务的所有内容基本都包含在这些问题中,相比于索引来说会更加重要
注:这里是按照 MySQL 中的事务来讲述的,其他数据库中的事务可能存在些微出入
初识事务
简单来说事务就是一组数据库操作或者说一组 SQL 集合,这组 SQL 集合要么全部执行成功,要么全部都不执行;主要的目的就是保证数据库在执行 SQL 时数据的一致性和完整性
如果不能理解事务的定义的话,那么接下来这个非常经典的转账例子应该能够让你大致理解事务这个概念:
如果没有事务的话,在我们的账户拥有 100 元的情况下给某个人转账,由于转账的过程中系统需要完成非常多的操作,可能没有办法立刻完成转账的行为;此时我们查询账户余额就会发现账户余额依然是 100 元,如果此时我们打算再给其他人转账,那么最终就可能导致账户余额显示为负数,这就破坏了数据的一致性
- start transaction / begin:事务要等到第一个 DQL 执行的时候才会启动,也就是视图才真正创建
- start transaction with consistent snapshot:事务立刻启动,也就是事务立刻创建
- set autocommit:设置自动开启事务,之后不需要再手动声明事务,但是需要手动提交,容易导致长事务
- commit:事务提交
- rollback:事务回滚
注:如果不能理解这里开启事务的区别,可以等到了解 MVCC 机制之后再倒回来看
事务特性
事务的 ACID 特性:
- 原子性:如果事务中的所有操作执行成功才可以提交;如果某个操作执行失败,那么就会发生回滚
- 持久性:如果事务执行成功并且提交后,无论事务是否写入磁盘,对于数据库的修改都是永久性的
- 隔离性:防止多个事务在并发执行的过程中交错执行导致的数据不一致性
- 一致性:事务执行的前后数据库的完整性约束都没有被破坏,也就是说更新之后的数据是符合要求的
事务 ACID 具体实现:
- 原子性:采用回滚日志实现事务的原子性(undo log)
- 每次事务执行操作的时候,回滚日志会记录这次操作对应的相反操作
- 只要事务执行失败,那么就会找到回滚日志中相应的记录并依次执行,回滚到事务执行前的状态
- 持久性:采用重做日志实现事务的持久性(redo log)
- 每次事务执行 SQL 更新内存中对应的数据页的时候,重做日志就会记录这次更新操作或者更新内容
- 只要事务能够成功提交,那么重做日志就可以采用两阶段提交的方式持久化到磁盘中
- 那么即使事务因为提交后还没有持久化到磁盘中而造成丢失,也是可以通过重做日志恢复的
- 隔离性:传统的事务隔离性是通过加锁来解决的,MySQL 采用 MVCC + 加锁 两种方案实现的
隔离级别
注:事务隔离级别的具体实现会在之后的原理篇中提到,因为会涉及到非常多的内容,这里就暂且不去展开
简单来说,就是此前提到的为了防止多个事务并发执行时由于交错执行带来的数据不一致的问题
具体来讲主要分为以下两类问题:
- 读-写:
- 脏读:如果当前事务读取到其他事务尚未提交的数据,就称为脏读,而脏读主要会导致事务读取的数据不正确。比如说某个事务数据发生回滚,那么当前事务读取到的数据就是错误的
- 不可重复读:如果当前事务前后两次查询读取到的数据不一致,就称为不可重复读,而不可重复读主要会导致当前事务读取到的数据频繁变化,在校验数据的时候非常不方便,大多数时候没有太多的危害
- 幻读:如果当前事务前后两次查询得到的结果集的行数不一致,就称为幻读,而幻读主要可能造成日志和数据逻辑不一致的情况,有时还会破坏锁的语义
- 写-写:
提交覆盖,回滚覆盖 > 脏读 > 不可重复读 > 幻读
因为每种问题的严重性也都是不同的,所以如果我们能够容忍一些问题的出现,那么就可以获取性能上的提升,这也是为什么要设计四种事务隔离级别的原因
注:事务的隔离级别也是典中典的问题,不仅要知道事务隔离级别有哪些,还要知道如何实现的
- 读未提交:允许当前事务读取其他事务未提交的数据:
- 仅能够避免回滚覆盖问题,但是依然存在脏读,不可重复读,幻读,提交覆盖的问题
- 读已提交:不允许当前事务读取其他事务没有提交的数据
- 可以解决脏读,回滚覆盖问题,大多数系统都适合这种隔离级别,无法解决不可重复读,幻读,提交覆盖的问题
- 可重复读:事务启动时会获取一致性快照,无论其余事务是否提交,都是无法读取其余事务的更新的
- SQL 标准中规定该级别可以解决脏读,不可重复读,回滚覆盖和提交覆盖,是无法解决幻读问题的
- MySQL 中采用 MVCC 仅能避免快照读下的幻读问题,还需要配合
- 串行化:仅允许事务串行执行,不允许并发执行
- 既然事务都不能够并发执行,那么肯定就不存在任何问题了,不过性能肯定非常低
参考内容:
