Mysql - 日志相关问题

通过一些问题来讨论 Mysql 中的日志:

  • Mysql 是怎么保证原子性的?
  • Mysql 怎么保证持久性的?
  • Mysql 怎么保证隔离性的?
  • 介绍一下 binlog 和 redo log,他们两有啥区别?
  • 两阶段提交了解吗?介绍一下,为啥需要两阶段提交呢?
  • 幻读了解吗?介绍一下,innodb引擎是如何解决幻读问题等?
  • 刚才我们说到了原子性,那宕机时还能保证原子性吗?undolog在宕机是怎么保证原子性的?

1. Mysql 是怎么保证原子性的

原子性是说,事务中的操作,要不一起执行完成,要不一起失败回滚

Mysql 主要是利用 undo log,也就是回滚日志来实现原子性

平常我们在对数据进行增删改时,InnoDB 除了会记录 redo log,还会将更新的数据记录写进 undo log 中。当事务出现异常,执行失败的时候,就可以利用 undo log 中的信息将数据回滚到修改之前的版本

2. Mysql 是怎么保证持久性的

持久性是指,一旦事务提交,它对数据库的改变就应该是永久性的,接下来的其他操作或故障不能对其有影响。InnoDB 中主要是通过 redo log 来保证事务的持久性。其中还用到 WAL 技术

WAL 的关键点在于 Mysql 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。具体来说,当有一条记录需要更新时,InnoDB 引擎会先把记录写到 redo log 里,并更新内存,这个时候整个记录的更新就算完成了。后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool(缓存池) 里的脏页刷新到磁盘里,这个时候往往也是系统空闲的时候

有了 redo log,当系统崩溃时,即使脏页的数据没有来得及持久化,但 redo log 已经持久化了,Mysql 就可以根据 redo log 记录的内容,将所有的数据恢复到最新状态,整个过程也就是常说的 crash - safe能力

3. Mysql 是怎么保证隔离性

隔离性是指一个事务内部的操作以及操作的数据对正在进行的其他事务是隔离的,并发执行的各个事务之间不能相互干扰。隔离性可以防止多个事务并发执行,可能存在交叉执行导致数据的不一致。Mysql 对隔离性的保证主要有两个方面,用锁机制来保证一个事务写操作对另一个事务写操作的隔离性,用 MVCC(多版本并发控制)机制来保证一个事务写操作对另一个事务读操作的隔离性。

Mysql 中按锁的粒度,可以分为全局锁,表锁和行锁。全局锁会使整个数据库处于只读状态,在做全库逻辑备份时经常用到。表级锁在操作数据时会锁定整张表,并发性能一般,而行锁可以做到只锁定需要操作的记录行,并发性能很好。但是由于加锁本身需要消耗资源,因此某些锁定数据较多的情况下可以使用表锁来减少开销

MVCC 主要解决的是读写冲突的问题,它是由 undolog + read view 实现的。其中 undo log 可以为每条记录保持多个历史版本,Mysql 在执行快照读的适合,会根据事务的 read view 里的信息,顺着 undo log 的版本链找到满足可见性的记录

4. 介绍一下 binlog 和 redo log,他们两有啥区别?

redo log 是 InnoDB 独有的,记录的是某个数据页做了什么修改,每执行一个事务就会产生相应的 redo log。当事务提交时,只需将 redo log 持久化到磁盘即可,可以暂时不考虑将 Buffer Pool 的脏页写回,而是在合适的时间交给后台线程去做。系统故障崩溃时,Mysql 也可以在重启之后利用 redo log 里的内容恢复数据

bin log 是 Server 层的日志,记录的是所有数据库表结构变更和表数据修改的日志。在 Mysql 完成一条更新操作后,Server 层都会生成一条 binlog,在事务提交时,会将事务整个执行过程中产生的所有 bin log 统一写进 bin log 文件,主要用于 "归档"

两者的区别主要有四点,首先是适用对象不同,binlog 是在 Mysql 的 Server 层实现的,所有存储引擎都可以使用。而 redo log 是 InnoDB 引擎独有的;其次是文件格式不同,bin log 是逻辑格式,记录的是某个语句的原始逻辑,比如 "给 ID 为 1 的 A 字段加 1"。而 redo log 记录的是某个数据页上做的修改,比如 "某个表空间中的某个数据页的某个偏移量处做了什么更新" ; 还有写入方式不同,bin log是追加写,写满一个文件就在创建一个新文件继续写。而 redo log 是循环写,日志空间的大小是固定的,写满就需要先刷脏页,然后继续从头写

