innodb 日志管理机制:
1,innodb 存储引擎是支持事务 ACID 特性的, 这个理论基本就是一个关系型数据库相关的数据恢复原形设计, 包括日志, 回滚, redo, 并发控制, buffer pool 等管理方面, 内容非常全面;
2,innodb 的 buffer pool 主要用来存储访问过的数据页面, 他就是一块连续的内存, 通过一定的算法可以使这块内存得到有效的管理, 它是数据库系统中拥有最大块内存的系统模块.
innodb 存储引擎中数据的访问是按照页 (也可以叫块, 默认为 16KB) 的方式从数据库文件读取到 buffer pool 中的, 然后在内存中用同样大小的内存空间来做一个映射; 未来提高数据访问效率, 数据库系统预先就分配了很多这样的空间, 用来与文件中的数据进行交换; buffer pool 的大小可以在配置文件中配置, 有参数 innodb_buffer_pool_size 的大小来决定, 默认大小为 128MB. 在 MySQL5.7.4 以前, 一旦 MySQL 启动这个值便不能再做修改, 如果要修改只能退出 MySQL 进程, 然后修改对应的配置文件来设置新的 buffer pool 大小, 重启才能生效.
注意: 在 MySQL5.7.5 之后, 可以在 MySQL 进程运行的情况下, 动态调整 innodb_buffer_pool_size, 需要强调的是, 如果 buffer pool 的大小超过了 1GB, 应该通过调整 innodb_buffer_pool_instances=N, 把它分成若干个 instance 的做法, 来提示 MySQL 处理请求的并发能力, 因为 buffer pool 是通过链表的方式来管理页面的, 同时为了保护页面, 需要在存取的时候对链表加锁, 在多线程的情况下, 并发去读写 buffer pool 里面缓存的页面需要锁的竞争和等待. 所以修改为多个 instance, 每个 instance 各自管理自己的内存和链表, 可以提升效率.
3,buffer pool 实现原理:
buffer pool 可以有多个实例, 可以通过配置文件中的参数 innodb_buffer_pool_instance 来设置, 默认值为 1, 实现多个实例的 buffer pool 主要是为了提高数据页访问时的并发度. 每个实例的空间大小都是相同的, 也就是说系统会将整个配置的 buffer pool 大小按实例个数平分, 然后每个实例各自进行初始化操作;
-- 注意: 在运维过程中, 看到状态参数 innodb_buffer_pool_bytes_data 总是比 innodb_buffer_pool_size 小, 就是因为控制头信息占用了部分空间. 实际的分配方式是, buffer pool 页面从整个实例池中从后向前分配, 每次分配一个页面, 而控制结构使从前向后分配, 每次分配一个 buf_block_t 结构的大小, 知道相遇为止, 这样就将一个实例初始化好了.
第一, redo log 日志文件管理:
redo log 是用来做数据库 crash recovery 的, 这是数据库保障数据安全的重要功能之一. 在数据库操作中, 它保存了对 innodb 表中数据的修改记录, 所以也叫日志文件. 在 innodb 存储引擎中, 一般默认包括 2 个日志文件, 新建数据库之后会有名为 ib_logfile0 和 ib_logfile1 的两个文件, 如果在启动数据库时, 这两个文件不存在, 则 innodb 会根据配置参数或默认值, 重新创建日志文件;
1.1,LSN 全名叫: log sequence number:
在 innodb 内部的日志管理中, 一个很重要的概念是 LSN, 全名叫 log sequence number, 它用来精确记录日志位置信息, 且是连续增长的. 在 innodb 中, 大小为 8 个字节值, 它的增长量是根据一个 MTR 写入的日志量来计算的, 写多少日志, LSN 就增长多少.(LSN 是一个完全逻辑的概念, 每提交一个物理事务, LSN 就加 1)
1.2, 在 innodb 中, 通过日志组来管理日志文件, 是一个逻辑定义, 包含若干个日志文件, 一个组中的日志文件大小相等, 大小通过参数来设置, 现在 innodb 只持有一个日志组.(在 MySQL5.5 以前日志组最大 4G;MySQL5.6.3 以后可以设置的更大到 512G)
1.3,redo 日志的写入, 都是字节连续的, 虽然看上去是多个日志文件, 但理解的时候, 完全可以把它想象成一个文件.(日志组中的每个日志文件, 都有自己的格式, 内部也是按照大小相等的页面切割, 每个页面大小是 512 字节)
--- 注意: 如果每次写入是磁盘块大小的倍数, 效率才是最高的, 并且日志将逻辑事务对数据库的分散随机写入转化成了顺序的 512 字节整数倍数据的写入, 这样就大大提高了数据库的效率.
1.4,redo 日志文件的格式:
每个日志文件, 都有文件头(普通页面中, 都会有 12 个字节用来存储页面头信息, 这些信息主要用于管理这个页面本身的数据存储方式;--- 注意只有 2KB 是日志头, 后面是一个个连续的, 用来存储 MTR 产生的日志页面)
1.5,MTRinnodb 物理事务:
它是 innodb 存储引擎中一个很重要的用来保证物理页面写入操作完整性及持久性的机制. 之所以被称为 MTR, 是因为它的意义相当于一个 mini-transaction, 用 MTR 来表示, 这里吧它称作 "物理事务", 这样叫是相对逻辑事务而言的.
物理事务既然被称为事务, 那它同样有事务的开始和提交, 物理事务的开始其实就是对物理事务结构体 mtr_struct 的初始化, 物理事务的提交主要是将所有这个物理事务产生的日志写入到 innodb 日志系统的日志缓冲区中, 然后等待 srv_master_thread 线程定时将日志系统的日志缓冲区的日志数据刷到日志文件中;
--- 注意: 日志缓冲区的存储只是一个暂时的中间状态, 日志缓冲区的大小可以通过参数 innodb_log_buffer_size 来设置, 一般都比较小, 存储不了多少日志.
-- 日志是在逻辑事务对数据库做 DML 操作时, 其所包含的物理事务 MTR 所记录的, 针对所有涉及的 buffer pool 页面的修改记录;
1.6, 日志提高性能的关键原因:
1: 因为日志是用来记录 buffer pool 中 page 的修改记录的, 所以把 page 的写入转化为对日志的写入, 那此时 page 就不需要每次都刷盘, 写 page 页面只需要在内存中写入即可, 性能会非常好;
2: 通常, 一个页面是 16KB, 如果不写入职, 每次写入的单位还是 16KB, 即使修改很少的数据, 也是如此, 这样会导致无效 IO 非常严重.
1.7,redo 日志大小设置的问题:
1: 如果设置的非常大, 固然性能可能会很好, 但是如果数据库出现异常停机, 此时可能有很多日志都没有刷盘, 也就是 log flushed up to 与 last checkpointat 两个值之间相差太多, 恢复需要比较长的时间.(redo 日志的恢复是顺序的, 都是根据页面号的大小排序恢复的;)
2: 日志容量大小的设置, 最好与 buffer pool 的总大小匹配. 如果日志容量太小, buffer pool 太大, 这就会导致 buffer pool 频繁做检查点, 大的 buffer pool 不能被好好利用, 如果日志容量过大, 而 buffer pool 很小, 此时 buffer page 经常会被淘汰出去, 增加 IO 频次, 同时如果数据库意外宕机, buffer pool 太小, 恢复起来也会比较慢;
1.8,redo 日志记录格式:
innodb 的日志是具有逻辑意义的物理日志, 所以, 日志记录的格式就不完全是物理信息, 而是有一定逻辑意义, 基本的格式如下:
type(日志类型),space(表空间 ID 值),offset(前面 space 所指定的文件中的页面号, 以页面大小为单位),data(表示这条日志记录对应的数据, 这个数据是不确定的, 根据不同的 type 值而不同)
---type 类型有很多, 比较常用的有:
1:mlog_ibyte,mlog_2bytes,mlog_4bytes,mlog_8bytes: 这四个类型, 表示要在某个位置, 写入一个 (两个, 四个, 八个) 字节的内容;
2:mlog_write_string: 这种类型的日志, 其实和 mlog_ibyte 是类似的, 只是 mlog_ibyte 是要写一个固定长度的数据, 而 mlog_write_string 是要写一段变长的数据.
3:mlog_undo_insert: 这个类型的日志, 是在将一条记录设置为页面中的最小记录时产生的, 因为只是打个标记, 存储的内容比较简单;
4:mlog_init_file_page: 这个类型的日志比较简单, 只有前面的基本头信息, 没有 data 部分;
5:mlog_comp_page_create: 这个类型只需要村一个类型及要创建的页面的位置即可;
6:mlog_multi_rec_end: 这个类型的记录是非常特殊的, 它只起一个标记的作用, 其存储的内容只有占一个字节的类型值.
7:mlog_comp_rec_clust_delete_mark: 这个类型的日志是表示, 需要将聚集索引中的某个记录打上删除标志;
8:mlog_comp_rec_update_in_place: 这个类型的日志记录更新后的记录信息, 包括所有被更新的列的信息.
9:mlog_comp_page_reorganize: 这个类型的日志表示的是要重组指定的页面, 其记录的内容也很简单, 只需要存储要重组哪一个页面即可;
1.9, 日志刷盘时机: 共有 5 种时机:
1:log buffer 空间用完了, 这就会将已经产生的 log buffer 中的日志刷到磁盘中, 这是最普遍的一种方式;
2:master 线程在后台每秒钟刷一次, 将当前 log buffer 中的日志刷到磁盘中;
3: 每次执行 DML 操作时, 都会主动检查日志空间是否足够, 如果使用空间的量已经超过了一个预设的经验值, 就会主动刷日志, 以保证在后面真正执行时, 不会再执行过程中被动的刷盘, 但这里只会是写文件 (写入 OS 缓冲中) 不会刷盘
4: 在做检查点的时候, 要保证所有要刷的页面中 LSN 值最小的日志已经刷入到磁盘, 不然, 如果此时数据库宕机, 日志不存在, 但数据页面已经被修改, 从而导致数据不一致, 就违背了写日志的原则;
5: 提交逻辑事务时, 会因为参数 innodb_flush_log_at_trx_commit 值的不同, 产生不同的行为. 如果设置 0, 则在事务提交时, 根本不会去刷日志缓冲区, 这种设置是最危险的; 如果设置 2, 则在事务提交时会将日志写入到文件中, 但不会去刷盘, 只要操作系统不挂, 即使数据库挂了, 数据还是不会丢失, 一般都是设置为 2;
1.10,redo log 刷盘机制:
当提交事务 (逻辑) 时, 可以通过参数 innodb_flush_log_at_trx_commit 来控制 redo log 写入的机制, 参数值不同, 产生的行为不同, 主要参数值如下:
1:innodb_flush_log_at_trx_commit=0
事务提交时, MySQL 不会去处理日志缓存区的内容, 也不会去处理日志文件的刷盘操作, 由 MySQL 的后台 master 线程每隔 1s 将缓存区的文件刷新到日志文件中;(主机正常, 数据库宕机后: 一般只会丢失最近 1s 的事务)
2:innodb_flush_log_at_trx_commit=1
事务提交时, 会将日志缓冲区的日志写入到文件中, 同时会刷新到磁盘中, 保证数据库事务完全不会丢失. 这种设置影响数据库性能;(主机正常, 数据库宕机后: 数据不会丢失)
3:innodb_flush_log_at_trx_commit=2
事务提交时, 会将日志缓存区日志写入到文件中, 但是不会刷新到磁盘中. 由 MySQL 的后台 master 线程每隔 1s 将系统缓存的日志文件刷新到磁盘中;(主机正常, 数据库宕机后: 数据不会丢失)
--- 注意: 如果数据库所在主机宕机后: 参数 0 会丢失最近 1s 的事务; 参数 1 不会有任何数据丢失; 参数 2 会丢失最近 1s 的事务;
第二, 数据库 undo 段管理:
在 innodb 中支持的回滚段总共有: 128X1024=131072 个, 在每一个事务开始的时候, 都会分配一个 rseg, 就是从长度为 128 的数组中, 根据最近使用的情况, 找到一个临近位置的 rseg;
在事务执行的过程中, 会产生两种回滚日志, 一种是 insert 的 undo 记录, 一种是 update 的 undo 记录;(因为 innodb 把 undo 分为两类, 一类就是新增, 也就是 insert, 一类是修改, 就是 update, 分类的依据就是事务提交后要不要做 purge 操作, 因为 insert 是不需要 purge 的, 只要事务提交了, 那这个回滚记录就可以丢掉了, 而对于更新和删除操作而言, 如果事务提交了, 还需要为 MVCC 服务, 那就需要将这些日志放到 history list 中去, 等待去做 purge 已经 MVCC 的多版本查询等, 所以分为两类)
2.1, 数据库 undo 日志记录格式:
undo 有 4 种类型:
1:trx_undo_insert_rec: 记录插入的 undo 日志类型, 插入记录用于回滚时, 只需要通过其主键就可以实现回滚操作, 所以在 undo 日志中, 只记录了表 ID 及主键信息;
2:trx_undo_upd_exist_rec: 更新一条存在记录的 undo 日志类型;
3:trx_undo_upd_del_rec: 更新一条已经打了删除标志记录的 undo 日志类型;
4:trx_undo_del_mark_rec: 删除记录时对记录打删除标志的 undo 日志类型;
--- 注意: 与 redo 日志记录存储不同, undo 日志的存储, 是不会垮页面的;
--- 注意: 使用参数 innodb_force_recovery 来决定要不要做回滚操作, 如果设置 3 或 3 以上, 那么在启动 innodb 的时候就不回滚了, 这样可能导致数据库逻辑上的不一致;
来源: http://www.linuxidc.com/Linux/2018-11/155494.htm