第三章 事务隔离
- 事务的特性:原子性、一致性、隔离性、持久性
 - 在 MySQL 中,事务支持是在
引擎层实现的。 - MyISAM 既不支持
事务,又没有crash-safe。 
隔离性与隔离级别
当数据库上有多个事务同时执行的时候,就有可能出现以下几种情况:
- 脏读:读到其他事务未提交的数据;
 - 不可重复读:前后读取的记录内容不一致;
 - 幻读:前后读取的记录数量不一致。
 
为了解决这些情况,就有了“隔离级别”的概念
SQL 标准的事务隔离级别包括:
- 读未提交(read uncommitted):别人改数据的事务尚未提交,我在我的事务中也能读到。
 - 读提交(read committed):别人改数据的事务已经提交,我在我的事务中才能读到。
 - 可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在
启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 - 串行化(serializable ):我的事务尚未提交,别人就别想改数据。
 - 注意:MySQL的默认隔离级别是 “可重复读”
 
举例
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
 

- 如果是“读未提交”:V1 的值就是 2,V2、V3 也都是 2
 - 如果是“读提交”:V1 是 1,V2 、V3也都是 2
 - 如果是“可重复读”:V1、V2 是 1,V3 是 2
 - 如果是“串行化”:事务A一开始查询值1的时候就获得了
读锁,根据两阶段加锁,事务A获得的锁要在 commit 的时候才释放,所以事务B在修改1为2的时候申请写锁会阻塞直到事务A提交,事务A提交之前获取的值都是1,所以V1、V2都是 1,事务A提交后事务B才获取到写锁完成更新操作,所以V3是 2 
关于 MVCC视图 的理解
- 读未提交 (RU):没有视图的概念,直接返回记录上的最新值。
 - 读提交 (RC):在每一行SQL语句执行的时候创建。
 - 可重复读 (RR):在第一个Select语句执行时创建的。
 - 串行化 (Serial):通过锁来实现数据访问,没有视图的概念。
 
查看和设置当前数据库的隔离级别
- 设置全局数据库的事务隔离级别为“读提交” (下一次会话生效):
set global transaction isolation level READ COMMITTED; 
mysql> set global transaction isolation level READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
- 查看当前数据库事务隔离级别:
show variables like 'transaction_isolation'; 
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set (0.00 sec)
事务隔离的实现
在 MySQL 中,实际上每条记录在更新的时候 除了记录变更记录,还会记录一条变更相反的回滚操作记录,前者记录在 redo log,后者记录在 undo log
假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。
 

在可重复读隔离级别中,表中的数据其实已经改变,在前面的视图里,需要查找某条记录时,是通过取当前数据,再取视图对应的回滚段回滚到该视图的值。
回滚日志什么时候才能删除呢 ?
- 读操作产生读视图,当这个事务结束后该视图删除,那么在这个读视图后的回滚日志可以删除,因为已经没有谁会驱动当前版本的数据执行回滚到删除的那个读视图版本了
 - 当没有比回滚日志更早的读视图(读视图在事务开启时创建)的时候,这个数据不会再有谁驱使它回滚了,这个回滚日志也就失去了用武之地,可以删除了
 - 回滚日志 是被
purge线程回收的 
长事务会带来哪些问题 ?
- 占有锁资源,可能拖垮整个库
 - 长事务可能导致暂时需要保存很多的
undo log,会占用内存空间 
事务的启动方式
- 显式启动事务语句,
begin 或者start transaction,提交commit,回滚rollback - 
set autocommit=0,该命令会把这个线程的自动提交关掉。这样只要执行一个select语句,事务就启动,并不会自动提交,直到主动执行 commit 或 rollback 或 断开连接。 
- 建议使用
方式一,如果考虑多一次交互问题,可以使用commit work and chain 语法 
- 在
autocommit=1 的情况下用 begin 显式启动事务 - 如果执行
commit 则提交事务。如果执行commit work and chain 则提交事务并自动启动下一个事务 
- 建议使用
set autocommit=1 来显式启动事务 - 
commit work and chain:提交事务并开启下一个事务(少执行一个begin语句) 
查询持续时间超过60秒的事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60;
mysql> select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60;
Empty set (0.01 sec)
                










