ACID

目的:Consistency——一致性 手段

  • Atomic——原子性
  • isolution——隔离性
  • durability——持久性

实现

持久性:redolog + binlog 原子性:undolog 隔离性:mvcc + 锁 + undolog

持久性

一条更新语句的执行步骤

server层执行器 innodb引擎层
  1. 查询指定数据 | | | |
  2. 从buffer pool中查询数据 | | | 3.1 查到则直接返回数据页 3.2 查不到则从磁盘中寻找数据页返回 | |
  3. 更新对应数据 | | |
  4. 将数据写回磁盘 | | | |
  5. 将数据写到buffer pool | | |
  6. 记录redolog,标记为prepare状态 | |
  7. 记录binlog | | |
  8. 调用提交接口 | | | |
  9. 将redolog对应数据标记为commit状态 |

这里是如何保证crash-safe呢?我们需要关注数据库在以下几个节点发生crash的情况

  1. 步骤8之前,数据没有记录在日志文件中
  2. 步骤8-9之间,日志记录到了redolog,但没有记录到binlog
  3. 步骤9之后,日志记录到了日志文件中

情况1,日志文件中根本没有数据,该事务执行rollback 情况2,只记录到了redolog而没有记录到binlog,执行rollback 情况3,redolog和binlog都已经记录的该事务,将redolog对应数据标记为commit,继续提交事务 这里的redolog其实只是更新到了redolog pool内存中,具体何时刷新到redolog_file中,是根据checkpoint机制来的

checkpoint机制

既然redolog日志和数据都是先进内存再更新到磁盘,为什么要多这一步呢?redolog的好处在哪?

  1. redolog是顺序写,而数据文件是随机写,随机写速度慢
  2. redolog并不需要把整个数据页都更新,io少

原子性

一条更新语句会先记录undolog,在对数据进行更新。这样在事务最终进行回滚时,就会根据undolog来进行回滚。

隔离性

MVCC

事务隔离级别 读未提交——read uncommit(RU) 读已提交——read commit(RC) 可重复读——repeat read(RR) 串行 ——serial

事务1 事务2
truncate user;
start transaction start transaction
insert into user values(1);
// 一
select count(*) from user
commit;
// 二
select count(*) from user
commit;
// 三
select count(*) from user
RU RC RR Serial
sql一 1 0 0 报错
sql二 1 1 0 报错
sql三 1 1 1 1

RU模式能读到未提交的数据,成为脏读 RC模式下,在同一个事务中,两次读到的结果不一样,称为不可重复读 RR模式下,事务之内读到的数据和事务之外的数据不一样,称为幻读 那么模式是如何实现的呢?这里要介绍两个概念 快照读 select * from user 当前读 update user set id = 2 where id = 1; delete from user where id = 1; select * from user where id = 1 for update;

ReadView(快照读)机制如下

在RC模式下,每次读都会创建一个readview 在RR模式下,每个事务只会创建一个readview 举例:

Lock

锁的分类

概念介绍

意向锁:为了简化加表锁的步骤,当需要加表锁时,需要遍历表中每行数据,数据都没锁才能加表锁,效率过慢,当加行锁的时候,首先在表上添加上意向锁 读锁:共享锁,行锁 写锁:互斥锁,行锁 自增锁:主键自增时加的锁 间隙锁:范围锁,又称GAP,前开后开 临键锁:范围锁,又称Next-key,前闭后开

读、写、意向锁的互斥关系如下

读锁 写锁 意向读锁 意向写锁
读锁 X X
写锁 X X X X
意向读锁 X
意向写锁 X X

update student set age = 10 where id = 13 age的索引情况和数据库隔离级别,加锁的情况也不同

索引情况 db隔离级别 加锁情况
id为主键 RC
  1. 根据主键索引找到id=13的数据,在该索引上加写锁 | | id为唯一索引 | |
  2. 根据唯一索引找到id=13的数据,在该索引上加写锁
  3. 将这一行的主键索引上加写锁 | | id为普通索引 | |
  4. 根据索引找到id=13的所有数据,在该索引上加写锁
  5. 在锁住的数据中找到主键列,在主键列的唯一索引上加写锁 | | id不是索引 | |
  6. 对表中的每条数据都加锁 | | id为主键 | RR | 同上 | | id为唯一索引 | | 同上 | | id为普通索引 | |
  7. 根据索引找到id=13的所有数据,在该索引上加写锁
  8. 在加锁数据的前后间隙,加上GAP锁
  9. 在锁住的数据中找到主键列,在主键列的唯一索引上加写锁 | | id不是索引 | |
  10. 对表中的每条数据都加锁
  11. 对表中每条数据间隙加上GAP锁 |

复杂语句分析 table: user(id, age, userId, comment) index:idx_user_age(age, userId) delete from user where age > 1 and age < 20 and userid = 'hdc' and comment is not null;

参考文档

https://hoxis.github.io/mysql-zhuanlan-02-redolog-binlog.html https://www.cnblogs.com/wupeixuan/p/11734501.html https://zhuanlan.zhihu.com/p/52977862 https://km.sankuai.com/page/231603432