1️⃣ 索引

2️⃣ 事务

1️⃣ 事务由哪些特性?

原子性(Atomicity

  • 一个事务中的所有操作,要么 全部完成,要么 全部不完成,不会 结束在某个中间环节
  • 事务在执行过程中 发生错误,会被 回滚到事务开始前的状态

一致性(Consistency

  • 事务操作前后,数据满足完整性约束数据库保持一致性状态

隔离性(Isolation

  • 数据库 允许 多个并发事务 同时对其数据进行 读写修改 的能力,防止多个事 务并发执行 时由于 交叉执行 而导致数据的不一致

持久性(Durability

  • 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

2️⃣ InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?

  • 通过 redo_log 解决 持久性;
  • 通过 undo_log 解决 原子性;
  • 通过 MVCC 解决 隔离性;
  • 通过 持久性 + 原子性 + 隔离性 解决 一致性;

3️⃣ 并行事务会引发什么问题?

由于 MySQL 服务端允许多个 客户端 同时连接,这使得 MySQL 服务端会同时处理多个 事务

这就会造成 MySQL 服务端在多个 客户端事务 中,出现 脏读幻读不可重复读

4️⃣ 什么是脏读、幻读、不可重复读?

脏读:一个事务读取到了另一个未提交事务修改后的数据

不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的 数据不一样 的情况

幻读:在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的 记录数量 不一样的情况

5️⃣ 事务的隔离级别

  • 读未提交 read uncommitted:一个事务还没提交,它做的变更就可以被看到
  • 读提交 read committed:一个事务提交后,它做的变更才可以被看到
  • 可重复读 repeatable read:一个事务它执行的前后,看到的数据是一致的
  • 串行化 serializable:对记录加上读写锁,当多个事务并发读写该事务的时候,后一个事务必须等前一个事务执行完,才可以对这条记录进行操作。

6️⃣ Read_viewMVCC 是如何工作的?

Read_view 有四个重要字段:

  • creator_trx_id:创建该 Read_view 事务的 id;
  • m_ids:创建 Read_view 的那一时刻 ,当前数据库中 活跃事务事务 id 列表;
  • min_trx_id:当前数据库 活跃事务 中的 事务 id 最小 的事务;
  • max_trx_id:创建 Read View 时当前数据库中应该给下一个事务的 id 值;

聚簇索引包含两个隐藏列

对于使用 InnoDB 存储引擎的数据库表,它的 聚簇索引记录 中都包含下面两个隐藏列:

  • trx_id:当 一个事务对聚簇索引记录进行改动,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
  • roll_pointer:每次对聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中;

创建 Read View 后,可将记录中的 trx_id 划分以下三种情况

  • trx_id < min_trx_id:事务 id (trx_id) 是由 晚于 Read_view 版本之前的已经提交的事务生成的, 所以这版本的记录对事务是可以访问的。
  • trx_id > max_trx_id:事务 id (trx_id) 是由 先于 Read_view 版本之后的已经提交的事务生成的, 所以这版本的记录是对事务不可以访问的。
  • trx_id 位于 min_trx_idmax_trx_id 之间:判断 trx_id 是否位于 m_ids
    • 如果记录的 trx_idm_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
    • 如果记录的 trx_id 不在 m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

7️⃣ 可重复读是如何工作的?

可重复读隔离级别启动事务时 生成一个 Read View,然后整个事务期间都在用这个 Read View

8️⃣ 读提交是如何工作的?

读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View

9️⃣ MySQL 可重复读隔离级别,完全解决幻读了吗?

  • 针对 快照读(普通 select 语句),是通过 MVCC 方式解决了 幻读

    因为 可重复读隔离级别 ,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。

  • 针对 当前读select ... for update 等语句),通过 next-key lock记录锁 + 间隙锁)方式解决了 幻读

    因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读。

1️⃣0️⃣ 事务是否可以自动执行?

  当执行一条 “增删改” 语句的时候,虽然没有输入 begin 开启事务和 commit 提交事务,但是 MySQL隐式开启 事务来执行 “增删改” 语句的,执行完就自动提交事务。

3️⃣ 锁

4️⃣ 日志

背景:执行一条 update 语句,期间发生了什么?

1
UPDATE t_user SET name = 'xiaolin' WHERE id = 1;
  • 客户端通过 三次握手连接器 建立连接,连接器判断用户身份;
  • 因为客户端使用的是 update 语句,所以不需要经过查询缓存;
  • 解析器 通过 词法分析 提取 关键字,构建出 语法树; 再通过 语法分析 判断 SQL 语句是否合法;
  • 预处理器 判断 字段 是否存在;
  • 优化器 确定 执行计划,由于 id 是主键,执行主键索引;
  • 执行器 负责具体执行,找到这一行记录,然后更新;

在这其中,就用到了 undo logredo logbin log

日志种类

  • 回滚日志 undo log Innodb 存储引擎层生成的日志,实现了事务中的 原子性,主要用于 事务回滚MVCC;
  • 重做日志 redo log Innodb 存储引擎层生成的日志,实现了食物中的 持久性,主要用于 故障恢复;
  • 归档日志 bin log Server 层生成的日志,主要用于 数据备份主从复制;

undo log

背景:

  没有提交事务之前,如果 MySQL 发生了崩溃,需要回滚到事务之前的数据。

  实现这一机制就是 回滚日志 undo log,它保证了事务的 ACID 特性中的原子性 Atomicity

作用

  • 实现事务回滚,保障事务的原子性: 事务处理过程中,如果 出现了错误 或者 用户执行了 ROLLBACK 语句MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。
  • 帮助实现 MVCC(多版本并发控制)MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。

redo log

背景:

  为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(标记为脏页),再对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。后续,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里。

定义

  当 执行一个事务 就会产生这样的 一条或者多条物理日志。当 事务 commit ,可以先将 redo log 持久化到磁盘,不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。

redo logundo log 的区别

  • redo log 记录了此次事务 「修改后」 的数据状态,记录的是 更新之后的值,主要用于 事务崩溃恢复,保证 事务的持久性

  • undo log 记录了此次事务 「修改前」 的数据状态,记录的是 更新之前的值,主要用于 事务回滚,保证 事务的原子性

redo log 是直接写入磁盘的吗?

  redo log 也有自己的缓存 redo log buffer,每当产生一条 redo log 时,会先写入到 redo log buffer,后续在持久化到磁盘。

redo log 什么时候刷盘?

缓存在 redo log buffer 里的 redo log 是在 内存中 !!!

  • MySQL 正常关闭;
  • redo log buffer 中记录的 写入量 大于 redo log buffer 内存空间的一半;
  • InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘;
  • 事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘 (通过参数控制);
    • 参数为 0: 每次事务提交时,将 redo log 留在 redo log buffer 中,不主动触发写入磁盘的操作;
    • 参数为 1: 每次事务提交时,将缓存在 redo log buffer 里的 redo log 直接 持久化到磁盘;
    • 参数为 2: 每次事务提交时,将缓存在 redo log buffer 里的 redo log 直接 写入到操作系统中的 PageCache;

bin log

背景:

  MySQL 在完成一条更新操作后,Server 层还会生成一条 binlog。之后事务提交 的时候,会将该事物执行过程中产生的所有 binlog 统一写 入 binlog 文件。

主从复制

  MySQL 的主从复制依赖于 binlog ,也就是记录 MySQL 上的所有变化并通过 二进制形式 保存在磁盘上。复制的过程 就是将 binlog 中的数据从 主库 传输到 从库上。

  • MySQL 主库在收到客户端提交事务的请求之后,会先写入 binlog,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端“操作成功”的响应。
  • 从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应。
  • 从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。

两阶段提交

  • prepare 阶段:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘;
  • commit 阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘,接着调用引擎的 提交事务接口,将 redo log 状态设置为 commit

    commit 状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;