Mysq事务日志面试常问点

216 阅读10分钟

\

面试官说,小伙子来,简单聊聊mysql的那些日志。

面试官:redo log,undo log,binlog有了解过没有。

候选者:没有

面试官:额......稀巴烂。

怎样保证ACID

  • redo log保证了原子性和持久化,undo log保证了一致性,锁和mvcc保证了事务隔离性。

事务隔离级别

  • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
  • 已提交读(Read Committed):只能读取到已经提交的数据。针对一个事务里面的多个sql都会产生一个新Read View(Oracle等多数数据库默认都是该级别,写的时候加的是排它锁)
  • 可重复读(Repeated Read):可重复读。InnoDB默认级别。针对一个事务只会生成一个Read View,后面的sql都是用前面生成的(写的时候加的是排它锁)
  • 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

怎样选择隔离级别

  • 未提交读和串行读:串行读是对所有数据加锁然后串行读,效率不高。未提交读会产生脏读,都不推荐使用;
  • 已提交读和可重复读对比

1、 在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多。

2、 锁都是基于索引的,如果没有命中索引就会锁表,在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,会先全部锁住,然后再把不符合的记录释放,命中的会只锁行。

3、 RC 与 RR 在复制方面的区别: RC 隔离级别默认 statement 格式的bin log,因为该格式的复制,会导致主从数据的不一致;只能使用 mixed 或者 row 格式的bin log; 这也是为什么MySQL默认使用RR隔离级别的原因。

4、 RC 支持半一致性读,RR不支持:RC隔离级别下的update语句,使用的是半一致性读(semi consistent);而RR隔离级别的update语句使用的是当前读;当前读会发生锁的阻塞。

5、 RC 与 RR 在锁方面的区别:显然 RR 支持 gap lock(next-key lock),而RC则没有gap lock。因为MySQL的RR需要gap lock来解决幻读问题。而RC隔离级别则是允许存在不可重复读和幻读的。所以RC的并发一般要好于RR。(select for update,update,delete都是当前读。正常的select都是快照读。RC级别下update是半一致性读)

半一致性读:就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!

场景

  • 不可重复读的场景是修改,幻读在新增或者删除场景

redo log

redo log是啥子,有啥作用?

  • redo log记录日志是顺序写,效率很高,所以在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到最新的状态。一句话就是提供持久化并保证数据一致性。
  • 详解:当数据库对数据做修改的时候,需要把数据页从磁盘读到buffer pool中,然后在buffer pool中进行修改,那么这个时候buffer pool中的数据页就与磁盘上的数据页内容不一致,称buffer pool的数据页为dirty page 脏数据,如果这个时候发生非正常的DB服务重启,那么这些数据还没在内存,并没有同步到磁盘文件中(注意,同步到磁盘文件是个随机IO),也就是会发生数据丢失,如果这个时候,能够在有一个文件,当buffer pool 中的data page变更结束后,把相应修改记录记录到这个文件(注意,记录日志是顺序IO),那么当DB服务发生crash的情况,恢复DB的时候,也可以根据这个文件的记录内容,重新应用到磁盘文件,数据保持一致。

两阶段提交

  • 写入 redo log 分为两个步骤,prepare 和 commit,这就是“两阶段提交”。

假设没有两阶段提交会发生什么问题

  • 先写 redo log,再写 binlog,假设 redo log 写完,binlog 还没写完,MySQL 进程异常重启,redo log 写完后,即使系统崩溃,仍然能把数据恢复回来,所有恢复后的数据是正确的。但是 binlog 没写完,这时候 binlog 中就没有记录这条语句的操作,因此,之后备份日志的时候,binlog 就没有这条操作记录,如果用这个 binlog 来恢复临时库的话,由于这条语句记录的丢失,临时库就会少了这一个语句的操作,恢复出来的数据就与原库的值不同。
  • 先写 binlog,再写 redo log,如果在 binlog 写完后系统崩溃了,由于 redo log 还没写,崩溃后这个事务无效,所以磁盘数据文件中的数据是没有这条语句的操作的,但是 binlog 中已经做了记录,所以以后用这个 binlog 来做数据恢复时,就多了一个事务操作,与原库的数据不一致。
  • 如果没有“两阶段提交”,会导致 redo log 和 binlog 记录的操作不一致。保证 redo log 和 binlog 的操作记录一致的流程是,将操作先更新到内存,再写入 redo log,此时标记为 prepare 状态,再写入 binlog,此时再提交事务,将 redo log 标记为 commit 状态。

Redo Log写入机制

