Loading

极客--事务隔离为什么你改了我还看不见?

查询余额、做加减法、更新余额等,这些操作必须保证是一体的,不然等程序查完之后,还没做减法之前,你这100块,完全可以借着这个时间再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么?

  • 事务就是保证一组数据库操作,要么全部成功,要么全部失败。在Mysql中,事务支持是在引擎层实现的。Mysql是一个支持多引擎的系统,但并不是所有的引擎都支持事务,比如Mysql原生MyISAM引擎不支持事务,
隔离性与隔离级别
  • 提到事务,ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们就说其中I,也就是隔离性

  • 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到.

  • 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。

  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下。未提交变更对其他事务也是不可行。

  • 串行化,顾名思义,"写"会加写锁,"读"会加读锁。当读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行

可重复读隔离级别下MVCC
  • 数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在可重复读隔离级别下,这个视图是在事务启动创建的,整个事务存在期间都用这个视图。在读提交隔离级别下,这个视图是每个SQL语句开始执行的时候创建。读未提交的隔离级别下直接返回记录上的最新值,没有视图概念;而串行化隔离级别下直接用加锁的方式来避免并行访问。

  • Orecal默认隔离级别是读提交,迁移过程的时候需要修改Mysql隔离级别

`
mysql> show variables like 'transaction_isolation';

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+`

事务隔离实现
  • 实际上Mysql每条记录在更新的时候都会同时记录记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

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

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

  • 同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。

  • 回滚日志再不需要的时候删除,当系统没有比这个回滚日志更早的read-view的时候

  • 尽量不要使用长事务,由于这些事务可能随时访问数据库里面任何数据,所以这个事务提交之前,数据库里面它可能用到回滚记录都必须保留,这就会导致大量占用存储空间。

  • 5.5以及之前版本,回滚日志跟数据字典一起放在ibdata文件里 ,即使长事务最终提交,回滚段被清理,文件也不会变小。

  • 对于回滚段的影响,长事务还会占用锁资源,也可能拖垮整个库

事务的启动的方式

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

  1. 显示启动事务语句,begin或start transaction。配套提交语句commit,回滚语句是rollback

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

  3. 如果客户端连接框架默认连接成功后先执行一个set autocommit=0的命令。这就导致接下来的事务,如果是长连接,就导致意外长事务

因此,我会建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务
你可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
避免长事务的方案

从数据库层面

  • 从数据库方面:
    • 设置autocommit=1,不要设置为0
    • 写脚本监控information_schemal.innodb_trx表中的数据内容,发现长事务,kill掉
    • 配置SQL语句所能执行的最大运行时间,如果查过最大运行时间后,中断这个运行事情长的SQL语句。
    • 设置回滚表空单独存放,便于回收表空间
  • 从业务代码方面:
    • 确认是否使用了autocommit=0的配置,如果有关闭它,然后再业务代码中手动的使用begin;commit来操作。
    • 检查业务逻辑代码,能拆分为小事务的不要用大事务。
    • 检查代码,把没有必要的select语句被事务包裹的情况去掉。
posted @ 2022-09-17 20:29  DoDo神  阅读(48)  评论(0)    收藏  举报