1、redo log(重做日志)
redo log(重做日志)是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
MySQL 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。
(1)刷盘时机
InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数,它支持三种策略:
0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作 (如果MySQL挂了或宕机可能会有1秒数据的丢失。)
1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
innodb_flush_log_at_trx_commit 参数默认为 1 ,也就是说当事务提交时会调用 fsync 对 redo log 进行刷盘另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。
也就是说,一个没有提交事务的 redo log 记录,也可能会被后台线程刷盘。
除了后台线程每秒1次的轮询操作,还有一种情况,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘。
1)为0时,如果MySQL挂了或宕机可能会有1秒数据的丢失。
2)为1时, 只要事务提交成功,redolog记录就一定在硬盘里,不会有任何数据丢失。如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。
3)为2时,只要事务提交成功,redologbuffer中的内容只写入文件系统缓存(pagecache)。如果仅仅只是MySQL挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。
(2)日志文件组
硬盘上存储的 redo log 日志文件不只一个,而是以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。比如可以配置为一组4个文件,每个文件的大小是 1GB,整个 redo log 日志文件组可以记录4G的内容。它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写
在个日志文件组中还有两个重要的属性,分别是 write pos、checkpointwrite pos 是当前记录的位置,一边写一边后移 checkpoint 是当前要擦除的位置,也是往后推移
每次刷盘 redo log 记录到日志文件组中,write pos 位置就会后移更新。每次 MySQL 加载日志文件组恢复数据时,会清空加载过的 redo log 记录,并把 checkpoint 后移更新。write pos 和 checkpoint 之间的还空着的部分可以用来写入新的 redo log 记录。
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log 记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。
(3)只要每次把修改后的数据页直接刷盘不就好了,还有 redo log 什么事?
实际上,数据页大小是16KB,刷盘比较耗时,可能就修改了数据页里的几Byte数据,有必要把完整的数据页刷盘吗?而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。如果是写redolog,一行记录可能就占几十Byte,只包含表空间号、数据页号、磁盘文件偏移量、更新值,再加上是顺序写,所以刷盘速度很快。所以用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。
其实内存的数据页在一定时机也会刷盘,我们把这称为页合
2、binlog(归档日志)
binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志。 可以说MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。 binlog会记录所有涉及更新数据的逻辑操作,并且是顺序写。
(1)记录格式
binlog 日志有三种格式,可以通过binlog_format参数指定。
- statement
- row
- mixed
指定statement,记录的内容是SQL语句原文,比如执行一条update T set update_time=now() where id=1
同步数据时,会执行记录的SQL语句,但是有个问题,update_time=now()这里会获取当前系统时间,直接执行会导致与原库的数据不一致。
为了解决这种问题,我们需要指定为row,记录的内容不再是简单的SQL语句了,还包含操作的具体数据,记录内容如下。 row格式记录的内容看不到详细信息,要通过mysqlbinlog工具解析出来。update_time=now()变成了具体的时间update_time=1627112756247,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(假设这张表只有 3 个字段)。
但是这种格式,需要更大的容量来记录,比较占用空间,恢复与同步时会更消耗IO资源,影响执行速度。所以就有了一种折中的方案,指定为mixed,记录的内容是前两者的混合。MySQL会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式。
(2)写入机制
binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlogcache写到binlog文件中。因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。 上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快上图的 fsync,才是将数据持久化到磁盘的操作
write和fsync的时机,可以由参数sync_binlog控制,默认是0。为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。 虽然性能得到提升,但是机器宕机,page cache里面的 binlog 会丢失。 为了安全起见,可以设置为1,表示每次提交事务都会执行fsync,就如同 redo log 日志刷盘流程 一样。最后还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write,但累积N个事务后才fsync。 在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。 同样的,如果机器宕机,会丢失最近N个事务的binlog日志。
3、两阶段提交
redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。
binlog(归档日志)保证了MySQL集群架构的数据一致性。
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样。
(1)redo log与binlog两份日志之间的逻辑不一致,会出现什么问题?
以update语句为例,假设id=2的记录,字段c值是0,把字段c值更新成1,SQL语句为update T set c=1 where id=2。
假设执行过程中写完redo log日志后,binlog日志写期间发生了异常,会出现什么情况呢?
由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。因此,之后用binlog日志恢复数据时,就会少这一次更新,恢复出来的这一行c值是0,而原库因为redo log日志恢复,这一行c值是1,最终数据不一致。
为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。 原理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redolog日志恢复数据时,发现redolog还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。
再看一个场景,redo log设置commit阶段发生异常,那会不会回滚事务呢? 并不会回滚事务,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。
可能会出现的问题
- 数据不一致性:当使用两阶段提交时,如果在提交事务的第二阶段出现错误,可能会导致数据不一致性。这种情况下,redo log和binlog记录的数据也可能是不一致的。
- 容错性:如果系统在执行两阶段提交时出现故障,可能会导致数据不一致性。在这种情况下,redo log和binlog可能会被破坏,无法用于数据恢复和复制。
- 性能问题:当执行大量事务时,使用两阶段提交可能会导致性能问题。在这种情况下,redo log和binlog的写入和读取速度可能会成为系统的瓶颈。
因此,在使用redo log和binlog记录数据恢复和复制时,需要考虑两阶段提交的影响,并使用适当的技术来确保数据的一致性和容错性,同时尽量减少性能问题的影响。
4、undo log(回滚日志)
我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。 另外,MVCC 的实现依赖于:隐藏字段、Read View、undo log。在内部实现中,InnoDB 通过数据行的 DB_TRX_ID 和 Read View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log
5、MVCC?
MVCC,英文全称Multiversion Concurrency Control,多版本并发控制。简单理解,就是相当于给我们的MySQL数据库拍个“快照”,定格某个时刻数据库的状态。
为了保证事务启动到结束整个生命周期看到的数据是一致的, 一般有两种方案:
(1)MySQL对数据“读-写”的时候,加锁,其他事务写这条数据时加上锁,其他事务读取的时候阻塞。
(2)MySQL可以对事务启动的时候,对数据库拍个“快照”,那么事务运行过程中读取都从这个快照读取,不也是保证数据一致么。
第一种方案存在明显的问题,加锁会引发阻塞,从而降低数据库性能。而MySQL设计者们采用第二种,也就是大名鼎鼎的MVCC,它不仅能够解决不可重复读,还一定程度解决幻读的问题,因为你整个数据库快照都有了,你就知道那个时刻的数据了。
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突 ,做到即使有读写冲突时,也能做到不加锁 , 非阻塞并发读,而这个读指的就是快照读 , 而非当前读。
什么是快照读和当前读?【十、数据库-当前读快照读】
MVCC机制是咋工作的呢?
(1)数据的多个版本
undo log保存了数据的各个历史版本,用于数据的回滚,保证事务的一致性。
对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列: trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里; roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到undolog日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。 所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,根据版本链就可以找到这条数据历史的版本。
(2)一致性视图ReadView
- trx_ids: 指的是在创建 ReadView 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表, “活跃事务”指的就是,启动了但还没提交的事务。
- min_trx_id: 指的是在创建 ReadView 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
- max_trx_id:这个并不是 m_ids 的最大值,而是创建 ReadView 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
- creator_trx_id :指的是创建该 ReadView 的事务的事务 id, 只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为 事务分配事务id,否则在一个只读事务中的事务id值都默认为0。
对于当前事务的启动瞬间来说,读取的一个数据版本的trx_id,有以下几种可能:
(1)如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。 (2)如果小于min_trx_id,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的; (3)如果大于max_trx_id,表示这个版本是由将来启动的事务生成的,是肯定不可见的; (4)如果大于min_trx_id小于max_trx_id,那就包括两种情况 (4.1)若 数据的trx_id在trx_ids数组中,表示这个版本是由还没提交的事务生成的,不可见,去读取这条数据的历史版本,这条数据的历史版本中都包含了事务id信息,去查找第一个不在活跃事务数组的版本记录。 (4.2)若 数据的trx_id不在trx_ids数组中,表示这个版本是已经提交了的事务生成的,可见。
这种通过版本链 + 一致性视图 来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制),现在你明白MySQL如何实现了“秒级创建快照”的能力了吧。
6、什么是执行计划 EXPLAIN?
执行计划 是指一条 SQL 语句在经过 MySQL 查询优化器 的优化会后,具体的执行方式。执行计划通常用于 SQL 性能分析、优化等场景。通过 EXPLAIN 的结果,可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引可以被命中、哪些索引实际会命中、每个数据表有多少行记录被查询等信息。
EXPLAIN 执行计划支持 SELECT、DELETE、INSERT、REPLACE 以及 UPDATE 语句。我们一般多用于分析 SELECT 查询语句 id SELECT查询的序列标识符 select_type SELECT关键字对应的查询类型 table 用到的表名 partitions 匹配的分区,对于未分区的表,值为 NULL type 表的访问方法 possible_keys 可能用到的索引 key 实际用到的索引 key_len 所选索引的长度 ref 当使用索引等值查询时,与索引作比较的列或常量 rows 预计要读取的行数 filtered 按表条件过滤后,留存的记录数的百分比 Extra 附加信息
1、type(重要)
查询执行的类型,描述了查询是如何执行的。所有值的顺序从最优到最差排序为:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL 常见的几种类型具体含义如下:
(1)system:如果表使用的引擎对于表行数统计是精确的(如:MyISAM),且表中只有一行记录的情况下,访问方法是 system ,是 const 的一种特例。
(2)const:表中最多只有一行匹配的记录,一次查询就可以找到,常用于使用主键或唯一索引的所有字段作为查询条件。
(3)eq_ref:当连表查询时,前一张表的行在当前这张表中只有一行与之对应。是除了 system 与 const 之外最好的 join 方式,常用于使用主键或唯一索引的所有字段作为连表条件。
(4)ref:使用普通索引作为查询条件,查询结果可能找到多个符合条件的行。
(5)index_merge:当查询条件使用了多个索引时,表示开启了 Index Merge 优化,此时执行计划中的 key 列列出了使用到的索引。
(6)range:对索引列进行范围查询,执行计划中的 key 列表示哪个索引被使用了。
(7)index:查询遍历了整棵索引树,与 ALL 类似,只不过扫描的是索引,而索引一般在内存中,速度更快。
(8)ALL:全表扫描
2、key(重要)
key 列表示 MySQL 实际使用到的索引。如果为 NULL,则表示未用到索引。
3、Extra(重要)
这列包含了 MySQL 解析查询的额外信息,通过这些信息,可以更准确的理解 MySQL 到底是如何执行查询的。常见的值如下:
(1)Using filesort:在排序时使用了外部的索引排序,没有用到表内索引进行排序。
(2)Using temporary:MySQL 需要创建临时表来存储查询的结果,常见于 ORDER BY 和 GROUP BY。
(3)Using index:表明查询使用了覆盖索引,不用回表,查询效率非常高。
(4)Using index condition:表示查询优化器选择使用了索引条件下推这个特性。
(5)Using where:表明查询使用了 WHERE 子句进行条件过滤。一般在没有使用到索引的时候会出现。
(6)Using join buffer (Block Nested Loop):连表查询的方式,表示当被驱动表的没有使用索引的时候,MySQL 会先将驱动表读出来放到 join buffer 中,再遍历被驱动表与驱动表进行查询。
这里提醒下,当 Extra 列包含 Using filesort 或 Using temporary 时,MySQL 的性能可能会存在问题,需要尽可能避免