redo log && undo log崩溃恢复过程中的结合使用
-
事务执行阶段:
- 当事务执行时,InnoDB存储引擎会将数据的物理变化写入redo log,并将此操作标记为未提交。同时,执行的SQL语句(逻辑变化)会被写入binlog。
- redo log几乎是实时写入的,而binlog可以在事务提交时才写入。
-
崩溃发生时:
- 如果数据库发生崩溃,部分事务可能已经修改了数据但还没有提交,这时redo log中已经记录了这些未提交事务的物理变化。
- 在数据库重启进行恢复时,InnoDB存储引擎会首先查看redo log,重做(redo)所有已记录的修改,以确保所有修改过的数据页反映了最后一次已知的事务状态,这保证了数据的持久性。
-
恢复到一致状态:
- 接下来,数据库需要确保处于一致的状态。这时,它会检查binlog,找出在崩溃时尚未完成的事务,并将这些事务回滚(undo),这一步确保了数据库的一致性。
只使用其中之一的问题
-
只使用redo log:虽然redo log能够确保事务的持久性,即在崩溃后能够恢复数据的物理状态,但它无法提供足够的信息来支持数据的复制和逻辑恢复。特别是在需要进行数据复制或者精确控制事务回滚到特定点的场景下,仅有物理变化的信息是不够的。
-
只使用binlog:如果只使用binlog,在崩溃恢复时,虽然可以知道哪些SQL语句被执行过,但由于binlog是逻辑日志,它无法提供足够的信息来快速恢复数据到最后一次已知的状态,特别是在数据量大的情况下,重新执行所有SQL语句的效率非常低下。此外,binlog默认不是实时写入的,如果在事务提交前数据库崩溃,那么这些变更可能不会被记录在binlog中。
结论
因此,在崩溃恢复的场景下,redo log和binlog通常是结合使用的,以利用redo log的快速物理恢复能力和binlog的逻辑恢复及数据复制能力。这种结合使用的方式,确保了数据库在发生崩溃时能够快速、有效地恢复到一致和准确的状态。
mvcc
当前读(加锁读的是最新数据)、快照读(通过mvcc实现,可以不加锁的实现并发读写)
mvcc是靠read view、undo log、三个隐藏字段(innodb给每行数据加了三个隐藏字段:修改该行时的事务id,回滚指针指向上一个undo log上一个版本、隐藏的自增id)实现的
-
判断当前读到的版本是否可见:当某一个事务开始快照读时,假设该事务的读到的某一个版本行事务id为a(该行记录是事务a读写的)。read view中有如下几个关键字段:up_limit_id,记录此时活跃的最小事务id,小于该id的对当前事务a都是可见的;down_limit_id记录当前活跃的最大事务id+1,也就是下一个要分配的事务id,若当前事务id>=down_limit_id,说明当前行时快照生成时的未来事务修改的,当前快照读不可见。如果位于up和down之间,判断是否位于tx_list(生成快照是活跃的事务列表),如果是,代表生成快照时,这些事务还未提交,该行还不可见
-
若不可见,则按回滚指针指向undo log中的上一个版本,再次重复上述逻辑判断该版本是否可见,直到找到可见版本
-
rr和rc的区别是生成快照的时机不同,rr是第一次快照读时就会生成一次read view之后都会用该快照。rc是每次快照读都会生成一个read view,所以会有不可重复读的问题
-
mysql执行过程
链接器 -> 查看缓存(很鸡肋,8.0已删除)-> 解析器 ->预处理器(看表,字段是否存在,将*替换为字段值)->优化器->执行器 -
mysql存储结构
.frm为表结构,.ibd文件为表数据的独立空间
->
ibd从小到大的结构为:行 -> 页(按页读取,16KB) ->区(1MB,按区分配空间。避免叶子节点磁盘随机读取)-> 段(数据段、索引段、回滚段) -
一行的格式
Compact结构举例:
从左到右依次:可变成字段的长度(逆序)-> NULL值字段->记录头信息(包含:指向下一行记录、是否被删除标记等)->rowid(若指定了主键,则不需要)->trxid(由哪个事务产生的)-> 指向上一个版本 -> 真实数据
-
行溢出
当一行的数据大于16KB页大小后,会存到溢出页中,然后存放真实数据的位置通过一个20字节的指针指向溢出页 -
B数多叉数(M),每个节点存M-1个数据和M个节点
B数理想情况可以一次遍历就获取到数据,但由于单个节点既存索引也存数据,层数会比B+树高。且B+数底层通过双向链表链接,便于范围查询。B+数每一个节点都是冗余索引,删除时树结构变更小 -
索引优化
逐渐自增:避免也分裂,执行需要开辟新页就好。覆盖索引。避免失效。 -
索引失效
查询条件使用函数,表达式、隐式类型转换、不满足最左匹配、or、like %s左模糊、!=、Null也会影响 -
count(1)和count(*)为啥快
InnoDB 循环遍历聚簇索引(主键索引),将读取到的记录返回给 server 层,但是不会读取记录中的任何字段的值,因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。参数 1 很明显并不是 NULL,因此 server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1。 -
MySQL 里除了普通查询是快照读,其他都是当前读,比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。
-
Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了间隙锁。
-
innodb myisam对比
事务支持、表所与行锁、是否必须有主键(前者没有指定会自动身材隔行)、索引(前者聚集索引,后者非聚集索引)、外键、查询行数(后者有字段专门存了行数,查询复杂度为O(1))
InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。 -
锁
全局锁、表锁(表锁、意向锁【快速判断表中是否有记录添加了读/写锁,否则需要逐行遍历】、MDL锁【元数据锁】)、行锁(记录、间隙、next-key) -
插入意向锁
防止多个事务插入统一间隙时频繁锁冲突。
多个不同的事务,同时往同一个索引的同一个间隙中插入数据的时候,它们互相之间无需等待,即不会阻塞 -
行锁
行锁是加载索引上的。加锁的基本单位是next key lock。对于唯一索引,有些情况下会会退化为记录锁和间隙锁
扫描到第一条不符合条件的二级索引记录,该二级索引的 next-key 锁会退化成间隙锁。
唯一索引等值查询:有记录(退化为记录锁)、无记录(退化为间隙锁)
在线上在执行 update、delete、select … for update 等具有加锁性质的语句,一定要检查语句是否走了索引,如果是全表扫描的话,会对每一个索引加 next-key 锁,相当于把整个表锁住了 -
几种日志
undo log
作用: 为了保证事务的原子性,事务失败回滚靠的是该日志;另一方面mysql的mvcc实现也是依赖他
结构:每行数据有trx_id(创建改行数据的事务id)、roll_pointer指向上一个版本记录,构成版本链。通过undo log,保存了数据更新前的版本记录
事务失败时,顺着这条版本链找到上一个版本的数据,然后回滚
redo log
1.作用:保证事务的持久性,使mysql具备crash-safe故障恢复能力;将随机写转换为顺序写(redo log追加写)磁盘,加快效率
mysql每次执行sql语句,并不是直接与磁盘进行交互,效率太低。引入buffer pool(innodb存储引擎引入的),当执行sql时(查询或更新)。如果buffer pool中没有,回去磁盘中以页为单位加载到buffer pool中,下一次查询相同内容可以从buffer pool直接返回,免去了异步io操作;对于更新操作,修改数据时,会先修改buffer pool中的对应数据并将buffer pool中的数据页标记为脏页,有后台线程定期将脏页中的数据回刷到磁盘中。
2.此时问题出现:当断电等故障出现时,内存中的数据会被清除,会出现丢失数据的情况。redo log就是为了解决该问题的,更新buffer pool的内存页后,会先写入redo log,并不会立即写入磁盘,也就是WAL,在写入磁盘前先记录日志。这样即使断电,也能根据redo log进行重写内容到磁盘中。
3 redo log本身的刷盘策略,redo log并不是每次数据变更都会写入磁盘的。每次数据变更会存入redo log buffer。每一秒会刷回磁盘、当buffer过半时也会刷盘、还有参数控制提交事务时的刷盘策略(innodb_flush_log_at_trx_commit)
innodb_flush_log_at_trx_commit参数有3个,要在保证一致性和性能之间权衡。1是最安全的,每次提交事务时都会立即写入磁盘,但是对性能消耗大。0表示,每次提交事务后没有操作,只是将数据留在redo log buffer中。2表示每次事务提交会将数据更新到pageCache中(操作系统层面)。安全性:1>2>0。2保证了即使mysql进程崩溃,但是操作系统正常时,也不会丢失数据
前面提到会有后台线程没秒进行刷盘操作,对于参数0,需要先通过write将数据读入pagecache,在通过fsync将pagecache中的数据刷入磁盘;对于参数0,只需要通过fsync()将数据刷入磁盘即可
4 redo log文件格式,当redo log满后,会暂停更新语句写入,强制刷盘。是一个环形结构,write_pos表示当前写入位置,check_point表示刷盘位置。mysql抖动原因还可能为buffer pool内存也不够用了
bin log
主要用于主从备份、数据同步等
是Server层的
bin log和redo log的区别
a. 写入方式不同,前者追加写,后者循环写,新内容会覆盖掉老内容
b. 前者是逻辑日志,记录了更新的sql语句;后者是物理日志,记录了某个内存页,xxx偏移量下变更了xxx数据
c. 前者是server层,后者是引擎层 innodb
d.前者是为了主从同步、数据备份;后者为了crash-safe,故障恢复
-
主从复制的步骤
1.客户端执行完事务后,会先写入本地binlog,更新磁盘。主库异步发起同步
2.从库有专门的io线程,连接主库的log dump线程,从库接受并写入reply log中
3.从库回放中继日志,同步成功 -
bin log刷盘频率
同一个事务必须一次写入,mysql为每个线程都分配了binlog buffer,他们都需要写入一个binlog文件。
binlog也可以通过sync_binlog控制刷盘频率。0表示每次只write进page cache中,有操作系统决定合适fsync到磁盘,1表示每次write完后立即fsync刷盘。n表示累计多少事务后再刷盘 -
两阶段提交
通过内部XA事务管理器实现,将redo log拆分为prepare解读那和commit阶段,中间穿插binlog的写入。
第一步:将XID,内部事务id先写入redo log中,redo log写入磁盘
第二步:将XID写入bin log, 将bin log写入磁盘。然后调用存储引擎接口将redo log状态改为commit。
当bin log还没有写入磁盘时发生故障,此时判断binlog中没有XID,进行回滚操作
当bin log已经先写入了磁盘,此时判断bin log 中有XID,进行事务的提交
以上两种情况redo log都处于prepare阶段,关键在于判断binlog中是否有相同的XID