<commit_before>---
layout:     post
title:      "mysql的事务和锁"
subtitle:   "mysql的事务和锁"
date:       2020-04-16
author:     "Lerko"
header-img: "/img/post-bg-e2e-ux.jpg"
catalog: true
multilingual: false
tags:
    - mysql
---

## 事务的定义

理论上说，事务有着极其严格的定义，他必须同时满足4个特性，即通常所说的事务的ACID特性。

1. A（Atomicity） 原子性：操作必须是原子的
2. C（consistency） 一致性：事务某个动作失败了，系统可以自动撤销事务，返回初始化状态，保存一致性
3. I （isolation） 隔离性：并发控制 串行化 锁 等称呼
4. D（durability）持久性 ： 事务提交之后就是永久性，即使发生宕机事故也要能恢复数据

## 事务的实现

事务的原子性，一致性，持久性是通过数据库的redo log 和 undo log实现的

| 日志类型  | 作用  | 写入规则|
|---|---|---|---|
|  redo | 保证原子性和持久性  |物理日志，记录页的物理修改操作|顺序写 |
|  undo | 保证一致性  |逻辑日志，根据每行记录进行记录，帮助事务回滚以及MVCC|随机读写|


### redo log

![20200816104116](http://img.chenyingqiao.top/blog/20200816104116.png)

mysql支持三种将redo log buffer写入redo log file的时机，可以通过innodb_flush_log_at_trx_commit参数配置，各参数值含义如下：

|  参数值 |  含义 |
|---|---|
|  0（延迟写） |  事务提交时不会将redo log buffer中日志写入到os buffer，而是每秒写入os buffer并调用fsync()写入到redo log file中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的，当系统崩溃，会丢失1秒钟的数据。 |
|  1（实时写，实时刷）	 | 事务每次提交都会将redo log buffer中的日志写入os buffer并调用fsync()刷到redo log file中。这种方式即使系统崩溃也不会丢失任何数据，但是因为每次提交都写入磁盘，IO的性能较差。 |
|  2（实时写，延迟刷） |  每次提交都仅写入到os buffer，然后是每秒调用fsync()将os buffer中的日志写入到redo log file。 |


![20200816104713](http://img.chenyingqiao.top/blog/20200816104713.png)

### undo log

重做日志记录了事务的行为，和redo log不同

undo log分为：
1. insert undo log 
2. update undo log

insert undo log 是在insert操作中的产生undo log。因为insert操作的记录，只对事务本身可见，其他事务不可见。


## 隔离级别

| 隔离级别  |  脏读 |不可重复读	 |幻读 |
|---|---|---|---|
|  读未提交 |  是 |是 |是 |
|  不可重复读 |  否 |是 |是 |
|  可重复读 | 否 |否 |是（innodb否） |
|  串行化 |  否 |否 |否 |


innodb可重复读中解决幻读是通过下面两种

主要通过以下两种情况避免幻读

**普通读**：快照读（非阻塞读）伪MVCC
表象避免幻读，是RR下查找数据，第一次读取创建快照，后面读取都是读取本次快照，不论别的事务是否提交相关更改，我们都不知道，掩耳盗铃

> 所谓当前读，指的是加锁的select(S或者X), update, delete等语句。在RR的事务隔离级别下，数据库会使用next-key locks来锁住本条记录以及索引区间。


**当前读**：next-key锁（行锁+gap锁）
上了锁，你别的操作不会修改我锁定的区间了，我就

> 因为普通读是不会加锁的读，故不会有next-key locks的使用，解决幻读的手段是MVVC


拿上面那个例子来说，在RR的情况下，假设使用的是当前读，加锁了的读

select * from table where id>3 锁住的就是id=3这条记录以及id>3这个区间范围，锁住索引记录之间的范围，避免范围间插入记录，以避免产生幻影行记录。

**脏读**

事务A读取了事务B更新的数据，然后B回滚操作，那么A读取到的数据是脏数据

![20200816143637](http://img.chenyingqiao.top/blog/20200816143637.png)


**不可重复读**

事务 A 多次读取同一数据，事务 B 在事务A多次读取的过程中，对数据作了更新并提交，导致事务A多次读取同一数据时，结果 不一致。


![20200816144642](http://img.chenyingqiao.top/blog/20200816144642.png)

**幻读**<commit_msg>change<commit_after>---
layout:     post
title:      "mysql的事务和锁"
subtitle:   "mysql的事务和锁"
date:       2020-04-16
author:     "Lerko"
header-img: "/img/post-bg-e2e-ux.jpg"
catalog: true
multilingual: false
tags:
    - mysql
---

## 事务的定义

理论上说，事务有着极其严格的定义，他必须同时满足4个特性，即通常所说的事务的ACID特性。

1. A（Atomicity） 原子性：操作必须是原子的
2. C（consistency） 一致性：事务某个动作失败了，系统可以自动撤销事务，返回初始化状态，保存一致性
3. I （isolation） 隔离性：并发控制 串行化 锁 等称呼
4. D（durability）持久性 ： 事务提交之后就是永久性，即使发生宕机事故也要能恢复数据

## 事务的实现

事务的原子性，一致性，持久性是通过数据库的redo log 和 undo log实现的

| 日志类型  | 作用  | 写入规则|
|---|---|---|---|
|  redo | 保证原子性和持久性  |物理日志，记录页的物理修改操作|顺序写 |
|  undo | 保证一致性  |逻辑日志，根据每行记录进行记录，帮助事务回滚以及MVCC|随机读写|


### redo log

![20200816104116](http://img.chenyingqiao.top/blog/20200816104116.png)

mysql支持三种将redo log buffer写入redo log file的时机，可以通过innodb_flush_log_at_trx_commit参数配置，各参数值含义如下：

|  参数值 |  含义 |
|---|---|
|  0（延迟写） |  事务提交时不会将redo log buffer中日志写入到os buffer，而是每秒写入os buffer并调用fsync()写入到redo log file中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的，当系统崩溃，会丢失1秒钟的数据。 |
|  1（实时写，实时刷）	 | 事务每次提交都会将redo log buffer中的日志写入os buffer并调用fsync()刷到redo log file中。这种方式即使系统崩溃也不会丢失任何数据，但是因为每次提交都写入磁盘，IO的性能较差。 |
|  2（实时写，延迟刷） |  每次提交都仅写入到os buffer，然后是每秒调用fsync()将os buffer中的日志写入到redo log file。 |


![20200816104713](http://img.chenyingqiao.top/blog/20200816104713.png)

### undo log

重做日志记录了事务的行为，和redo log不同

undo log分为：
1. insert undo log 
2. update undo log

insert undo log 是在insert操作中的产生undo log。因为insert操作的记录，只对事务本身可见，其他事务不可见。


## 隔离级别

| 隔离级别  |  脏读 |不可重复读	 |幻读 |
|---|---|---|---|
|  读未提交 |  是 |是 |是 |
|  不可重复读 |  否 |是 |是 |
|  可重复读 | 否 |否 |是（innodb否） |
|  串行化 |  否 |否 |否 |


innodb可重复读中解决幻读是通过下面两种

主要通过以下两种情况避免幻读

**普通读**：快照读（非阻塞读）伪MVCC
表象避免幻读，是RR下查找数据，第一次读取创建快照，后面读取都是读取本次快照，不论别的事务是否提交相关更改，我们都不知道，掩耳盗铃

> 因为普通读是不会加锁的读，故不会有next-key locks的使用，解决幻读的手段是MVVC

**当前读**：next-key锁（行锁+gap锁）
上了锁，你别的操作不会修改我锁定的区间了，我就

> 所谓当前读，指的是加锁的select(S或者X), update, delete等语句。在RR的事务隔离级别下，数据库会使用next-key locks来锁住本条记录以及索引区间。


拿上面那个例子来说，在RR的情况下，假设使用的是当前读，加锁了的读

select * from table where id>3 锁住的就是id=3这条记录以及id>3这个区间范围，锁住索引记录之间的范围，避免范围间插入记录，以避免产生幻影行记录。

**脏读**

事务A读取了事务B更新的数据，然后B回滚操作，那么A读取到的数据是脏数据

![20200816143637](http://img.chenyingqiao.top/blog/20200816143637.png)


**不可重复读**

事务 A 多次读取同一数据，事务 B 在事务A多次读取的过程中，对数据作了更新并提交，导致事务A多次读取同一数据时，结果 不一致。


![20200816144642](http://img.chenyingqiao.top/blog/20200816144642.png)

**幻读**
{'fraction_non_alphanumeric': 0.14187741105872267, 'fraction_numerical': 0.05829404200600086, 'mean_word_length': 5.2061170212765955, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5649502', 'n_tokens_mistral': 3398, 'n_tokens_neox': 3740, 'n_words': 309}