数据库常问问题
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_view 在 MVCC 是如何工作的?
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_id和max_trx_id之间:判断trx_id是否位于m_ids- 如果记录的
trx_id在m_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 log 、redo log、bin 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 log 和 undo 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;
- 参数为 0: 每次事务提交时,将
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也没有关系,一样会被认为事务已经执行成功;
