# Mysql事务


### ACID原则
####  A原子性
数据处理操作的基本单位
####  C一致性
当事务提交后，或者当事务发生回滚后，数据库的完整性约束不能被破坏
####  I隔离性
一个事务在提交之前，对其他事务都是不可见的
####  D持久性
事务提交之后对数据的修改是持久性的

### 事务语句
* Start Transaction或者 begin, 显式的开启事务
* Commit 提交事务
* Rollback或者 Rollback to 回滚事务
* Savepoint 在事务中创建保存点，方便后续针对保存点进行回滚
* Release savepoint 删除某个保存点
* Set transaction 设置事务的隔离级别

### 设置自动提交
```
set autocommit = 0;//关闭
set autocommit = 1;//开启
```

### completion type参数
* completion=0 当我们执行 commit的时候会提交事务，在执行下一个事务时，需要我们 start transaction或者 begin开启
* completion=1 当我们提交事务后，相当执行了 commit and chain，开启一个链式事务。每条 SQL语句都会自动进行提交，如果采用start transaction或者begin显式开启事务，那么这个事务只有在 commit时才会生效，在 rollback时才会回滚
* completion=2 commit and release 当我们提交事务后，会自动与服务器断开连接

### 隔离性
* 脏读
读到了其他事务还没有提交的数据
* 可重复读
可重复读指的是在一个事务内，最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新（UPDATE）操作。
* 不可重复读
在同一事务内，不同的时刻读到的同一批数据可能是不一样的，可能会受到其他事务的影响。通常针对数据更新（UPDATE）操作
* 幻读
幻读是针对数据插入（INSERT）操作来说的

### 隔离级别
| 隔离级别 | 脏读  | 不可重复读 | 幻读  |
|------|-----|-------|-----|
| 读未提交 | 可能  | 可能    | 可能  |
| 读提交  | 不可能 | 可能    | 可能  |
| 可重复读 | 不可能 | 不可能   | 可能  |
| 串行化  | 不可能 | 不可能   | 不可能 |

#### 举例
| 事务 A         | 事务 B    |
|--------------|---------|
| 启动事务，查询得到值 1 | 启动事务    | 
|              | 查询得到值 1 | 
|              | 将 1改成 2 | 
| 查询得到值 V1     |         | 
|              | 提交事务 B  | 
| 查询得到值 V2     |         | 
| 提交事务 A       |         | 
| 查询得到值 V3     |         | 
* 若隔离级别是“读未提交”，则 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。
### 快照在MVCC里是怎么工作的？
InnoDB 里面每个事务有一个唯一的事务 ID，叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的，是按申请顺序严格递增的。

而每行数据也都是有多个版本的。每次事务更新数据的时候，都会生成一个新的数据版本，并且把 transaction id 赋值给这个数据版本的事务 ID，记为 row trx_id。同时，旧的数据版本要保留，并且在新的数据版本中，能够有信息可以直接拿到它。

一个事务只需要在启动的时候声明说，“以我启动的时刻为准，如果一个数据版本是在我启动之前生成的，就认；如果是我启动以后才生成的，我就不认，我必须要找到它的上一个版本”。当然，如果“上一个版本”也不可见，那就得继续往前找。还有，如果是这个事务自己更新的数据，它自己还是要认的。

在实现上， InnoDB 为每个事务构造了一个数组，用来保存这个事务启动瞬间，当前正在“活跃”的所有事务 ID。“活跃”指的就是，启动了但还没提交。数组里面事务 ID 的最小值记为低水位，当前系统里面已经创建过的事务 ID 的最大值加 1记为高水位。

![transaction.png](/images/transaction.png)
1. 如果落在绿色部分，表示这个版本是已提交的事务或者是当前事务自己生成的，这个数据是可见的；
2. 如果落在红色部分，表示这个版本是由将来启动的事务生成的，是肯定不可见的；
3. 如果落在黄色部分，那就包括两种情况
   a. 若 row trx_id 在数组中，表示这个版本是由还没提交的事务生成的，不可见；
   b. 若 row trx_id 不在数组中，表示这个版本是已经提交了的事务生成的，可见。

InnoDB 利用了“所有数据都有多个版本”的这个特性，实现了“秒级创建快照”的能力。
### 更新逻辑
更新数据都是先读后写的，而这个读，只能读当前的值，称为“当前读”（current read）。

其实，除了 update 语句外，select 语句如果加锁（加上 lock inshare mode 或 for update），也是当前读。

事务的可重复读的能力是怎么实现的？

核心就是一致性读（consistent read）；而事务更新数据的时候，只能用当前读。如果当前的记录的行锁被其他事务占用的话，就需要进入锁等待。

### 为什么建议你尽量不要使用长事务？
长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据，所以这个事务提交之前，数据库里面他可能用到的回滚记录都必须保留，这就会导致大量占用存储空间

### 事务的启动方式
1. 显式启动事物语句，begin或start transaction
2. set autocommit=0，这个命令会将这个线程的自动提交关掉。 
建议你总是使用 set autocommit=1, 通过显式语句的方式来启动事务。 

在 information_schema 库的 innodb_trx 这个表中查询长事务。

### 为保证数据库隔离级别的一致，将 MySQL 的隔离级别设置为“读提交”
配置的方式是，将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。
```
show variables like ’transaction_isolation'
```

