03 | 事务隔离

105 阅读4分钟

隔离性与隔离级别

ACID

  • A:Atomicity,原子性;
  • C:Consistency,一致性;
  • I:Isolation,隔离性;
  • D:Durability,持久性;

多个事务同时执行,可能会出现以下问题:

  • 脏读(dirty read)
  • 不可重复读(non-repeatable read)、
  • 幻读(phantom read)

为了解决以上问题,就有了“隔离级别”的概念。

隔离级别:

  • 读未提交:一个事务还没提交,它做的变更就被其他事务看到。
  • 读已提交:一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
  • 串行化:对于同一行记录,“读”会加“读锁”,“写”会加“写锁”,读写锁冲突时,后一个事务必须等前一个事务执行完成,才能继续执行。

通过一个示例了解不同的隔离级别读取值的效果:

mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

image.png

  • 读未提交:V1=2, V2=2, V3=2
  • 读已提交:V1=1, V2=2, V3=2
  • 可重复读:V1=1, V2=1, V3=2(事务在执行期间看到的数据必须是前后一致的,所以v1=v2=1)
  • 串行化:在事务B执行“将1改成2”的时候,会被锁住,直到事务A提交后,事务B才可以继续执行。从A的角度看,V1=1, V2=1, V3=2。

【结论】

  • 读未提交的隔离级别下,直接返回记录的最新值,没有视图的概念。
  • 读已提交的隔离级别下,在SQL语句开始执行的时候创建视图。
  • 可重复读的隔离级别下,会在事务启动的时候创建视图,整个事务期间都使用这个视图。
  • 串行化的隔离级别下,直接用加锁的方式避免并行访问。

什么时候可以用到可重复读的隔离级别:数据校对

假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。
做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。 希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。
这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。

事务隔离级别的实现

每一条记录在更新时,会同时记录一条回滚操作。
记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从1被按照顺序改成了2、3、4,在回滚日志里会有类似下面的操作:

image.png

当前值是4,不同时刻启动的事务会有不同的read-view。如上图,视图A,B,C里面,这一记录的值分别是1,2,4。同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC),对于read-view A,要得到1,必须将当前值一次执行图中的所有回滚操作得到。

为什么避免使用长事务?

长事务意味着系统里面会存在很老的事务视图,由于这些事务随时可能访问数据库的任何数据,所以这个事务提交之前,数据库会保存可能用到的回滚记录,导致大量占用存储空间。
除了对回滚段的影响,长事务还占用锁资源,可能拖垮整个库。

事务的启动方式

  1. 显式启动事务语句,begin或start transaction,提交是commit,回滚时rollback。
  2. set autocommit=0。该命令会将这个线程事务自动提交关掉,意味着哪怕执行select语句,事务都会启动,因为设置为0,事务不会自动提交,直到主动执行commit或rollback语句,或者断开连接。

建议:set autocommit=1,通过显式的方式来启动事务。