MySQL之InnoDB
InnoDB数据结构
使用
PRIMARY KEYorUNIQUE NOT NULL字段作为主键,如果都没有,则使用隐式主键(DB_ROW_ID)_rowid会映射数值主键或第一个唯一非空数值字段
数据记录是以主键为顺序的单向链表。所以使用自增整数作为主键最好,新增数据都在最后
索引类型
聚簇索引和非聚簇索引都是B+Tree,区别是聚簇索引的叶子节点存放整行数据
聚簇索引
- 叶子节点存放整行数据,查询可直接获取整行数据
- 一个表只有一个聚簇索引,默认是主键
非聚簇索引(普通索引、辅助索引)
- 叶子节点存放索引列的值和聚簇索引键(通常是主键)
- 如果查询的字段不止聚簇索引键和索引列,则需要回表
- 回表:根据聚簇索引键查询聚簇索引获取行的其他列数据
- 索引覆盖:非聚簇索引字段满足查询字段的需求
Change Buffer
用于存储SQL变更操作。每个变更都会对应一个数据页。仅适用于普通索引
插入/更新/删除,使用Change Buffer条件:
- 数据页不在内存(
Buffer Pool) - 不用返回修改后的数据
innodb_change_buffering参数配置缓存哪些操作
Change Buffer合并时机:
- 加载变更操作对应的数据页
- 后台定时
- Buffer Pool空间不足,通过
innodb_change_buffer_max_size参数设置 - redo log满了
- 数据库正常关闭
唯一索引
- 查询效率高。查询时,查询到第一个符合当前索引值时,查询便结束
- 插入效率较低,需先读取可能的数据页,判断是否存在,写入磁盘,没有change buffer
- 如果数据页就在内存,则直接判断是否冲突
联合索引
对于联合索引(a, b, c)
- B+Tree是针对a列进行索引
- 叶子结点存放(a,b,c)列的值,当a相等时,b有序,当b相等时,c有序
- 相当于同时建立(a)、(a, b)、(a, b, c)索引
索引检索
索引失效
以下情况不会使用索引:
where语句字段类型不一致where里的字段类型和数据库类型不一致,MySQL会进行隐式的数据类型转换,使用内置转换函数- 当左侧需要转换且唯一时,不影响索引。 比如数值类型=字符类型时
where语句使用表达式。如where age + 2 = 30where语句使用内置函数。如where lefe(phone_number) = '136'where语句使用左模糊匹配- 右模糊匹配依然可以使用索引
where语句使用or,innodb不会使用索引- 可以使用
union代替or,前提是union的查询列有索引
- 可以使用
where语句使用!=或<>where语句中in、not in是否失效取决于MySQL版本,8.0之后会用索引where语句包含NULL判断order by字段和where字段不属于同一个索引- MySQL 每次查询只使用一个索引
order by字段和where属于同一联合索引但和索引顺序不同order by多个字段在同一个联合索引,但排序顺序不同,一个ASC,一个DESC
联合索引的最左匹配原则
必须按照索引定义从左到右匹配
比如,对于联合索引(a, b, c)
a=1 and b=1 and c=1:使用索引(a, b, c)a=1 and b=1:使用索引(a, b)b=1 and a=1:使用索引(a, b),查询优化a=1:使用索引(a)b=1 and c=1:不能使用索引a=1 and c=1:使用索引(a)a=1 and b=1 and c>0:使用索引(a, b,c)a=1 and b>0 and c=1:使用索引(a, b),原因当b有多个时,c无序- 当有范围查询时,后面字段的索引无效
日志与两阶段提交
Redo Log(重做日志)
redo log(重做日志)是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。
一个事务中,对数据库的每次修改会先写入redo log buffer,一次数据修改往往会有B+Tree的多个修改点,有多条redo log,称为Mini-Transaction(简写mtr),是写入的最小单元。每一组redo log结尾会有一个标志符,直到读到这个标志符这一组才完整,否则丢弃,保证mtr的原子性。
Log结构
{log block header,12字节} + {log block body} + {log block tralider,4字节}
文件结构
redo log不是只有一个文件(数量由innoDB_log_files_in_group指定),而是以redo日志文件组(redo log group)的形式,每个日志文件大小48M(由Innodb_log_file_Size指定),log文件每次以block为单位操作,每个block有512个字节
日志文件组每个文件大小一样,格式一样,由两个部分组成:
- 前2048个字节(前4个block)存一些管理信息
- 之后其他,开始存log buffer的block
记录时采用环形数组的方式,循环写这些文件,所以我们需要知道当前写入的位置和哪些日志已经写到磁盘了,由log_sys记录
written_to_all_lsn:已经写盘的的LSN(未flush)。并非每次生成log就写盘flushed_to_disk_lsn:记录已经flush的LSN。并非每次写盘都flushbuf_next_to_write:buf中下一个要写入磁盘的位置buf_free:buf中实际内容的最后位置,buf_next_to_write<buf_free时说明存在数据未写盘
LSN(log sequence number)
LSN是一个单调递增的值,从8704开始,脏页(页被修改但未同步至磁盘)和redo log都有各自的LSN。
Buffer Pool里的脏页,是一个链表结构(Flush List)。第一次被修改的页,会以头插法的形式插入到表头,每个脏页都记录着第一次被修改的LSN(123)和最后一次被修改的LSN(123)。
刷盘时机
InnoDB后台定时线程每隔1s刷盘
InnoDB提供
innodb_flush_log_at_trx_commit参数配置事务刷盘策略0:不主动落盘。MySQL挂了会丢失1s内数据1:事务提交时刷盘。默认2:事务提交时,写入page cache。操作系统宕机会丢1s内数据
当redo log buffer大小达到
innodb_log_buffer_size一半时
binlog
binlog是MySQL的Server层实现的,所有引擎都可以使用,有以下作用
- 数据备份、恢复
- 主备、主主、主从复制
binlog记录了这个语句的原始逻辑,格式有3个选项,通过binlog_format指定
statement:SQL原语句。问题是如果SQL中有当前时间相关的,会出现不一致row:二机制,需用mysqlbinlog解析,不会发生不一致,但比较占空间和IOmixed:MySQL判断是否会引起数据不一致,如果会,则用row
写入机制
- 先写入bin cache,事务提交时,在写入文件(page cache)
sync_binlog:控制fsync时机0:不主动,默认1:每次写入时N:每N次,N>1
- 一个事务的binlog不能分开
binlog_cache_size控制单个事务cache大小,超过这个大小,暂存到磁盘
二阶段提交
redo log的写入分为两个阶段:prepare和commit
写入流程:
- 事务开始
- 缓存内更新数据
- 写入redo log,prepare阶段
- 提交事务,写入binlog
- redo log commit阶段
- 事务结束
如果redo log commit阶段失败,由于有binlog,也认为事务完成
Undo log(回滚日志)
Undo log也是InnoDB的一种日志。事务内所有的修改会先记录到undo log并落盘,如果事务执行过程异常,利用undo log回滚
- 逻辑日志,提供相反操作
- 事务回滚
- 多版本并发控制(MVCC),InnoDB使用乐观锁
- insert、upate、delete会记录undo log
- 通过purge线程回收
文件结构
- 采用分段(segment)的方式存储,每个段有1024个undo log segment
- MySQL5.5之前,只支持1个rollback segment,只能记录1024个undo操作
- MySQL5.5之后,可支持128个rollback segment,分别从resg slot0 ~ resg slot127
innodb_undo_tablespaces:表空间,默认是0,公用表空间
- 内容:
- Next:指向下一条Undo log
- Undo Log类型:
Insert和Update - Undo No
- 事务ID(DATA_TRX_ID)
- Table id:表ID
- 上一次回滚ID(DATA_ROLL_ID)
- 指向上一个undo log的Undo No
- 假如前后发生事务A、B、C修改同一个字段name,则undo log的指向是,C的undo log指向B,如果C回滚会回滚到B的值,如果B回滚,则回滚到A的值
- 其他 https://zhuanlan.zhihu.com/p/165457904
多版本并发控制
ReadView
read view 是实现 MVCC 的关键,描述的是在该事务开始时刻数据库系统中所有事务的总体状态。
在新事务开始时,会存在一个已提交事务 T,在其之前的事务都已提交。该事务只能看到 T 及其之前事务的影响(事务 T 提交时刻数据库的 snapshot)
例如,此时的事务提交情况如下,当前事务是7,事务开始时,复制当前的事务总体状况m_ids=[5, 8, 9],此时最大事务已经max_trx_id=10,所以,m_up_limit_id=5,m_low_limit_id=10
1 | trx_id: 1 2 3 4 5 6 7 8 9 *10 11 12 |
对于事务7,当前正在进行的事务是
- 所有大于等于
m_low_limit_id(10)的事务,[10, 11] - 所有大于等于
m_up_limit_id(5)且在m_ids内,[5, 6, 9, 10]
对于事务7,已经提交的事务是
- 所有小于
m_up_limit_id(5)的事务,[1, 2, 3, 4] - 所有大于等于
m_up_limit_id(5)且小于等于m_low_limit_id(10)且不在m_ids内的,[8]
综上,对于事务7,可见的事务为[1, 2, 3, 4, 8],就是在7开始时已经提交的事务
数据的隐藏列DATA_TRX_ID记录当前该行的事务ID,当该行的事务对其(事务7)不可见时,使用数据隐藏列DB_ROLL_ID回溯undo log直到可见
- 假设事务4插入一条数据(
name="Tom"),此时DATA_TRX_ID=4,提交 - 事务5修改该行(
name="Jerry"),生成undo log(undo no=1 DATA_TRX_ID=4 name="Tom"),此时DATA_TRX_ID=5, DB_ROLL_ID=1 - 对于事务7来说,事务5还未提交,此时不应该读到”Jerry”,事务7判断该行的
DATA_TRX_ID不在可见范围内,就通过DB_ROLL_ID=1找到undo no=1的undo log,看此时DATA_TRX_ID=4在自己的可见范围,则读到Tom
快照读和当前读
快照读(SnapShot Read)是一种一致性不加锁的读 ,是InnoDB并发如此之高的核心原因之一
这里的一致性是指,事务读取到的数据,要么是事务开始前就已经存在的数据 ,要么是事务自身插入或者修改过的数据
不加锁的简单SELECT都属于快照读,例如:
1 | SELECT * FROM t WHERE id=1` |
与快照读相对应的则是当前读, 当前读就是读取最新数据,而不是历史版本的数据。加锁的SELECT就属于当前读,例如:
1 | SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE; |
日志总结
MySQL InnoDB引擎使用redo log保证事务的持久性,使用undo log来保证事务的原子性
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性
Update和Delete操作的内部机制
update分为两种情况
- 如果不是主键列,在undo log中直接反向记录是如何update的
- 如果是主键列,update分两部执行:先删除该行,再插入一行目标行
delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的
磁盘存储
MySQL一次IO读取的数据称之为一页,每页默认是16KB。指针(64位,8B)+值(bigint,8B)=16B,16KB/16B=1K个键值,两层就是1K*1K=100w条记录,对于100w记录的表两次IO即可找到对应的位置
磁盘IO,磁盘读取数据靠的是机械运动,每一次读取数据需要寻道、寻点、拷贝到内存三步操作,平均需要10ms左右
B+Tree结构
BTree,又叫多路平衡查找树,一颗m叉的BTree特性如下:
- 树中每个节点最多包含m个孩子
- 除根节点与叶子节点外,每个节点至少有ceil(m / 2)个孩子(ceil为向上取整)
- 若根节点不是叶子节点,则至少有两个孩子
- 所有的叶子节点都在同一层
- 每个非叶子节点由n个key与n+1个指针组成,其中ceil(m/2)-1 <= n <= m-1
参考
- https://blog.csdn.net/popofzk/article/details/122909645
- https://blog.csdn.net/cristianoxm/article/details/121957011
- https://www.modb.pro/db/436027
- https://www.zhihu.com/question/345657290
- https://zhuanlan.zhihu.com/p/442802131
- https://javaguide.cn/database/mysql/mysql-logs.html
- https://developer.aliyun.com/article/8829
- https://zhuanlan.zhihu.com/p/272696187
- 《MySQL的undo log》https://zhuanlan.zhihu.com/p/421358988
- 《InnoDB:undo log(2)》https://zhuanlan.zhihu.com/p/263038786