事务的隔离性及MySQL多版本并发控制实现

隔离级别

说到隔离性,肯定就会想到隔离级别,SQL标准的事务隔离级别包括:

  • 读未提交(Read uncommited 简称RU):指一个事务还没有提交,它做的修改就可以被另一个事务读到
  • 读提交(Read Commited 简称RC):指直到一个事务提交,它做的修改才可以被另一个事务读到
  • 可重复度(Repeatable Read 简称RR):指从一个事务开启,到事务提交,在这个事务执行过程中,它看到的数据总是跟在这个事务启动的时候看到的数据一致
  • 串行化(Serializable):对同一行记录,“写”会加“写锁”,“读”会加“读锁”。读写锁冲突时,后访问的事务必须等前一个事务执行完成才能继续执行

MySQL隔离级别修改:transaction_isolation,可设置的值为: READ-UNCOMMITTEDREAD-COMMITTEDREPEATABLE-READ,或 SERIALIZABLE

数据视图 ReadView

为了实现隔离级别,数据库会创建一个视图,访问的时候以视图的逻辑结果为准,当然这个视图在“读未提交”和“串行化”这两种隔离级别下是没有的,“读未提交”隔离级别下直接返回记录上的最新值,而“串行化”则直接使用加锁的方式避免并发访问同一行记录。
在“可重复读”隔离级别下,这个视图是在开启事务的时候创建的,整个事务期间都使用这个视图;
在“读已提交”隔离级别下,这个视图是在每次执行SQL的时候创建的

ReadView中并不是真正的存储数据,如果是存储数据的话,每个事务都创建一个ReadView未免也太浪费内存了,ReadView会记录4个非常重要的属性:

  1. creator_trx_id: 当前事务的 id;
  2. m_ids: 当前系统中所有的活跃事务的 id,活跃事务指的是当前系统中开启了事务,但是还没有提交的事务;
  3. min_trx_id: 当前系统中,所有活跃事务中事务 id 最小的那个事务,也就是 m_id 数组中最小的事务 id;
  4. max_trx_id: 当前系统中事务的 id 值最大的那个事务 id 值再加 1,也就是系统中下一个要生成的事务 id。

ReadView会根据这4个属性,再结合回滚段来实现MVCC机制,决定让一个事务能读到哪些数据,不能读取到哪些数据。

可重复读的使用场景

假设在对账户流水进行对账时,希望在对账过程中,即使有用户发生了一笔新的交易,也不影响校对的过程,这个时候使用“可重复读”隔离级别就很方便

事务隔离的实现:MVCC多版本并发控制

我们通过“可重复读”隔离级别进行阐述:
我们对记录的每次更新MySQL都会记录一条回滚操作,通过回滚操作,记录可以得到前一个状态的值。
比如表T的c字段的值被事务C从1被修改为2->3>4,在回滚日志中就会有三个回滚日志,分别为4>3、3>2、2>1。
在这里插入图片描述

理解“多版本并发控制”
在“可重复读”隔离级别下,
当事务A启动时会创建一个视图Read Videw A,字段值是1;同时事务C也启动了,也同样的创建了一个视图Read View C,并且把字段的值从1被修改为2->3>4,如上图的字段值行,对应的就会生成三个回滚日志,对应上图的回滚段行。
此时事务A读该记录的字段仍然应该是1,但是但是要读到1,就必须将当前值依次执行上图的所有回滚操作得到,这个问题在数据视图部分已经讲过了
这时候就算事务C提交了,但是针对这行记录的回滚日志也不能删除,因为事务A可能还会使用它来还原到自己开启事务的版本。
如果事务A是一个长事务,回滚日志就一直不能删除,这样就占用了大量的存储空间,因此得出一个结论:尽量不要使用长事务


如何查找长事务

information_schema库的innodb_trx表可以查看事务,下面的语句是查找持续时间超过60s的事务。

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

如何避免长事务对业务的影响

应该从应用开发端和数据库端来看

应用开发端

  1. 确认是否使用了 set autocommit=0。这个确认工作可以在测试环境中开展,把 MySQL 的 general_log 开起来,然后随便跑一个业务逻辑,通过 general_log 的日志来确认。一般框架如果会设置这个值,也就会提供参数来控制行为,你的目标就是把它改成 1。
  2. 确认是否有不必要的只读事务。有些框架会习惯不管什么语句先用 begin/commit 框起来。我见过有些是业务并没有这个需要,但是也把好几个 select 语句放到了事务中。这种只读事务可以去掉。
  3. 业务连接数据库的时候,根据业务本身的预估,通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。(为什么会意外?在后续的文章中会提到这类案例)

数据库端

  1. 监控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警 / 或者 kill;Percona 的 pt-kill 这个工具不错,推荐使用;
  2. 在业务功能测试阶段要求输出所有的 general_log,分析日志行为提前发现问题;

开启 general log 将所有到达MySQL Server的SQL语句记录下来,查询日志开启 方法一: mysql>set global general_log_file=’/tmp/general.lg’; #设置路径 mysql>set global general_log=on; # 开启general log模式 mysql>set global general_log=off; # 关闭general log模式 命令行设置即可,无需重启 在general log模式开启过程中,所有对数据库的操作都将被记录 general.log 文件 方法二: 也可以将日志记录在表中 set global log_output=‘table’ 运行后,可以在mysql数据库下查找 general_log表 二、查询日志关闭 查看是否是开启状态: mysql> show global variables like ‘%general%’; 关闭 mysql> set global general_log = off; // 关闭查询日志

  1. 如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 设置成 2(或更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便。

innodb_undo_tablespaces是控制undo是否开启独立的表空间的参数 为0表示:undo使用系统表空间,即ibdata1 不为0表示:使用独立的表空间,一般名称为 undo001 undo002,存放地址的配置项为:innodb_undo_directory 一般innodb_undo_tablespaces 默认配置为0,innodb_undo_directory默认配置为当前数据目录

事务的启动方式

显式启动
使用begin或start transaction启动,对应使用commit提交,rollback回滚

自动启动
set autocommit=0,这个命令会将这个线程的自动提交关掉,只执行一个select语句,这个事务就启动了,并且不会自动提交,持续到主动执行commit、rollback语句,或者断开连接。set autocommit=0会导致意外的长事务(如果忘记手动commit或者rollback),建议使用set autocommit=1,表示MySQL自动开启和提交事务。 比如执行一个update语句,语句只完成后就自动提交了。不需要显示的使用begin、commit来开启和提交事务。 所以,当我们需要对某些操作使用事务的时候,手动的用begin、commit来开启和提交事务。

自动提交autocommit
数据库事务默认是自动提交的,如果语句没有返回错误,MySQL会在每个SQL语句后提交一次。

commit work and chain
在autocommit=1的情况下,如果使用begin显式启动事务,也可以使用commit work and chain来提交事务,意思是提交事务并开启下一个事务,省去了再次执行begin语句的开销。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹氿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值