1. 概述
这一章主要讲到了事务的隔离性与隔离级别,事务隔离的实现,提到了undo log,数据库多版本控制(MVCC),事务的启动方式,以及避免使用长事务。
2. 总结
2.1 事务的特性
就是我们熟知的 ACID,原子性、一致性、隔离性、持久性。
2.2 有多个事务同时执行时,就可能出现脏读、不可重复读、幻读的问题,为了解决这些问题,就出现了隔离级别
2.3 隔离级别有哪些
- 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到
- 读已提交:一个事务提交之后,它做的变更才会被其他事务看到
- 可重复读:一个事务在执行过程中看到的数据是一致的。未提交的更改其他事务不可见
- 串行化:一个记录会加读写锁,当发现读写锁互斥时,后访问的事务必须等前一个事务执行完成才能继续执行
2.4 Oracle 数据库默认的隔离级别是读提交,MySQL 数据库默认的隔离级别是可重复读,当 Oracle 数据库迁移到 MySQL 数据库时,需注意隔离级别问题
2.5 隔离级别配置的方式 transaction-isolation
2.6 提到了可重复读的使用场景,对账,保证对账时,发生新的操作不会影响对账数据
2.7 事务隔离的实现
当发生数据变更时,会正向记录变更日志,也就是我们之前提到的redo log(重做日志),同时还会反向记录回滚日志,也就是undo log。
同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。
2.8 回滚日志不能一直保留,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的 read-view 的时候
2.9 为什么不建议使用长事务
- 长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
- 除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库。
2.10 事务的启动方式
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
- set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
注:建议总是使用 set autocommit=1, 通过显式语句的方式来启动事务。如果考虑多一次交互问题,可以使用commit work and chain语法。在autocommit=1的情况下用begin显式启动事务,如果执行commit则提交事务。如果执行commit work and chain则提交事务并自动启动下一个事务。
2.11 思考题:如何避免长事务对业务的影响
首先,从应用开发端来看:
- 确认是否使用了 set autocommit=0。这个确认工作可以在测试环境中开展,把 MySQL 的 general_log 开起来,然后随便跑一个业务逻辑,通过 general_log 的日志来确认。一般框架如果会设置这个值,也就会提供参数来控制行为,你的目标就是把它改成 1。
- 确认是否有不必要的只读事务。有些框架会习惯不管什么语句先用 begin/commit 框起来。我见过有些是业务并没有这个需要,但是也把好几个 select 语句放到了事务中。这种只读事务可以去掉。
- 业务连接数据库的时候,根据业务本身的预估,通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。
其次,从数据库端来看:
- 监控 information_schema.Innodb_trx 表,设置长事务阈值,超过就报警 / 或者 kill;
- Percona 的 pt-kill 这个工具不错,推荐使用;
- 在业务功能测试阶段要求输出所有的 general_log,分析日志行为提前发现问题;
- 如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 设置成 2(或更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便。