本文主要是本人学习Mysql内容过程中重点学习内容,无论是面试还是日常工作都有很大作用。自己也会不定时继续学习Mysql并且补充内容。内容基本来自于<<MYSQL技术内幕:InnoDB存储引擎>>和<<软件设计架构>>,有空会看更多内容,希望大家推荐
Mysql知识补充:github.com/Snailclimb/…
1、MySQL 基础
1.InnoDB:InnoDB,是MySQL的数据库引擎之一,现为MySQL的默认存储引擎,为MySQL AB发布binary的标准之一。
· 支持行级锁
· 支持事务--- 事务是数据库区别于文件系统的一个关键特性
· 支持外键
· 支持数据库异常崩溃后的安全恢复
· 支持 MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
2.何为 ACID 特性呢?
undo log原子性(Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性(Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
redo log持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
3.DML 语句和 DDL 语句区别:
DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),
是开发人员日常使用最频繁的操作。
DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,
而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
4、数据库设计通常分为哪几步?
需求分析 : 分析用户的需求,包括数据、功能和性能需求。
概念结构设计 : 主要采用 E-R 模型进行设计,包括画 E-R 图。
逻辑结构设计 : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。
物理结构设计 : 主要是为所设计的数据库选择合适的存储结构和存取路径。
数据库实施 : 包括编程、测试和试运行
数据库的运行和维护 : 系统的运行与数据库的日常维护。
5、数据库命令规范
所有数据库对象名称必须使用小写字母并用下划线分割
所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
数据库对象的命名要能做到见名识意,并且最好不要超过 32 个字符
临时库表必须以 tmp_为前缀并以日期为后缀,备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀
所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
6、mysql索引:
使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 redo log 。
🌈 拓展一下: MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。 MySQL InnoDB 引擎通过 锁机制、MVCC 等手段来保证事务的隔离性( 默认支持的隔离级别是 REPEATABLE-READ )。 保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
7、mysql的主从复制
原理:
(1)master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中;
(2)slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/OThread请求master二进制事件
(3)同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。
也就是说:
从库会生成两个线程,一个I/O线程,一个SQL线程;
I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
主库会生成一个log dump线程,用来给从库I/O线程传binlog;
SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;
因为binlog是引擎层和server层之间提交的,是一个分布式的的事务,因此binlog通过内部XA保证一致性。
BLGC(Binary Log Group Commit)的主要步骤有:
1.提交时首先按顺序将所有事务放在队列中
2.二阶段提交第一阶段prepare:
Flush阶段,将每个事务的二进制日志写入内存中
Syn阶段,将内存中的二进制日志刷盘
返回OK
3.二阶段提交第二阶段commit:
commit阶段,调用引擎层的事务的提交,即将redo log刷盘。
如果在二阶段开始时,数据库宕机了,那么重启后会先检查准备的UXID事务是否已经提交,如果没有,就会在引擎层再进行一次commit操作
8、事务隔离级别有哪些?
SQL 标准定义了四个隔离级别:
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
(默认)REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
9、mysql主键索引和非主键索引有什么区别(都是B+树索引)
由于在B+树结构的索引中,索引项是按照索引定义里面出现的字段顺序排序的,索引在查找的时候,可以快速定位到 ID 为 100的张一,然后直接向右遍历所有张开头的人,直到条件不满足为止。 也就是说,我们找到第一个满足条件的人之后,直接向右遍历就可以了,由于索引是有序的,所有满足条件的人都会聚集在一起。 而这种定位到最左边,然后向右遍历寻找,就是我们所说的最左前缀原则。
主键索引和非主键索引的区别是:非主键索引的叶子节点存放的是主键的值,而主键索引的叶子节点存放的是整行数据, 其中非主键索引也被称为二级索引,而主键索引也被称为聚簇索引。
根据这两种结构我们来进行下查询,看看他们在查询上有什么区别。 1、如果查询语句是 select * from table where ID = 100,即主键查询的方式,则只需要搜索 ID 这棵 B+树。 2、如果查询语句是 select * from table where k = 1,即非主键的查询方式,则先搜索k索引树,得到ID=100,再到ID索引树搜索一次,这个过程也被称为回表。
InnoDB存储引擎
1、B+树和索引
B+树索引并不能找到一个给定键值的具体行,只能找到被查找数据所在的页,然后通过数据库把页读入到内存中,再在内存中进行查找。
聚集索引:以主键ID建立B+树,叶子结点是数据页,存放着完整的每行记录,非叶子结点的是索引页,存放的仅仅是键值以及指向数据页的偏移量。
非聚集索引(辅助索引):叶子结点不包含行记录的全部数据。叶子结点除了包含键值外,还存储着数据的聚集索引键。
联合索引(多个列进行索引):比如有联合索引(a, b),叶子结点存放顺序会按照先排a再排b的方式排序存放 联合索引已经把b排序了,因此在处理a = 1,并按照b升序取3个值(where a = xxx order by b)的这种情况使用联合索引
最左匹配原则就是指在联合索引中,如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配。 也就是说通过最左匹配原则你可以定义一个联合索引,但是使得多中查询条件都可以用到该索引。 值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。 如:select * from t where a=1 and b>1 and c =1; #这样a,b可以用到(a,b,c),c索引用不到
通过对联合索引的结构的了解,那么就可以很好的了解为什么最左匹配原则中如果遇到范围查询就会停止了。以 select * from t where a=5 and b>0 and c =1; 这样a,b可以用到(a,b,c),c不可以 为例子,当查询到 b 的值以后(这是一个范围值),c 是无序的。所以就不能根据联合索引来确定到低该取哪一行。
覆盖索引:
倒排索引(反向索引):blog.csdn.net/qq_43403025…
什么是正排索引(正向索引):上面所说的索引都是正向索引,形式如下:行记录ID -> 列1,列2,列3......
而倒排索引则相反,可能形式如下:
值a -> 行记录id1,行记录id2,行记录id3
值b -> 行记录id1,行记录id2,行记录id3
倒排索引(Inverted Index):倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
单词词典(Lexicon):搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
倒排列表(PostingList):倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。
倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
搜索引擎中经常使用,Innodb中在使用全文检索时可以使用倒排索引,innoDB中支持全文检索技术,采用full inverted index方式。有全文检索表
倒排索引需要将word存放到一张表中,称为辅助表,InnoDB中有6张辅助表,为了提高全文检索的并行性能,持久化存储于磁盘上。
同时还有一个全文检索索引缓存,采用红黑树的结构,提高全文检索性能。为了解决缓存不一致问题,InnoDB会批量对辅助表进行更新,而不是每次插入都刷盘
对于InnoDB存储引擎,总是在事务提交时将分次写入到全文检索索引缓存中,然后再通过批量更新写入磁盘。
full inverted index:不仅存储了包含关键字的文档,还存储着单词出现的位置信息,如:
Number Text Documents
1 code (1:6),(4:8)
2 days (3:2),(6:2)
3 hot (1:3),(4:4)
...
例子:code这个单词出现在文档1的第六个单词,文档4的第八个单词。
表空间--段--区--页--行
如何判断是否需要建索引: 1、选择高选择性的列,即取值范围广,重复度小的列。 可以根据show idex中的Cardinality来观察,此值表示索引中不重复记录的预估值
InnoDB全文检索
innodb存储结构:blog.csdn.net/WenWu_Both/…
结构视图:www.cnblogs.com/piaobodeyun… mysql中如何实现mvcc blog.csdn.net/SnailMann/a…
1、缓冲池结构: blog.csdn.net/liushangzai… blog.csdn.net/u010648194/…
一些参数:
Total memory allocated :分配给缓冲池的总内存 这里的单位是字节
Dictionary memory allocated: 分配给数据字典中的内存单位是字节
Total memory allocated:是mysql实际使用的内存大小,这个是大于buffer pool size的
Buffer pool size: 分配给缓冲池的页面的总大小, 这里指的是页的大小,图上面的32768是32768页, 一个页是16KB 则缓冲池大小 = 32768 * 16 / 1024 = 512M
Free buffers : 缓冲池空闲列表的总页大小,就是Free List ,单位:页
Database pages: buffer pool LRU列表的总页面大小。 单位:页
Old database pages:old LRU子列表的总页面大小。单位:页
Modified db pages: 表示就是脏页的数量 单位:页
组成:
1、Free List 空闲的链表list
2、LRU List 已经存放数据(已经使用的页)的链表list
3、Flush List 脏页list ,Flush 页其实是LRU List 的指针,LRU List包括Flush List
4、数据页缓存哈希表:key: 表空间+数据页编号, value: 缓存页地址
如果 InnoDB 存储引擎只有一个 Buffer Pool,当高并发时,多个请求进来,那么为了保证数据的一致性(缓存页、free 链表、flush 链表、lru
链表等多种操作),必须得给缓冲池加锁了,每一时刻只能有一个请求获得锁去操作 Buffer Pool,其他请求只能排队等待锁释放。那么此时 MySQL
的性能是多么的低!
下面简单总结一下 Buffer Pool 从初始化到使用的整个流程。
1、MySQL 启动时会根据分配指定大小内存给 Buffer Pool,并且会创建一个个描述数据块和缓存页。
2、SQL 进来时,首先会根据数据的表空间和数据页编号查询 数据页缓存哈希表 中是否有对应的缓存页。
3、如果有对应的缓存页,则直接在 Buffer Pool中执行。
4、如果没有,则检查 free 链表看看有没有空闲的缓存页。
5、如果有空闲的缓存页,则从磁盘中加载对应的数据页,然后将描述数据块从 free 链表中移除,并且加入到 lru 链表的old数据区域的链表头部。后面
如果被修改了,还需要加入到 flush 链表中。
6、如果没有空闲的缓存页,则将 lru 链表的old数据区域的链表尾部的缓存页刷回磁盘,然后清空,接着将数据页的数据加载到缓存页中,并且描述数据
块会加入到 lru 链表的old数据区域的链表头部。后面如果被修改了,还需要加入到 flush 链表中。
7、5或者6后,就接着在 Buffer Pool 中执行增删改查。
注意:5和6中,缓存页加入到old数据区域的链表头部后,如果在 1s 后被访问,则将入到new数据区域的链表头部。
8、最后,就是描述数据块随着 SQL 语句的执行不断地在 free 链表、flush 链表和 lru 链表中移动了。
缓冲池(可以支持多个缓冲池)
首先,缓冲池中保存着多个数据页:主要数据页类型有:索引页,数据页,undo页。
还有自适应哈希索引,数据字典信息,锁信息。
每个缓存页都会对应着一个描述数据块,里面包含着数据页的表空间,编号,地址等,大小大概是缓存页的5%。
数据页缓存哈希表:key为表空间+数据页编号,value是缓存页地址
checkpoint: 保证脏页能刷到硬盘上,更新wal的checkpoint,减少恢复时间,因此checkpoint可以认为是刷新脏页到磁盘上的时间点
机制:
Sharp CheckPoint会将所有脏页都刷新回磁盘,发生在数据库关闭时。
Fuzzy CheckPoint只刷新一部分脏页,发生在数据库运行时,这种机制会有以下几种情况:
Master Thread CheckPoint:每秒或每十秒刷新一部分脏页到磁盘上,这个过程是异步的,不会阻塞查询线程。
FLUSH_LRU_LIST CheckPoint:InnoDB需要保证LRU列表中有一定数量的空闲页可用,当空闲页数量不够时,会将LRU列表尾部的页移除,并将被移除的页
中的脏页刷新到磁盘上。在1.1.x版本之前,这个操作是由用户查询线程完成的,自InnoDB1.2.x开始放在了Page Cleaner Thread中,且可通过
innodb_lru_scan_depth参数控制空闲页数量。
Asunc/Sync Flush CheckPoint:重做日志不可用时,需要强制将一些Flush列表中的脏页刷新到磁盘。
Dirty Page too much CheckPoint:当脏页数量太多时,将一部分脏页刷新到磁盘,可以通过innodb_max_dirty_pages_pct参数设置脏页比例。
通常,8MB的重做日志缓存可以满足大部分应用。 在以下三种情况下,会将重做日志缓存刷新到磁盘重做日志文件中: Master Thread每一秒刷新。 每个事务提交时刷新。 重做日志缓存剩余空间小于1/2时刷新
Master Thread 每秒一次的操作:
1、日志缓冲刷新到磁盘,即使这个事务还没有提交
2、合并插入缓冲:判断若一秒前的io次数小于五次,则执行
3、至多刷新100个InnoDB的缓冲池的脏页到磁盘
4、如果当前没有用户活动,则切换到background loop
每十秒的操作:
刷新100个脏页到磁盘(可能):判断若前10秒的IO次数小于200次,则执行操作
合并至多5个插入缓存(总是)
日志缓冲刷新到磁盘(总是)
删除无用的Undo页(总是):每次最多尝试回收20个Undo页
刷新100个或10个脏页到磁盘(总是):若脏页比例超过70%,则刷新100个脏页,否则刷新10个脏页。
innodb关键特性:
1、插入缓冲(性能上的提升),基于b+树
在InnoDB中,主键是行唯一的标识,主键列默认是聚集索引(数据行的物理顺序与索引列的逻辑顺序相同),一定是唯一的,在索引页插入一个聚集索引时,一定需要读取其他索引页判断是否唯一,这是主键约束必须付出的代价,不在Insert Buffer的优化范围内。
但我们需要关注一点:通常我们是不会修改主键的,而其他数据列可能会频繁的修改。
在一张表中,我们可能在除主键列以外的其他数据列建立多个非聚集、非唯一索引,那么我们在插入大量数据时就需要插入大量的非聚集索引,Insert Buffer提供了一种优化方式:对于每一次的插入不是直接写到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在则直接插入;若不在,则先放到Insert Buffer中,再按照一定的频率将索引数据合并到索引页中,这样一来,Insert Buffer中的插入操作通常能够合并到一个操作中(因为在一个索引页中),这样就大大提高了非聚集索引的插入性能。
2、两次写(可靠性的提升)--- 一个page默认16KB
当InnoDB正在刷新某个脏页到磁盘中,发生了宕机,这种情况称为部分写失效,我们通常的想法是通过重做日志进行恢复,但重做日志中记录的是关于数据页的物理操作,如偏移量800,写入’aaaa’,那么如果在宕机时数据页本身发生了损坏,那么重做则是没有意义的。因此,在进行重做恢复前,要有一个页的副本,先对磁盘数据页进行还原,得到一个正确的版本,然后再进行重做。
内存中保存一个2MB的doublewrite buffer,硬盘中保存一个连续的2MB空间(共享表空间)。先把脏页写到doublewrite buffer,然后把doublewrite buffer顺序写入共享表空间,最后再由doublewrite buffer写入数据文件。崩溃时就先从共享表空间中恢复表,然后再通过redo log重做
3、自适应哈希索引
自适应哈希索引是指InnoDB会监控对表上各索引页的查询,如果观察到可以通过建立Hash索引提高速度,则自动根据访问的频率和模式为某些热点页建立Hash索引。
自适应哈希索引要求对某个页的连续访问模式必须是相同的,比如 where a = xxx,且满足以下要求: 以该模式访问了100次页通过该模式访问了N次,N = 页中记录数 / 16
4、异步IO
当前数据库系统都采用Async IO的方式来处理IO操作。
AIO指的是发出一个IO请求后,不等待该请求完成则继续发送其他IO请求,当所有请求发送完成后,等待所有IO请求的完成。
AIO可以进行IO Merge操作,即将多个IO合并为一个IO,如分别读取多个相邻的16K数据页可以合并为一次读取N * 16K的数据页。
5 刷新邻接页
当刷新一个脏页时,InnoDB会检测该页所在区的所有页,如果是脏页则一起刷新,这样就可以将多个IO操作合并为一个IO操作,这个特性在传统机械硬盘上具有显著的优势,在固态硬盘上不建议使用,可以通过innodb_flush_neighbors参数配置。
5.5 innodb数据页结构
InnoDB中标都是按照主键顺序组织存放的,这种存储方式的表成为组织索引表从逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间,表空间又由段、区、页组成。
区:区是由连续页组成的空间,任何情况下每个区大小都为1MB,为了保证区中页的连续性,InnoDB存储引擎一次从磁盘申请4~5个区。默认情况下,页大小为16KB,即一个区中有64个连续的页。
索引被存储在索引段中,数据被存储在数据段中,此外还有回滚段等。每创建一个索引就会创建两个段:一个是数据段(叶子结点),一个是索引段(非叶子结点)
对于聚集索引(一般是主键索引)数据段存储的是索引关键字和业务行(所有字段);对于非聚集索引,数据段存储的是索引关键字和主键;如果通过非聚集索引查询,需要先通过B+树查出主键,再通过主键从聚集索引中二次查询具体的行,这称为回表。 下图:左边为二级索引(非聚集索引),右边为主键索引(聚集索引)
每个页都有一个对应的从0开始的编号,这个编号叫做页号。因为表空间的数据文件会被划分成大小相等的页,所以知道页号,再根据文件的初始位置,就可以计算出页在磁盘中的准确地址。同理,一张表对应一个聚集索引,而聚集索引元数据中指定了root page的页号,因此Innodb引擎可以根据页号和页大小计算出索引B+树root page的准确地址,从而对整个表数据进行操作。
page主要用来存储业务相关的数据,但是为了管理内存分配而存在的extent和segment信息也需要page存储。innodb根据page存储内容不同分以下几类:
FSP HDR 页: 一个表空间可能对应多个数据文件,每个文件都有自己的编号。 表空间是数据库中最顶层的结构,通过系统表空间中的元数据可以查询对应的表
空间文件等元信息,却无法查询当前表空间对应的段、区等信息,因此也无法获取表空间中页的存储状态。
为了使表空间的物理存储有一个对外访问的入口,规定表空间中的0号文件的0号page页中存储表空间信息以及当前表空间所拥有的段链表的指针。
任何一个页都由页头、页身和页尾组成。
一个page默认16KB,而段和区对应的指针数据量并不大,因此只需要部分头信息就可以维护。而剩下的大部分空间,则用来存储当前表空间拥有的部分发区实体
信息。
FSP HDR页就像一个表空间的封面页,是整个表空间的入口页
INODE 页:同区/簇对应的Entry信息一样,表空间只是指向了各种状态的段页(非段实体)链表,而未存储段信息本身。inode页就是用来存储描述段信息 inode entry的页。
一个inode页默认存储85条段实体,每个实体又指向了本段对应的不同状态的区/簇链表:未使用的区/簇链表、已写满的区/簇链表、未写满的区/簇链表。
Index页以上的页均是存储物理空间使用状态,并用于管理区/簇和段本身的页。 index页则是用于最终存储业务数据。 innodb中表数据是通过聚集索引组织存储的,
而叶子节点存储在一个段中,非叶子节点存储在另一个段中,但最终都会存储在Index类型的页中。
数据页主要由以下七个部分组成,下面逐步介绍:
1. File Header(38字节)----记录页的头信息
下面列举一些重要内容
FIL_PAGE_PREV: 当前页的上一个页(B+树的特性决定了叶子结点必须是双向列表)
FIL_PAGE_NEXT: 当前页的下一个页
FIL_PAGE_LSN : 代表该页最后被修改的日志序列位置(LSN)
2. Page Header(56字节)----记录数据页的状态信息
下面列举一些重要内容
PAGE_N_DIR_SLOTS: 代表Page Directoty有多少个槽,每个槽占用两个字节
PAGE_HEAP_TOP:代表空闲空间开始位置的偏移量
PAGE_FREE:代表删除的记录字节
PAGE_LAST_INSERT:代表页最后插入的位置的偏移量
PAGE_DIRECTION:代表数据的插入方向,通过自增长方式插入的记录,方向为向右
PAGE_N_DIRECTION:代表一个方向连续插入记录的数量
PAGE_N_RECS:表示此页的行记录数
PAGE_LEVEL: 当前页在索引树中的位置,0x00代表叶结点,页结点总是在第0层。
PAGE_INDEX_ID:索引ID,表示当前页属于哪个索引。
PAGE_BTR_SEG_LEAF:B+树数据页非叶节点所在段的segment header(仅在ROOT页中定义)
PAGE_BTR_SEG_TOP:B+树数据页所在段的segment header(仅在ROOT页中定义)
3. Infilmum和Supremum Record
在Innodb中每个数据页都有两个虚拟行记录,用来限制记录边界。Infilmum记录比该页中任何主键值都要小,Supremum
比任何值都要打。
4. User Record
Compact行记录格式:
变长字段长度列表 + NULL标志位 + 记录头信息 + 列1数据 + 列2数据....
NULL标志位:代表行数据中是否有NULL值
记录头信息record header(40字节):
delete_flag :标志该行是否删除
record_type : 记录类型,000为普通记录,001表示B+树节点指针,010为Infilmum,011为Supremum
next_record : 指向页中下一条记录的相对位置,链表形式。
隐藏列:
TransactionID
Roll Pointer
5. Free Space
一个链表结构,当一条记录被删除后,该删除空间会加入到空闲链表中
6. Page Directory(逆序存放)
Page Directory中存放了记录的相对位置,称为目录槽(InnoDB中的槽是稀疏目录, 即一个槽中可能包含多个目录)。
在Slots中的记录按照索引值顺序排放,这样可以二叉查找。
7. File Trailer
作用:检测页是否完整地写入磁盘中。checksum算法
2、 回滚段
redo log是物理日志,undo log是逻辑日志。
undo log既做回滚操作,也做MVCC。undo log也需要产生redo log(非临时undo)
https://blog.csdn.net/bohu83/article/details/80941162?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166377524316800182169843%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=166377524316800182169843&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-20-80941162-null-null.nonecase&utm_term=innodb&spm=1018.2226.3001.4450
回滚段都存储于共享表空间中
INNODB支持的回滚段总共有128*1024=131072个,TRX_RSEG_UNDO_SLOTS数组的元素每一个元素对应一个页面,这个页面对应一个段,页面号就段首页的页面号。
在每一个事务开始的时候,都会分配一个rseg,就是从长度128的数组中,根据最近使用情况,找一个邻近位置的rseg,再这个事务的生命周期内,被分配的
rseg就会被这个事务所使用。
在事务要存储回滚记录的时候,就会从1024个slot中,根据类型(插入还是更新)找到空闲的槽作为自己的undo段。如果已经申请过同类型的槽,则直接使用。
否则就需要新创建一个段。并将段首号写入到这个rseg对应的空闲槽中。这样结构就与事务具体结合起来了。当然找不到空闲位置,就报异常了。
所有回滚段都记录在trx_sys->rseg_array,数组大小为128,分别对应不同的回滚段;
rseg_array数组类型为trx_rseg_t,用于维护回滚段相关信息;
每个回滚段对象trx_rseg_t还要管理undo log信息,对应结构体为trx_undo_t,使用多个链表来维护trx_undo_t信息;
事务开启时,会专门给他指定一个回滚段,以后该事务用到的undo log页,就从该回滚段上分配;
事务提交后,需要purge的回滚段会被放到purge队列上(purge_sys->purge_queue)。
trx_rseg_t 源码在innobase/include/trx0rseg.h
当开启一个读写事务时(或者从只读事务转换为读写事务),我们需要预先为事务分配一个回滚段:对于只读事务,如果产生对临时表的写入,则需要为其分配
回滚段,使用临时表回滚段(第1~32号回滚段)。
只读事务与读写事务的区别在于他们随后会不会记录redo log(undo也是需要redo来保护的)。
完成Undo log写入后,构建新的回滚段指针并返回(trx_undo_build_roll_ptr),回滚段指针包括undo log所在的回滚段id、日志所在的page no、以及page
内的偏移量,需要记录到聚集索引记录中。
由于在修改聚集索引记录时,总是存储了回滚段指针和事务id,可以通过该指针找到对应的undo 记录,通过事务Id来判断记录的可见性。当旧版本记录中的
事务id对当前事务而言是不可见时,则继续向前构建,直到找到一个可见的记录或者到达版本链尾部。
从上面的分析我们可以知道:update_undo产生的日志会放到history list中,当这些旧版本无人访问时,需要进行清理操作;另外页内标记删除的操作也需要
从物理上清理掉。后台Purge线程负责这些工作。
在数据页中,每一行记录还有额外三个字段
下面介绍了是三个字段:
6字节的事务ID(DB_TRX_ID) 表示最后一个事务的更新和插入,(每处理一个事务,其值自动+1)
7字节的回滚指针(DB_ROLL_PTR)指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针
6字节的DB_ROW_ID 标识插入的新的数据行的id
当然还有个删除位,DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候。
事务链表
MySQL中的事务在开始到提交这段过程中,都会被保存到一个叫trx_sys的事务链表中,这是一个基本的链表结构:
事务链表中保存的都是还未提交的事务,事务一旦被提交,则会被从事务链表中摘除。
RR隔离级别下,在每个事务开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view)
RC隔离级别下,在每个语句开始的时候,会将当前系统中的所有的活跃事务拷贝到一个列表中(read view)
read view
上面可知MVCC实现了多个并发事务更新同一行记录会时产生多个记录版本,那问题来了,新开始的事务如果要查询这行记录,应该获取到哪个版本呢?
就需要read view来解决行的可见性问题。
Read View是事务开启时当前所有事务的一个集合,这个类中存储了当前Read View中最大事务ID及最小事务ID。祥见read_view_struct这个结构体
low_limit_id 前者表示事务id大于此值的行记录都不可见。
up_limit_id 后者表示事务id小于此值的行记录都可见。
purge操作:
delete和update操作并不会直接删除原有数据。对于delete操作,只是将主键列等于1的记录delete flag设置为1
先从histroy list中找最新的undo log,再找到对应的undo log页清理其他可以被清理的记录,
3、 gap lock
间隙锁是在索引记录之间的间隙上的锁,或者在查询的第一条索引记录之前或最后一条索引记录之后的间隙上的锁。
间隙锁通过锁住一个范围,如果其他事务想要在这个范围内插入该范围不存在的数据时就会被阻塞。
4、联合索引
https://www.jianshu.com/p/6412ddaaca3f
https://blog.csdn.net/qq_44761854/article/details/123153502
5、innodb的锁 锁表:INNODB_LOCKS
现象1:经测试,事务一修改了值后,事务2再去修改被加锁的值,会被阻塞住,如果长时间阻塞会超时。
现象2:经测试,事务一修改了值,事务二去读同样的值,不会被阻塞住,而是返回旧值。
三种行锁算法:
1、Record Lock:单个行记录上的锁
2、Gap Lock:间隙锁,锁一个范围,但不包括记录本身
3、Next-Key Lock:Gap Lock + Record Lock,锁一个范围,包含记录本身。
Next-Key Lock是为了解决幻读问题(幻读只会在可重复读隔离级别上才会出现)。
当查询的索引有唯一属性时,会将Next-Key Lock优化,降级为Record Lock。若是辅助索引,则不会降级
如果对辅助索引加锁查询(FOR UPDATE),那么对于聚集索引(唯一索引),只会对具体的某个索引加上Record Lock,而对于辅助索引,则会加上Next-Key Lock(对上一个键值
和下一个键值都加上gap lock)
锁带来的问题
1. 脏读 : 读到另一个事物未提交的数据。只有在读未提交的隔离级别才会产生
2. 不可重复读
3. 丢失更新:数据库在任何隔离级别下都不会导致理论上的丢失更新。
在Next-Key Lock算法下,对于索引(辅助索引)的扫描,不仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围(gap)。因此在这个范围的插入都是不允许的。
这样就避免了另外的事务在这个范围内插入数据导致的不可重复读的问题。因此,InnoDB存储引擎的默认事务隔离级别是READ REPEATABLE,采用Next-Key Lock
算法避免了不可重复读的现象。
死锁:两个或两个以上的事务在执行的过程中,因争夺锁 资源而造成的一种相互等待的现象。
解决方案:
1、超时机制。当两个事务互相等待时,当一个等待时间超过阈值时,回滚此事务,另一个就能继续执行。
2、采用wait-for graph(等待图)的方式来进行死锁检测,检测到死锁则进行回滚。
需要维护两种信息:锁的信息链表、事务等待链表。
根据两种信息构造一个图,如果图中存在回路,那么就代表死锁。
死锁发生的概率与以下几点因素有关:
系统中当前事务的数量,数量越多发生死锁的概率越大。
每个事务操作的数量,每个事务操作的数量越多,发生死锁的概率越大。
操作数据的集合,越小则发生死锁的概率越大。
6、innodb的每一行数据
InnoDB存储引擎的页为16KB,即16384字节,那么如何保存比这大的65532字节数据呢?当发生行溢出时,数据存放在页类型为
Uncompress BLOB(未压缩二进制大对象页)页中。在数据页中,其实只保存了VARCHAR的前768字节的前缀数据,之后是偏移量,
指向行溢出页(Uncompress BLOB Page)。
多长的VARCHAR是保存在单个数据页中,多少才开始分裂到BLOB中呢?原理上来说,对于每一个数据页,都是索引组织的(B+)树,
那么每个结点至少得有两条记录,否则就变成了链表。因此如果整个数据页只能放入一条记录,那么InnoDB会自动将数据存放
到溢出页中。对于VARCHAR,经过多次建表测试,阈值为8098字节。
对于CHAR和VARCHAR,在建表指定了多字节字符集的情况下行存储是没有区别的。
7、InnoDB中日志格式
重做日志都是以512字节进行存储的,称之为重做日志块,每块大小512字节。
1.日志块头(12字节)
LOG_BLOCK_HDR_DATA_LEN: 表示log block所占用的大小
LOG_BLOCK_FIRST_REC_GROUP:表示log block中第一个日志所在的偏移量
2.日志内容
3.日志块尾(4字节)
LSN:log Sequence Number,日志序列号,含义有:
重做日志写入的总量
checkpoint的位置
页的版本
恢复:
重做日志文件:记录了对于InnoDB存储引擎的事务日志。
每个InnoDB都至少有一个重做日志组,每个文件组下至少有两个重做日志文件。因此为了更高的可靠性,用户可以设置多个镜像日志组,将不同的文件组放在
不同的磁盘上,以提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB会先写重做日志文件1,当达到文件最后
时,会切换至重做日志文件2,再当重做日志文件2也被写满时,会再切换到重做日志文件1中。
重做日志从缓冲中刷到磁盘上的条件:
1、由主线程master thread每秒中会将重做日志缓冲刷到磁盘上,不论事务是否已经提交
2、由参数innodb_flush_log_at_trx_commit控制,表示在提交操作时,处理重做日志的方式,分别为:
1:表示在执行commit时就将重做日志缓冲同步写到磁盘上,即伴有fsync的调用。
2:表示将重做日志异步写入磁盘,即写到文件系统的缓存中,不会调用fsync
为了保证ACID中的持久性,需要设置为1,如果考虑性能可以设置为2。
8、数据库备份
热备:在数据库运行时直接备份,对正在运行的数据库操作没有影响,也叫在线备份。
ibbackup是官方提供的热备工具,有更好的开源免费热备工具XtraBackup
工作原理: (看起来和redis中的RDB很像,因此RDB也是一个热备)
1.记录备份开始时,redo log日志文件checkpoint的LSN
2.复制共享表空间文件以及独立表空间文件
3.记录复制完表空间文件后,redo log日志文件checkpoint的LSN。
4.复制在备份时产生的重做日志。
恢复过程:
1.恢复表空间文件
2.应用重做日志文件
优点:
1.在线备份,不阻塞任何的SQL语句。
2.备份性能良好,备份的实质是赋值数据库文件和重做日志文件。
3.支持压缩备份,通过选项,可以支持不同级别的压缩
4.跨平台支持
冷备:在数据库停止的情况下,复制相关的数据库文件即可
innodb引擎的冷备只需要备份MySQL数据库中的frm文件,共享表空间文件,独立表空间文件,重做日志文件,另外建议定期
备份MySQL数据库的配置文件my.cnf,这样有利于恢复的操作。
通常做法:写脚本进行冷备操作,还可能对备份完的数据库进行打包和压缩,最好将本地产生的备份放到一台远程服务器中。
冷备优点:
备份简单,只需复制相关文件即可。
备份文件易于在不同操作系统,不同MySQL版本上进行恢复。
恢复想当简单,只需把文件恢复到指定位置即可。
冷备缺点:
InnoDB存储引擎中冷备文件通常比逻辑文件大
注意不同平台的兼容性问题。
温备:也是在数据库运行时进行的,但是会对当前数据库的操作有所影响。
逻辑备份:指备份出的文件内容是可读的,一般是文本文件,比如一条条SQL语句,或者是表内实际数据组成。
mysqldump就是一个逻辑备份工具。
逻辑备份的恢复操作比较交单,因为备份文件就是导出的SQL语句,一般只需要执行这个文件就可以了。
裸文件备份
完全备份:MySQL中二进制日志(binlog)的备份就是一个完全备份
增量备份:
MySQL本身不支持真正的增量备份,而XtraBackup工具支持对于InnoDB存储引擎的增量备份。工作原理如下:
1.首先完成一个全备,并记录下此时检查点的LSN。
2.在进行增量备份时,比较表空间中每个页的LSN是否大于上次备份时的LSN,如果是,则备份该页,同时记录当前检查点的LSN
日志备份
快照备份(热备)
MySQL数据库本身并不支持快照功能。因此快照备份是指通过文件系统支持的快照功能对数据库进行备份。备份的前提是
将所有数据库文件放在同一文件分区中,然后对该分区进行快照操作,这里以Linux下的LVM为例进行介绍。
LVM是LINUX系统下对磁盘分区进行管理的一种机制,在硬盘和分区之上建立了一个逻辑层,提高磁盘分区管理灵活性。
层级关系:
多个磁盘分区----一整块卷组----任意个逻辑卷----创建文件系统
简单说,用户可以通过LVM由物理块设备(硬盘)创建物理卷,由一个或多个物理卷创建卷组,最后从卷组中创建任意个逻辑卷
LVM使用了写时复制的(Copy-on-write)技术来创建快照,当创建快照时,仅复制原始卷中数据的元数据。原始卷上有写操作
时,快照会跟踪原始卷块的改变,将要改变的数据在改变之前复制到快照预留的空间里。
用LVM快照备份InnoDB存储引擎表的方式:将引擎相关的文件如共享表空间、独立表空间、重做日志文件等放在同一个逻辑卷中,
然后对这个逻辑卷做快照备份即可。这样做可以进行在线备份
9、复制
复制可以用来作为备份,但功能不仅限于备份,其主要功能有:
1、数据分布,可以在不同的数据中心之间实现数据的复制。
2、读取的负载平衡,通过建立多个从服务器,可将读取平均地分布到这些从服务器中,并且减少了主服务器的压力
3、数据库备份
4、高可用性和故障转移,可以减少故障的停机时间和恢复时间。
一个比较好的方案是通过对从服务器上的数据库所在分区做快照,以此来避免误操作对赋值造成的影响。当发生主服务器上的误操作时,
只需将从服务器上的快照进行复制,再根据二进制日志进行point-in-time的恢复即可。因此快照+复制的备份架构如下:
从服务器通过I/O线程获取到主服务器的二进制日志,并进行读取执行恢复,同时通过LVM快照功能进行备份。
另外从服务器上不做修改操作,只做只读操作。
10、性能
OLTP(在线事务处理)是I/O密集型应用,OLAP(在线分析处理)是CPU密集型应用。
内存:内存大小直接反映数据库的性能。对于InnoDB引擎,既缓存数据,又缓存索引,并且将它们缓存于一个很大的缓冲池中,即InnoDB Buffer Pool中
判断InnoDB内存是否达到瓶颈:通常InnoDB引擎的缓冲池命中率不应该小于99%
文件系统:文件系统中提供的功能可以额外关注,比如ZFS支持快照功能,