笔记 | 事务隔离:为什么你改了我还看不见?

2022-10-26 15:57:53 浏览数 (2)

【事务】

事务支持是在引擎层实现的,不是所有的引擎都支持事务,只有InnoDB支持事务 特性:原子性、一致性、隔离性、持久性 Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。 Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。 Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。 Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到
  • 读提交: 一个事务提交之后,它做的变更才会被其他事务看到
  • 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的
  • 串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行

【举个栗子】

代码语言:javascript复制
mysql> create table T(c int) engine=InnoDB;insert into T(c) values(1);

  • 若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
  • 若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
  • 若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
  • 若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。

Oracle 数据库的默认隔离级别其实就是“读提交” MySQL默认的隔离级别是可重复读(Repeatable Read) 因此对于一些从 Oracle 迁移到 MySQL 的应用,为保证数据库隔离级别的一致,你一定要记得将 MySQL 的隔离级别设置为“读提交”。配置的方式是,将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。

事务隔离的实现

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

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

长事务

长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。

set autocommit=1;表示MySQL自动开启和提交事务。 比如执行一个update语句,语句只完成后就自动提交了。不需要显示的使用begin、commit来开启和提交事务。 所以,当我们需要对某些操作使用事务的时候,手动的用begin、commit来开启和提交事务。

【查找长事务】

可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。

代码语言:javascript复制
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

【避免长事务】

  • 监控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警 / 或者 kill;
  • 如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 设置成 2(或更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便。
  • 确认是否使用了 set autocommit=0 如果是0把它改成 1
  • 确认是否有不必要的只读事务。
  • 通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。

0 人点赞