本博客主要是对《MySQL是怎样运行的》一书的内容进行整理
数据库中,各个数据页可以组成一个双向链表,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表。
可以通过数据页的页目录寻找对应的槽,通过槽使用二分法快速查找到对应的信息。
查找信息流程
1、定位到记录所在的页
2、从所在的页内查找相应的记录
新分配的数据页编号可能并不是连续的,也就是说我们使用的这些页在磁盘上可能并不挨着
1、下一个数据页中数据主键的大小必须大于上一个页中用户记录的主键值
在对页中的记录进行删改操作的过程中,我们必须通过一些诸如记录移动的操作来始终保证这个状态一直成立:下一个数据页中用户记录的主键值必须大于上一个页面中用户记录的主键值,这个过程也可以称为页分裂。
2、给所有的页简历一个目录项
每个页对应一个目录项,每个目录项记录了页号和页的用户目录中的最小的主键值
InnoDB设计者发现这个目录项与数据记录十分相似,只不过目录项的两个列是页号和主键。所以他们将目录项视作一条记录按照相同方式进行管理(record_type为1的记录即为目录项记录)。
为了快速管理构造出复杂的树索引模式,类似多级目录,大目录套小目录,小目录里才是实际的数据
抽象出来的结构如下所示:
即B+树。
我们真正的用户记录都放在B+树最底层的节点上(只有最底层是数据页),被称之为叶子结点。剩余的节点成为非叶子节点,存储目录项。
一般而言,我们用到的B+树不会超过4层,即查找最终数据只需要内存和磁盘IO交互4次。
特点:
* 使用记录主键值的大小进行记录和页的排序
* B+树的叶子节点存储的是完整的用户记录。所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。
我们把具有这两个特点的B+树称为聚簇索引,所有完整的用户记录都村房子啊这个聚簇索引的叶子节点处。在InnoDB引擎中,聚簇索引就是数据的存储方式(所有的用户记录都存储在了叶子结点),即“索引即数据,数据即索引”。
聚簇索引只能在搜索条件是主键值时才能发挥作用,那如果要以其他列作为搜索条件怎么办呢?
答:多建几棵B+树,不同B+树按照不同规则对数据进行排序。
新的B+树的特点:
* 使用新的索引列的大小进行记录和页的排序
* B+树的叶子节点存储的并不是完整的用户记录,而是索引列+主键这两个列的值(因此需要进行回表操作,无法直接得到数据,需要再次使用索引)。
* 目录项记录中不再是主键+页号的搭配,而是索引列+页号+主键值的组合(添加主键值以防出现索引列值一样无法进行页划分的问题)。
回表:通过索引列获取主键的值后,需要再次使用聚簇索引进行数据查询
使用回表的原因:直接把完整的用户目录放到索引节点太过浪费空间
因为需要回表,搜索两次,这种B+树也被称为辅助索引或者二级索引。
其核心问题是叶子结点存储信息不全。
我们可以同时给多个列建立索引
例如:
1、先把各个记录和页按照C2列进行排序
2、在记录的C2列相同的情况下,再采用C3列进行排序
联合索引特点:
* 每条记录项目录都由C2、C3、页号、主键值这4部分组成。
* B+树叶子节点处的用户记录由C2列、C3列和主键C1列组成。
1、根节点固定不移动
* 每当为某个表创建一个B+树索引,都会为这个表创建一个根节点页面。初始这个跟页面为空
* 随后向表中插入数据时,先把数据存储在这个根节点中。
* 随着数据增多,会将根节点全部数据放到一个新的页中,该页进行页分裂,根节点升级为存储目录项的页。
一个B+树的根节点自创建之日起便不会再移动(即页号不再改变),该信息会被存储在数据字典中。
2、创建二级索引实际是创建(索引列,主键值)的联合索引,主键的信息不可或缺,否则可能出现目录项值一样发生冲突的问题。
MyISAM将索引和数据分开,通过索引找到行号,进而通过行号找到数据
InnoDB“索引即数据,数据即索引”,MyISAM“索引是索引,数据是数据”。
* 每个索引都对应一颗B+树,B+树分为好多层,最下边一层是叶子结点,其余的是内节点。所有用户记录都存储在B+树的叶子节点,所有目录项内容都储存在内节点。
* InnoDB会自动为主键建立聚簇索引,聚簇索引的叶子节点会包含完整的用户记录。
* 可以为感兴趣的列设立二级索引,如果想通过二级索引查找完整的用户记录,需要执行回表操作。
* B+树的每层节点(以页为单位)都按照索引大小构成了双向链表,节点内数据按照索引排列成单向列表。
* 可以在页目录中通过二分法快速定位到索引列等于某个值的记录。
* 空间上的代价:每建立一个索引,都需要建立一棵B+树。一页16KB,一个很大的B+树由很多数据页构成,会占用很大的存储空间。
* 时间上的代价:
1、每当对表中数据进行增删改操作时,都需要修改各个B+树索引,同时增删改查操作可能对节点和记录的排序造成破坏。因此存储引擎需要额外的时间进行页面分裂、页面回收等操作,以维护节点和记录的排序。如果创建了很多索引,这会浪费很多时间。
2、在执行查询语句前,首先要生成一个执行计划。一般情况下,一个语句在执行过程中最多使用一个二级索引,此时如果建了太多的索引,会导致成本分析过程耗时太多,从而影响查询语句的执行功能。
有时候使用索引效率不一定能超过全表检索:
范围搜索得到一个个分散的页号,回来依次回表搜索,都需要花费时间
如果覆盖的数据量很大,那一个一个回表不如直接使用聚簇索引全局遍历
具体使用索引还是直接全局遍历由MySQL优化器根据统计数据进行判断,决定最终选择
核心浪费的时间还是页面与磁盘的IO时间
MySQL中,如果order by子语句使用了索引列,就有可能省去在内存或磁盘中排序的步骤。
不可以使用索引进行排序的几种情况
1、ASC、DESC混用
2、order by自居后面的列的顺序与索引列的顺序不一致(a b c, a c b)
3、排序列包含非同一个索引的列
4、排序列是某个联合索引的索引列,但是这些排序列在联合索引中并不连续(a,b,c为索引,根据a,c排序)
5、用来形成扫描区间的索引列与排序列不同
6、排序列不是以单独列名的形式出现在order by子句中(upper(key1))
1、只为用于搜索、排序或分组的列创建索引
2、考虑索引列中不重复值的个数
如果一个索引里不重复值的个数占全部值比例太低,使用该索引可能会出现大量回表操作,毫无意义,没必要创建。
3、索引列的类型尽可能小
能使用int就不要使用big int。数据类型越小,索引占用的存储空间就越小,一页就能放更多数据,磁盘IO带来的性能损耗也就越小。
4、为列前缀建立索引
例如,对于字符串类型,为了避免字符串过长,可以直接以字符串前10个字符构建索引,减小索引大小。这样select key="" 时更快,但是order by无用,都需要进行权衡。
5、覆盖索引
尽量不要select *,而是直接select key,id等,以减少回表过程,直接获取对应的值
6、让索引列以列名的形式在搜索条件中单独出现
MySQL并不会尝试简化key*2<4这种式子,因此key*2<4或者upper(key)这种条件都无法直接使用key对应的索引。
7、新插入记录时尽量按照主键大小顺序插入,否则会需要页面分裂等操作,造成性能损耗
8、减少冗余和重复索引
重复索引较易理解,冗余索引例如(a,b,c)和(a),此时已经有了按a排序的索引,没必要单独为a构建索引。(内部只要值一样先后顺序是否按主键有时并不重要)
MySQL里表存储为ibd文件,初始12M,随后会根据数据的增大而自增大。
当数据量过大,表空间页过多时,为了更好地管理页面,InnoDB提出了区(extent)的概念。对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。无论是系统表空间还是独立表空间,都可以看成是由若干个连续的区组成的,没256个区分成一组。
B+树每一层中的页都会形成一个双向链表,如果以页为单位来分配存储空间,双向链表相邻的两个页之间的物理位置可能离得非常远。
如果双向链表中相邻的两个页的物理位置不连续,对于传统的机械硬盘来说,需要重新定位刺头位置,也就是会产生随机IO,这样会影响磁盘性能。
所以应该尽量让页面链表中相邻的页的物理位置页相邻,这样在扫描叶子节点中大量的记录时才可以使用顺序IO。
因此引入了区(extent)的概念。一个区就是在物理位置上连续的64个页,这样虽然会产生一点资源浪费,但是会消除大量的随机IO。
我们在使用B+树执行查询时只是在扫描叶子节点的记录,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中,扫描效果就大打折扣了。
因此InnoDB对叶子节点和非叶子节点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成两个段:叶子节点段和非叶子节点段
为了减小基本单位增大导致的资源浪费问题,InnoDB提出了碎片区这个概念,同一个区可以存储不同信息,帮助实现功能。
制约因素:算法效率、实际的物理结构(磁盘)原理
另外为了调节磁盘和CPU之间的速率差异,InnoDB还设置了默认大小为128M的Buffer Pool,用于缓存数据。
通常的缓存算法:LRU