菜鸟笔记
提升您的技术认知

事务隔离:为什么你改了我还看不见?-ag真人游戏

简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败

在mysql中,事务是在存储引擎层实现的。mysql时支持多种存储引擎的系统,但并不是所有存储引擎都支持事务。比如myisam引擎就不支持事务,所以这也是myisam被innodb取代的重要原因。

事务的特性包括:原子性、一致性、隔离性、持久性

当有多个事务同时执行时,可能会出现:脏读、不可重复读、幻读等问题
为了解决这些问题,就会涉及到隔离级别的概念。隔离得越严实,效率就会越低。sql标准的事务隔离级别如下:

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

举个例子:创建表t,其中只有一个字段c,其中一行的值为1

mysql> create table t(c int)engine=innodb;
insert into t(c) values(1);

然后分别执行两个事务。

事务a 事务b
启动事务,查询得到值1 启动事务
查询得到值1
将1改成2
查询得到值v1
提交事务b
查询得到值v2
提交事务a
查询得到值v3

对于不同的隔离级别,得到的结果也会不一样

  1. 读未提交:v1=v2=v3=2
                      v1的时候虽然事务b未提交,但是也能够被事务a看到。
  2. 读提交:v1=1,v2=v3=2
                      事务b对于值的修改,只有在提交后才能被事务a看到。
  3. 可重复读:v1=v2=1,v3=2
                      事务a在执行期间,看到的数据前后是一致的。
  4. 串行化”:v1=v2=1,v3=2
                      因为事务a先启动,所有只有当事务a执行完后,事务b才会开始执行。

对于四种隔离级别的实现原理是和视图相关的:

  1. 读未提交:直接返回记录上的最新值,没有视图的概念
  2. 读提交:视图是在每个sql语句开始执行的时候创建的,期间可以修改视图
  3. 可重复读:视图是在这个事务启动的时候创建的,整个事务期间都使用这个视图
  4. 串行化:直接利用加锁的方式来避免并行访问

每一种隔离级别都是有用的,只是适用于不同的应用场景。

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

下面利用可重复读来说明事务隔离的具体实现。

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

假设一个值从1被按顺序改成了2、3、4,在回滚日志中就会有类似下面的记录:

现在当前值是4,但是不同时刻启动的事务会有不同的read-view,能够看到不同的值。同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(mvcc)

同时你会发现,即使现在有一个事务将当前值改成5,也不会影响read-view a、read-view b、read-view c。

此外,回滚日志在不再需要的时候会进行删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志的时候会被删除
什么时候才是不需要呢?就是当前系统里没有比这个回滚日志更早的read-view的时候。

基于此,建议尽量不要使用长事务。因为长事务意味着系统里面会存在很老的事务视图。因为这些事务随时可能访问数据库里面的任何数据,所以在这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,会占用大量的空间。

事务的启动方式有以下两种:

  1. 显式启动事务语句:begin或start transaction。配套的提交语句是commit,回滚语句是rollback。
  2. set autocommit=0:这个命令会将这个线程的自动提交关闭。意味着你只执行select语句,他也不会提交。这个事务一直持续到你主动执行commit或rollback语句,或断开连接。

通常是建议将set autocommit=1,通过显式的方式来启动事务

如果在需要频繁使用事务的业务,begin会带来多一次交互,那么就可以在autocommit=1的情况下,用begin显式启动事务,如果执行commit则提交事务。如果执行commit work and chain,则是提交事务并启动下一次事务。

网站地图