0
点赞
收藏
分享

微信扫一扫

03 事物隔离:为什么你改了我还是看不见?

狐沐说 2022-04-14 阅读 34
mysql

3.1、隔离性与隔离级别

  • 事务具备的特性:原子性,一致性,隔离性,持久性。
  • 当 Mysql 数据库中同时有多个事务在执行就有可能会出现脏读,不可重复读,幻读的问题,为了解决这些问题,就有了隔离级别的概念,但是需要注意的是,使用的隔离级别越高,其事务的执行效率就会越低,因此需要找到一个平衡点。

(1)隔离级别的种类

  • 读未提交(read uncommitted)
    • 一个事务还未提交就被别的事务看到了
    • 例如:事务 A 启动后在对数据 A=1 执行加一操作后还未提交事务,此时事务 B 启动读取数据 A 返回的结果是 2,说明线程 A 执行完还未提交的事务的结果对线程 B 可见了。
  • 读提交(read committed)
    • 一个事务提交之后,它做的变更才会被其他的事务看到
    • 例如:当事务 A 启动后将数据 a 由 1 修改为 2,未提交前,事务 B 查看数据 a 的值都是 1,直到事务 A 提交了,那么之后事务 B 读取到的 a 的值才为 2
  • 可重复读(repeatable read)
    • 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据保持一致,当然在可重复读的隔离级别下,未提交事务的更新对于其他的事务是不可见的。
    • 例如:当事务 A 启动后读取到数据 a 的值等于 1,在事务还未提交前,事务 B 启动修改了数据 a 的值为 2,但是事务 A 整个执行过程看到的数据 a 的值仍然保持刚刚开始启动时读取到的 a = 1 的结果,除非事务 A 自己对数据 A 进行了修改。
  • 串行化(serializable)
    • 顾名思义是对同一条记录,写操作和读操作会使用写锁和读锁进行同步安全保护,当出现读写锁发生冲突的时候,后访问的事务必须等待前一个事务执行完成才能继续执行。
    • 例如:当事务 A 启动查询数据 a 时,如果事务 B 启动对数据 a 进行写操作,发生读写锁冲突,那么事务 B 就会被锁住,必须等待事务 A 提交才能读取数据 a 及其之后的操作。

(2)隔离级别下的各种读

  • 脏读
    • 脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
  • 可重复读
    • 可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。
  • 不可重复读
    • 对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
  • 幻读
    • 幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
      在这里插入图片描述

3.2、事务隔离的实现

  • 1.各种隔离界别的实现
    • 在实现上,Mysql 数据库里面会创建 read-view,访问的时候以 read-view 的逻辑为准
    • 可重复读:在事务启动的时候创建视图,整个事务存在期间都使用这个视图。
    • 读提交:在 SQL 语句开始执行的时候在创建视图
    • 读未提交:不需要创建视图,直接返回表记录中的最新值
    • 串行化:使用加锁的方式来避免并行访问。
    • 需要注意的是:在不同的隔离级别下,数据库的行为是不同的,Oracle 数据库默认使用的是读提交的隔离级别,因此对于 Oracle 迁移到 Mysql 的应用时,为了保证数据库隔离级别一致,一定要将 Mysql 的隔离级别设置为 “读提交”:transaction-isolation 参数设置为 READ-COMMITTED,可以使用 show variables 来查看当前的值。
  • 2.可重复读隔离级别的实现
    在这里插入图片描述
    • 上图是将数据从 1 修改成 4 的回滚段,实际的当前值为 4,在 Mysql 中每一条记录的更新都保存这一段回滚段,记录上的最新的值可以通过回滚操作获取到数据以前版本的值,在不同时刻启动的事务其对应的是不同的视图,并且各个事务在各自的视图上操作互不影响,在不同的视图中同一个数据可能读取的值不一样,这也就是 Mysql 中的多版本并发(MVCC)。
    • 然而回滚日志不可能一直保留,系统会进行判断当没有比回滚日志更老的视图时就会将其回滚日志删除,即没有任何一个事务需要这个回滚日志的时候就会删除。
  • 3.避免使用长事务
    • 基于上面的了解,因此不建议使用长事务,因为在长事务内部绝大可能回存有大量的回滚日志,在该事务提交之前,这些回滚日志都必须保留着,因此可能会出现数据库实际存储的数据占用空间远小于数据库实际占用的存储空间,导致不得不重建整个数据库。长事务除了会产生大量的回滚段占用资源以外还会占用锁资源,也可能因此拖垮整个数据库。

3.3、事务启动的方式

  • 1.如前面所说的,长事务存在这些风险建议不要使用,但是很多时候存在并不是有意地使用长事务,通常是由于失误所导致的,Mysql 启动事务的方式有以下两种:
    • 1.显式的启动事务的语句:begin 或则 start transcation。配套的提交语句时 commit。回滚语句是 rallback。
    • 2.set autocommit=0,这个命令是将线程的自动提交给关闭,导致查询语句都存在于事务中即意味如果执行一条 select 语句,这个事务就启动了,而且并不会提交。这个事务的提交必须由自己手动的 commit 提交或者 rollback 回滚语句或者断开连接。
  • 2.有些客户端连接框架默认连接成功后就先执行一条 set autocommit=0; 的命令,导致只要执行一条 SQL 语句,即使是一条 select 查询 SQL 语句其事务就开始启动了,导致意外的长事务操作。因此建议总是使用 set autocommit=1; 命令选择其显式的启动事务的方式启动事务。
  • 3.如果纠结于多一次语句交互的次数的问题,当在一个需要频繁使用事务的数据库中,由于每次都要使用 begin 语句显式的方式启动一个事务,为了减少使用 begin 的次数使用 commit woke and chain,这样只需要执行一次 begin 即可之后只要事务一旦提交就立马启动事务。(提交事务链)

3.4、问题

  • 如何避免长事务对业务的影响?
    • 这个问题要从开发端可数据库端两方面进行考虑
    • 开发端
      • 确认是否设置了 set autocommit=1,一般框架将该值设置为 0,就取消了自动提交,都需要手动进行确认提交事务,事务才能结束。
      • 确认是否有不必要的只读事务,有些框架会习惯不管什么语句都先使用 begin / commit 框起来,将好几个 select 的查询操作也添加的事务中去。
      • 业务连接数据库的时候,根据业务本身的预估,通过 SetMax_execution_time 命令来控制每个语句执行的最长时间,避免单个语句意外执行太长时间(注意为什么会发生以外在后续会有所讲解)
    • 数据库端
      • 监控 information_schema.Innodb_trx 表,设置长事务的阈值,超过就报警或者 kill
      • Percona 的 pt-kill 这个工具不错,推荐使用
      • 在业务功能测试阶段要求输出所有的 general_log(总体日志),分析日志行为提前发现问题
      • 如果是一个的是 Mysql5.6 或者更早的版本,把 innodb_undb_tablespaces(innodb_undb_表空间) 设置成 2(或者更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便。
举报

相关推荐

0 条评论