在Redo Log日志信息从Redo Buffer持久化到Redo Log时,具体的持久化策略可以通过innodb_flush_log_at_trx_commit 参数进行设置,具体策略如下所示。

  • 0:每秒提交 Redo buffer ->OS cache -> flush cache to disk,可能丢失一秒内的事务数据。由后台Master线程每隔 1秒执行一次操作。
  • 1(默认值):每次事务提交执行 Redo Buffer -> OS cache -> flush cache to disk,这种方式最安全,性能最差。
  • 2:每次事务提交执行 Redo Buffer -> OS cache,然后由后台Master线程再每隔1秒执行OS cache -> flush cache to disk 的操作。

binlog

binlog是啥子,有啥作用?

  • 主从同步:在主数据库上开启binlog,主数据库把binlog发送至从数据库,从数据库获取binlog后通过I/O线程将日志写到中继日志(Relay log)中,然后通过SQL线程将Relay中的数据同步至从数据库。
  • 数据恢复:当MYSQL发生故障或者崩溃时,可以通过BinLog进行数据恢复。(例如,mysqlbinlog工具进行数据恢复)

日志记录方式

  • Row模式:非常清楚的记录每一行数据的修改情况,完全实现了主从数据库的同步和数据的恢复,但是在大批量操作的时候,会产生许多二进制日志。
  • Statement模式:不记录具体的修改细节,只记录对应的sql,所以日志数据量比较小。但是在碰到类型now()等函数时可能会导致主从数据不一致。
  • Mixed模式:mixed模式是row和statement模式的混用,当判断没有使用now()等函数时就使用statement模式,如果使用了则使用row模式。

Binlog写机制

根据记录模式和操作触发event事件生成log event(事件触发执行机制)。

  • 将事务执行过程中产生的日志时间(log event)写入缓冲区,每个事务线程都有一个缓冲区。Log Event保存在一个binlog_cache_mngr数据结构中,在该结构中有两个缓冲区,一个是stmt_cache,用于存放不支持事务的信息;另一个是trx_cache,用于存放支持事务的信息。
  • 事务在提交阶段会将产生的log event写入到外部binlog文件中。不同事务以串行方式将log event写入Binlog文件中,所以一个事务包含的log event信息在binlog文件中是连续的,中间不会插入其他事务的log event。

undo log

undo log是啥子,有啥作用?

  • 主要起到两方面作用:回滚事务和多版本并发事务(mvcc)。
  • 在开启事务之前,会先将要修改的数据记录到Undo log中,如果数据库库崩溃,则可以通过undo og来进行回滚操作,从而保证数据的一致性。

redo log 与 binlog区别

    1. redo log 是InnoDB 引擎特有的;而 binlog 是MySQL Server 层实现的,他们都可以在一定程序上恢复数据  
    1. redo log 是物理日志,记录的是“在某个数据页做了什么修改”;而 binlog 是逻辑日志,记录的是语句的原始逻辑。比如 update T set c=c+1 where ID=2;这条SQL,redo log 中记录的是 :xx页号,xx偏移量的数据修改为xxx;binlog 中记录的是:id = 2 这一行的 c 字段 +1 
  • 3. redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
    1. 记录内容时间不同,redo log 记录事务发起后的 DML 和 DDL语句;binlog 记录commit 完成后的 DML 语句和 DDL 语句
    1. 作用不同,redo log 作为异常宕机或者介质故障后的数据恢复使用;binlog 作为恢复数据使用,主从复制搭建。

日志记录的顺序

    1. 把需求修改的数据更新到内存
    1. 记录undo.log日志
    1. 修改数据内存中的数据
    1. 记录redo.log,并把日志状态改成prepare
  • 5. 记录binlog
  • 6. 提交事务
    1. 记录redo.log,并把日志状态改成commit,根据刷盘机制持久化到磁盘。

提交事务的流程

  • 清理undo段信息: 对于innodb存储引擎的更新操作来说,undo段需要purge,这里的purge主要职能是,真正删除物理记录。在执行delete或update操作时,实际旧记录没有真正删除,只是在记录上打了一个标记,而是在事务提交后,purge线程真正删除,释放物理页空间。因此,提交过程中会将undo信息加入purge列表,供purge线程处理。
  • 释放锁资源: mysql通过锁互斥机制保证不同事务不同时操作一条记录,事务执行后才会真正释放所有锁资源,并唤醒等待其锁资源的其他事务;
  • 刷redo日志: 前面我们说到,mysql实现事务一致性和持久性的机制。通过redo日志落盘操作,保证了即使修改的数据页没有即使更新到磁盘,只要日志是完成了,就能保证数据库的完整性和一致性;
  • 清理保存点列表: 每个语句实际都会有一个savepoint(保存点),保存点作用是为了可以回滚到事务的任何一个语句执行前的状态,由于事务都已经提交了,所以保存点列表可以被清理了。

本篇许多都是通过冰河出版的《深入理解分布式事务:原理与实战》一书学习的,同学们一起进步呀。