1、事务隔离级别

事务的 ACID 中的 I 指的就是隔离性(Isolation)。

1、隔离级别

读未提交(read-uncommitted):一个事务还没提交时,它做的变更就能被别的事务看到。

读提交(read-committed):一个事务提交之后,它做的变更才会被其他事务看到。

可重复读(repeatable-read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。

串行化(serializable):当出现读写锁冲突的时候,后执行事务必须等前一个事务执行完成,才能继续执行。

MySQL 的隔离级别设置为"读提交"。

2、事务隔离例子

1
2
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

事务隔离例子

在不同的隔离级别下的结果:

  1. 隔离级别为读未提交,v1 = 2, v2 = 2, v3 = 2。

  2. 隔离级别为读提交,v1 = 1, v2 = 2, v3 = 2。

  3. 隔离级别为可重复读,v1 = 1, v2 = 1, v3 = 2。

  4. 隔离级别为串行化,v1 = 1, v2 = 1, v3 = 2。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

可重复读隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。

读提交隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。

读未提交隔离级别下,直接返回记录上的最新值,没有视图概念。

串行化隔离级别下,直接用加锁的方式来避免并行访问。

3、事务配置方式

通过命令 show variables like 'transaction_isolation'; 来查看当前隔离级别。通过命令 set transaction_isolation = 'read-committed'; 来设置隔离级别。

事务配置方式

2、事务隔离的实现

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

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

回滚日志示例

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

3、事务的启动方式

建议你总是使用 set autocommit = 1, 通过显式语句的方式来启动事务。

在 autocommit = 1 的情况下,用 begin 显式启动的事务,如果执行 commit 则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务。

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

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

4、问题

怎样避免长事务?

在开发端:

  1. 设置set autocommit = 0
  2. 确认是否有不必要的只读事务。
  3. 通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。

在数据库端:

  1. 控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警/或者 kill。
  2. ercona 的 pt-kill 这个工具不错,推荐使用。
  3. 在业务功能测试阶段要求输出所有的 general_log,分析日志行为提前发现问题。
  4. 如果使用的是MySQL 5.6或者更新版本,把 innodb_undo_tablespaces 设置成2(或更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便。
0%