5. 两阶段提交了解吗?介绍一下,为啥需要两阶段提交呢?

两阶段提交在 Mysql 中主要用来确保 redo_log 和 bin_log 在逻辑上保持一致

具体的,在 Mysql 中执行一条更新语句时,在语句执行的最后需要分别写 redo log 和bin log。Mysql 把整个过程分为两个部分:首先先写 redo log,并把它标为 prepare 状态,然后紧接着写 bin log,等到 bin log 写完之后再将 redo log 标记为 commit 状态。这样的话,无论写日志的过程中 redo log 和 bin log 哪个环节出现问题,在崩溃恢复时都会保证两个日志系统的数据一致性

比如,假设写 redo log 处于 prepare 阶段之后,写 bin log 之前发生了崩溃,由于此时 bin log 还没有写,redo log 还没有提交,崩溃恢复时整个事务就会回滚。再假设 bin log 写完了,但 redo log 还没 commit 就崩溃了,这时在崩溃恢复时系统会判断 bin log 是否是完整的,如果完整,则提交事务,不完整则回滚事务

其实两阶段提交不仅存在于 Mysql 中,它还是分布式系统中用于保证事务一致性的协议。关键在于需要保证提交阶段所有参与者的操作要么都提交成功,要么都回滚

6. 幻读了解吗?介绍一下,innodb引擎是如何解决幻读问题等?

幻读是指一个事务在前后两次使用当前读查询同一个范围的时候,后一次的查询看到了前一次查询没有看到的新插入的行。需要注意的是,在 RR(可重复读)隔离级别下的普通查询是快照读,利用 MVCC(多版本并发控制)确保了别的事务不好插入数据,所以幻读在 "当前读" 下才会出现

幻读会带来两个问题,首先是会破坏给目标行加锁的语义,导致出现新的目标行被修改;其次是会导致数据和日志在逻辑上的不一致,即使把所有的记录都加上锁,还是可能会被其他事务插入新的记录

InnoDB 为了解决幻读,引入了间隙锁,这样在使用 select ... for update 语句时,会在对目标行加锁的同时,不仅加了行锁,还给两边的间隙页加上了间隙锁,这些就确保了无法再插入新的记录,同时已存在的数据页不能更新成间隙内的数据,也就不会出现幻读情况

不过也有一些特殊情况,比如事务 A 在 T1 时刻进行快照读,事务 B 在 T2 时刻插入记录,事务 A 在 T3 时刻进行当前读,此时会查出事务 B 新增的记录,也就出现了幻读。为了避免这类特殊场景下的幻读,最好是在事务开启之后,立马执行 select ... for update 这类当前读语句

7. 刚才我们说到了原子性,那宕机时还能保证原子性吗?undolog在宕机是怎么保证原子性的?

可以的,由于每个事务执行时,在提交事务前都会将对数据的修改记录到 undo log 中,现在假设 Mysql 正在执行某个事务时突然宕机,如果此时事务还没有提交,那么恢复时系统会通过 undo log 依次对数据进行相反操作,比如已经 insert 了一条记录,那么就 delete 这条记录等等,对这个未完成的事务进行回滚,确保事务的原子性;如果宕机前事务已经提交了,那么事务就无需回滚,一切正常


诚恳欢迎大家提出意见Orz

......(待续未完

相关推荐
无名之逆2 小时前
Hyperlane:Rust 生态中的轻量级高性能 HTTP 服务器库,助力现代 Web 开发
服务器·开发语言·前端·后端·http·面试·rust
Dnui_King2 小时前
Redis 持久化机制:AOF 与 RDB 详解
数据库·redis
jay丿2 小时前
Django 发送邮件功能详解
数据库·django·sqlite
shix .2 小时前
王者荣耀道具页面爬虫(json格式数据)
数据库·爬虫·json
A__tao2 小时前
在线 SQL 转 Flask-SQLAlchemy
数据库·sql·flask
04Koi.2 小时前
Redis--渐进式遍历
数据库·redis·缓存
Moment2 小时前
京东一面:postMessage 如何区分不同类型的消息 🤪🤪🤪
前端·javascript·面试
王佑辉3 小时前
【mysql】查事务进程
mysql
独行soc3 小时前
2025年渗透测试面试题总结-某四字大厂面试复盘 一面(题目+回答)
网络·python·科技·面试·职场和发展·红蓝攻防
铁打的阿秀3 小时前
navicat 创建Oracle连接报错:ora28040 没有匹配的验证协议
数据库·oracle