MySQL讲一讲

目录

前言

概述

1.mysql逻辑架构

1.1总体架构

1.2存储引擎类型

1.3存储引擎之间的区别

2.辅助存储管理(重点)

2.1存储器层次

2.2存储器层次间传输数据

2.3易失和非易失存储器

2.4磁盘结构

2.5磁盘存取特性

2.6 I/O开销的主导地位

2.7组织磁盘上的数据-定长记录

2.8 组织磁盘上的数据-变长记录

2.9 组织磁盘上的数据-不能装入一个块中的记录

2.10 组织磁盘上的数据-记录的插入

3.InnoDB数据的存储

3.1逻辑架构

3.2 表文件

3.3 行记录

3.4 行溢出

3.5 数据页结构

4最佳实战

5 后记


前言

数据库是我们平时接触最多的中间件,大多数同学对数据库的理解,只停留在了CURD操作。但是在高并发以及服务高可用需求的情况下,数据库往往是整个系统服务的瓶颈所在,也是重构中最难优化的部分。

所以,在项目初期设计表结构时,应尽量遵循设计规范,避免后期优化时,对系统带来严重影响。

但是大家有没有想过,我们遵循的设计规范到底是否合理,设计规范又是怎么形成的?这就是我们本篇wiki的要解决的问题,去看看数据库设计实现原理。

概述

数据库种类很多,有关系型数据库,非关系型数据库,有内存型数据库,也有持久化型数据库。本篇wiki以关系型数据库,MySQL为例讲解数据库实现原理,暂不涉及其他数据库。

由于本人能力有限,对于MySQL也只能算是一知半解,接下来的讲述中如果有讲述不清楚,或者不对的地方还望不吝赐教。

MySQL是一个庞大的系统,本文不可能涵盖所有,本文按照MySQL整体逻辑架构、存储管理以及InnoDB存储引擎的简单介绍,三个主要系统来讲述。

1.mysql逻辑架构

 

1.1总体架构

  • 最上层的服务并不是MySQL所独有的大多数基于网络的服务器程序都有类似的结构如:连接处理、授权认证、安全等
  • 大多数MySQL的核心服务功能都在第二层如:查询解析、分析、优化、缓存以及所有的内置函数跨存储引擎的功能都在这一层:存储过程、触发器、视图等
  • 最底层,存储引擎负责数据的存储和提取,每种存储引擎都有其优势和劣势上层通过API与存储引擎交互,API屏蔽了不同存储引擎间实现差异不同存储引擎之间不会相互通信,(就像模范,我们设计模范,由数据提供方具体实现)只是单纯响应上层的请求引擎是基于表的,而不是基于数据库的。很多大公司,也是在使用innoDB,只不过是对innoDB做了修改。
     

1.2存储引擎类型

MySQL5.0之后提供了以下几种存储引擎

 点击此处展开...

对于不同存储引擎我们需要考虑一下几个方面

 点击此处展开...

1.3存储引擎之间的区别

  • MyISAM

    MyISAM存储引擎不支持事务。其优势是访问速度快。所以如果一个表对事务没有要求,并且以Select为主。那么使用MyISAM存储引擎是比较合适的。例如系统的配置信息。黑名单、情报信息(恶意IP)等等。每张MyISAM表在磁盘上存储三个文件

      1.  frm文件(表的定义文件,表的结构)

      2.  MYD文件(表的数据存储文件)

      3.  MYI文件(表的索引存储文件)

  • InnoDB

    现在MySQL默认都是使用InnoDB存储引擎。他提供了很多非常有用的功能例如:行锁、事物、外键约束、自增ID。但是相比MyISAM存储引擎他会占用更多的磁盘来保留数据和索引。
    InnoDB的数据和索引的两种存储方式,先做一个简单介绍,后面还会详细讲解:
    •使用共享表空间存储。这种存储方式中表结构保存在.frm文件中。数据文件保存在innodb_data_home_dir中。索引文件保存在innodb_data_file_path中。
    •使用多表存储空间。这种存储方式表结构保存在.frm文件中。表的数据和索引一起存储在.idb文件中。多表存储空间的文件没有大小限制。现在一般都配置使用多表空间存储。

  • Memory

    Memory存储引擎的表数据文件存储于内存之中。但是表结构空间.frm文件存储于磁盘。他的优点是内存访问速度非常快,并且默认使用hash索引。缺点是一旦数据库关闭。表中的数据就会丢失。每个Memory类型的表都可以设置表数据量的大小。可以使用max_heap_table_size来配置。默认是16M。

     

2.辅助存储管理(重点)

这是我们需要重点讲解的一部分。

辅助存储其实是从英语翻译过来的,原文是Secondary Storage,翻译成辅助存储还是比较合适的,其实我们实际看这个存储,就可以简单的当成磁盘。

2.1存储器层次

我们看一下存储器的层次,数据库的主要功能肯定就是存储数据,那么这些数据到底如何存储,他会涉及不同层次的存储器,那么我们从下面往上来看。

1.最底下一层,cache层,也就是高速缓存,这一层包括CPU的寄存器,CPU共享的以及独享的高速缓存,也就是L1、L2、L3,数据和指令从内存中移到高速缓存中这个时间非常短,只要几纳秒。

2.主存储器,也就是Main Memory,翻译成主存储器也算是直译,其中我们中文更常说的就是内存,后面的解说中主存储器也就等同于内存。它是计算机的活动中心,磁盘上的数据都必须读取到内存中,然后再从内存读取到高速缓存中读,将数据从内存移到高速缓存或者处理器的速度大概在10-100纳秒。这个速度也是非常快的,快到我们几乎不用考虑。

3.辅助存储层,典型的辅助存储器就是磁盘,也就是我们俗称的硬盘。磁盘到主存间传送一个字节的时间在10毫秒,这里需要注意是传送一个字节的时间,但我们平时往往是大段数据进行读取,那么这个时间开销就比较大了。接下来我们会详细讲解。这里稍微聊一下虚拟内存,大家在学习操作系统这门课的时候应该有了解,虚拟内存其实还是内存,它是为了解决物理内存不够大的情况的解决方案,这里不再展开讨论,感兴趣的同学可以看一下操作系统这本书。

4.第三级存储器,比如DVD,磁带等,我们不太常用,这里就不再详解了。开个脑洞,看过三体的同学可能会注意到一个细节,地球在被毁灭之前曾经试图将人类文明进行存储,要求存储时间至少亿年级别,作者最后给出的结果是,只有将故事刻在石头上。

 

2.2存储器层次间传输数据

正常情况下,数据都是在相邻的层次间进行传递,以从磁盘到内存间传递数据为例,由于访问想要的数据和查找指定的位置以及写入数据会消耗大量的时间,所以,当需要数据时,我们往往不是一个字节一个字节的读和写,而是把它打包起来,一块一块的去读和写。

在辅助存储中,磁盘就被划分为磁盘块,每个块的大小是4~64KB,整个数据块在主存和磁盘间移进移出,这里需要注意一下,比如某次只需1K的数据,而对应的这个块是64K,本次查询还是读取整个块的数据。那么在设计数据库时,最好的情况是什么呢,就是当读取这1KB的时候,是不是很有可能把其他的有用数据都同时读取出来,这样的话,我们读取一块的数据,有可能就满足了两次甚至三次查询要求,所以我们在设计表结构时,可以将那些一个字节一个字节的字段放在一起,这样的话,就可以减少读取磁盘的次数。

当然这里讲解的是设计DBMS,会比DBA更深入一层。其实这里讲的文件系统以及虚存,都是操作系统级别的概念,而数据库对于磁盘的管理往往绕过操作系统,同时也就绕开了操作系统的限制,它直接对磁盘的裸设备进行管理,它自己实现了一套文件系统,我们在学习操作系统时,也是有块的概念,但是这里的块和操作系统的块在概念上不完全一样,但是其设计思想和理念是一至的,就是为了不一个字节一个字节读取数据,那么为什么要这么设计,后面我们会用数学方法来说明。

在内存和高速缓存之间传输,是以高速缓存线为基本单位的,一般是32个连续字节。这里我们可以看到,即使读取速度是几十纳秒,都不会一个字节一个字节的读取,所以我们也希望整个高速缓存线也能够被一起使用。

 

2.3易失和非易失存储器

接下来我们聊一下存储器的易失性,这里的易失性并不是我们上面讲的,要存储几亿年,这是在时间上的易失性。而我们要讲的是在电力维度上的易失性。上面的两层都是在断电后可以不丢失数据的存储设备,下面的两层都是断电之后就会丢失的。

这里需要说明的一个点是,我们都说DBMS非常复杂,为什么会复杂呢,如果只是一个简单的文件系统的话,也就是上面讲的那点东西。之所以复杂,就是因为在数据库中的任何修改都不能认为是最终有效的,直到该修改被存储到非易失设备上。也就是,如果我们修改一个数据,在内存中是非常容易的,但是内存中修改完成之后还要同步到磁盘上,那么如何平衡易失和非易失上的性能差异,这是主要矛盾。当然这次分享的内容,没有涉及到事物,如果后续还有机会,我会给大家详细讲解事物,就会考虑到事物文件,事物文件也不是一直往磁盘上写,也是先写到内存中,再选择合适的时机同步到磁盘上,那么理论基础就在于这里了。

2.4磁盘结构

接下来就聊一下大家非常感兴趣的地方,从硬盘读取一个字节的时间是10毫秒的问题。

首先先看一下磁盘的结构,这里讲解的是机械硬盘的结构。

磁盘驱动器主要包括:磁盘组合和磁头组合。

磁盘组合就是图中圆片,类似于光盘一样,他们都是围绕中间的轴做高速旋转。

旁边的机械臂上就是磁头,磁头是悬浮在磁盘表面上的,来读取磁盘上磁信息,有磁信息是1,无磁信息是0。这就是所谓的二进制的保存。磁盘的上下两面都可以保存。

通过下面的俯视图,我们可以看到,这是一圈圈的同心圆,这些同心圆我们定义为磁道。也就是说,磁头就是在磁道上高速旋转划过一圈。

因为光盘是叠加在一起的,相同半径的磁道就组成了柱面。

磁道又会被组织成扇区,扇区是被间隙分割的圆的片段,间隙未被磁化成0或者1.间隙约占整个磁道的10%,间隙可以帮助定位扇区的起点。

就读写磁盘来说,扇区是不可分割的单位,这是由硬件决定的,每次读都是读整个扇区的数据,不可能只读一部分。

就磁盘错误来说,扇区也是不可分割的单位,也就是说,如果某个扇区中的一个数据位出错,那么整个扇区也都不能再用。

这里补充说明一下,我们看下面的图,这是一个比较老的硬盘,在最外面的同心圆,他的半径比里面的同心圆的半径要大,因为一个扇区存储的物理空间是一定的,512B的数据,那么里面的同心圆数据更加密集,外面的就更加稀疏。在后来新的硬盘中就不是这么处理的了,而是每个扇区都是等分的,也就是外圈的扇区数目比内圈扇区的数目要多。

还有一个特点,假设一个新的磁盘,里面没有任何数据,我们如果要在上面写数据,它是由最外面的磁道开始往里写的,(先不考虑删除),就会产生一个问题,因为磁盘的旋转,角速度是固定的,那外圈的线速度较大,同样单位时间内,它滑的扇区就会更多,这样的话,读取外磁道就比内磁道要快。通过这里的分析,大家应该能想到一个现象,就是刚买的电脑读写速度本来挺快的,但是用一段时间之后就变慢了,整理磁盘之后就会又快点,产生以上现象的原因就是因为磁盘是从外向里写的。当然,需要说明的一点,大家在学操作系统时书上应该也讲过,磁盘空间的分配与回收,整理磁盘之后速度之所以会提升,除了刚才讲的外磁道速度快之外,还有一个原因就是,分配空间时,连续的物理空间速度回更快。

 

2.5磁盘存取特性

上面的内容我们了解了磁盘的结构,那么我们继续来看一下磁盘到底是怎么读取数据的。

它主要有3个步骤:

1.第一步,磁盘控制器将磁头组合先定位到正确的柱面上。

2.第二步,磁盘是旋转的,将磁盘旋转到正确的扇区的起点。

3.第三步,就相应的扇区起点开始往后读。

这三步,分别产生了三个时间,寻道时间、旋转延迟、传输时间。这三个时间的总和就是磁盘的延迟。

上面我们讲从磁盘读取一个字节的时间是10ms,那么我们接下来看一下是怎么算出来的。

已某磁盘为例,在柱面之间移动磁头考虑到惯性作用,还有机械臂本身的延迟,大约花费1ms,这个时间其实是可以接受的。

每移动4000个柱面就会又花费1ms,这样磁盘移动一个柱面的时间就是1.00025ms。

从最内圈到最外圈,总共是65536个柱面,大约用时17.38ms。这里我们取平均情况,理论值大概是三分之一,可能有同学会问,理论值是怎么来的,在这里我们花一点时间来说明,假设,最内圈标记为0,最外圈标记为L,实际的起点是X,终点是Y,那么X是可以从0取到L的,Y也是从0取到L的,我们把X和Y可能的组合就是L*L,那么值就是:最后通过积分预算,大概是三分之一。

我们在买电脑时,有个磁盘指标就是磁盘转速,有5400转,7200转,这里的单位是都是 转/分钟,我们这里已7200转为例,那么旋转一圈的时间就是8.33ms。当然考虑到实际情况,如果旋转的一周的话,是最差情况的,也就是磁头刚好跨过起点,又得旋转一周才回起点。最好的情况是磁头刚好停到起点上,这里我们取平均情况,也就是旋转半圈的时间,4.17ms。

假设我们取4个扇区为一个块,传送一个块的时间大约是0.13ms。

我们取平均时间

我们取平均值,6.46+4.17+0.13 = 10.76

最后我们来看读取字节数是不是线性增加的,比如读一个字节的时间是10ms,那么读100个字节的时间是不是就是1000ms,根据我们上面讲的读取特性,如果这100个字节都在同一个块中,那么就和读取一个字节的时间是一样的。所以这个时间显然不是成倍增加。

2.6 I/O开销的主导地位

我们刚才讲到读一次就需要10ms,其实这个时间对于数据库来说是非常长的,所以我们执行磁盘读写所花费的时间比用于操作内存中数据所要花的时间要长的多,这样块的访问(I/O的次数),其实就是算法所需的近似值。如果我之后要讲查询优化,那么优化的标准是什么,其实就是这里的:对块的访问次数,次数越少,查询越优。

下面有两个例子,感兴趣可以看一下,这里不再展开详细说了。

例1:数据库有关系R,其中有对一元组的查询,该元组有确定的键值K。最好创建一个索引来指向带有键值K的元组存在哪个磁盘块,而索引是否告诉我们该元组在块的什么位置是不重要的。

例2:上页中我们讨论的读取一个块的平均时间大约是11ms。而11ms对于一个现代微处理器,可以执行几百万条指令。如果块在内存中,查找键值为K的元组不过千余条指令,在主存中搜索的时间不过是块访问时间的1%还要少,可以安全忽略之。

2.7组织磁盘上的数据-定长记录

磁盘上的数据分为两种,一种是定长记录,另一种是非定长记录。

下面我先说一下定长记录。

最简单的记录由定长字段组成,元组的每个属性对应一个字段。通常会根据情况要求字段的起始地址是4或8的倍数,以便机器更有效率的读写。

通常记录以记录的首部开始,首部是关于记录自身信息的一个定长区域。例如我们要保存如下信息:

1.一个指向该记录中存储数据的模式的指针。例如:一个元组可以指向该元组所属关系的模式,此信息可以帮助我们找到记录的字段。
2.记录字段。此信息帮助我们在不查看记录所属关系的模式情况下,略过某些记录。
3.时间戳。标识记录最后一次修改或被读的时间。有利于后面的事物
4.指向记录的字段的指针。此信息可以代替模式信息,在考虑变长字段时,这个信息格外重要。

我们看一个例子:

创建一张表,表结构如下。

CREATE TABLE MovieStar {

name CHAR(30) PRIMARY KEY,

address CHAR(255),

gender CHAR(1),

birthday DATE

};

按下图所示,前面分别是模式指针,记录的长度,还有时间戳,接下来开始存name,name是30个char,(不考虑字符集的问题,这里认为一个char就占用一个字符集的字节,如果用utf8的话,1个2个3个字节都有可能)这里考虑一个字节的情况下,30个字符就需要30个字节,读取的时候需要2的次幂的倍数来加载数据,比30大的最小整数倍就是32,所以这里是从12到44。以此类推,adress字段是char(255),,最小长度是256,所以从44到300。

2.8 组织磁盘上的数据-变长记录

我们再看一下变长记录是怎么做的,我们把上表中name的字段由char(30)改为varchar(30)。

CREATE TABLE MovieStar {

name VARCHAR(30) PRIMARY KEY,

address VARCHAR(255),

gender CHAR(1),

birthday DATE

};

如果一个记录中含有变长字段,那么最有效的解决方法就是把所有定长的放前面,变长的字段跟在定长字段的后面。

但是这样也存在一个问题,如果后面就只有一个变长字段,也就无所谓了,但是如果有多个变长字段怎么办呢。

因为第一个变长字段我们知道起始位置,但是不知道有多长,对于第二个变长字段,我们就需要一个指针,来指向第二个字段的起始位置。如果后面还有更多的变长字段,以此类推就可以了。

这里有出现了一个新问题,因为我们不知道第一个变长字段到底多长,那么第二个变长字段的指针应该加在什么位置呢?

接下来我们解决这个问题,也就将要揭示出MySQL的理论基础,我们把这个问题思考清楚了,再去看MySQL的设计,一下就会明白他为什么要这么做。

一边我们存定长字段,另外一边存变长字段,中间是空闲区,这样做有什么好处,中间及可以加指针,又可以填记录,这就相当于双向栈的形式。

2.9 组织磁盘上的数据-不能装入一个块中的记录

上面我们讲了变长记录,其实考虑到变长记录,这个长度是无法控制的,可能会很长,这里也只用了varchar类型字段,在mysql中varchar的最大限制是65535(后面我们会讲这个限制有什么含义),那么对于text等类型,这个长度会更长,这种情况就产生了新的问题,对于这么大的数据量,一个块根本无法装下,该怎么解决呢?

解决方案也很简单,分两个块去装。

如下图,记录2在块1中放不下了,那就再找一个块,把记录2的一部分放在块1,另一部分放在块2。当然需要加上指针和标记位,标记一下记录的完整的记录还是片段。如果是片段,就需要指针指向对应的片段,形成双向链表形式。我们在遇到问题感觉很麻烦,但其实解决思想很简单,在MySQL中也大量运用了这种解决思想,后面我们看具体的例子。

2.10 组织磁盘上的数据-记录的插入

接下来我们如果想要插入一条记录怎么办。

当一条记录插入到一张表中时,如果这张表中数据没有特定的存储顺序,我们就只需要随便找一个块,如果这个块刚好有一段空闲空间,把这条记录放进去就OK了。

但是MySQL中,存储数据是必须要按照主键的顺序来存放的,在这种情况就产生一个问题,如果我按顺序插入了1、2、3条记录,这个时候要插入记录4时,是需要插到2和3中间的,按照下图所示,我们就需要把记录3往后移,把记录4查到记录2和 3中间,这样就完成了按顺序存储的规定。

当然我们讲的把记录3往后移,是逻辑上的移动,并且,按照主键顺序存储也是逻辑上按顺序存储,而在物理空间上并不是按顺序来存储的,比如说,我们刚才讲的把记录3往后移,如果是真的在物理上的移动,那么就是无谓的增加IO开销。MySQL是实现时,将记录1 2 3 4之间是用双链表关联起来的,物理空间上记录还是放在记录3的后面,只是指针指向了记录2。

我想讲到这里大家应该会对主键自增有新的认识了吧,虽然在逻辑上记录都是连续的,但是物理上是分离的,我们实际在使用MySQL进行查询时,如果加载数据在物理空间上非连续,就会导致多次磁盘寻址。这个IO的开销还是特别大的,所以我们在设计表结构时,尽量让主键自增,且是整型数字,尽量避免非数字类型当主键,一方面自增的记录在插入时是在物理空间上连续的,节省磁盘寻址时间,另一方面,整型是可以在寄存器中直接完成,而字符串需要在内存中处理。

我们上面所讲的都是在一个块中存放记录,我们之前讲到读写都是以块为基本单位,在说物理空间时,又说扇区是最小的读写单位,那么这两个概念的区别是什么呢?

扇区是物理上的概念,块是软件层间的概念,一个块大概是若干个扇区。

如果一个块存不下这条记录该怎么办?

一种方法是临近块,也就是再申请一个相邻的块。这种方案MySQL也采用了,但并不会特别好的解决问题。

另一种方案是溢出块,块的首部有个字段来标记有一个溢出块,并用指针指向存溢出块的地址。溢出块的头部还可以指向下一个溢出块。

3.InnoDB数据的存储

接下来我们一看一下MySQL中最常用的存储引擎,InnoDB,看一下它在做存储时的思路

3.1逻辑架构

表空间→段 → 区 → 页(块) → 行记录

表空间可以看做InnoDB存储引擎的逻辑结构的最顶层。默认情况下,是共享表空间,一个数据库中所有的表都在同一个表空间中,也可以设置成多表存储空间,与此表有关的,索引、数据、缓存、日志等等一切,都存在此表空间中,这是逻辑上的存储空间。

段是建立在表空间下面的概念。分为数据段、索引段、回滚段。这里要说一下索引段,在InnoDB中,数据即索引,索引即数据。也就是说,在B+树中,每个叶节点就是数据块本身,而不是键值,所以在InnoDB中,数据块和索引块是没有区别的。但是有的存储引擎中,比如MyISAM,它的表空间是要创建三个文件,数据段和索引段是分开的文件。但是InnoDB中,数据和索引是放在同一个文件中的。

区是由连续的页组成的空间。任何情况下,一个区的大小是1M,在默认情况下,InnoDB一个块的大小的16KB,也就是说,一个区默认有64个块。我们看一下区的主要作用,在写数据的过程中,一开始申请的空间有限,但是随着插入数据越来越多,空间放不下了,这种情况下需要再向磁盘申请更多的块,InnoDB的做法是一次就申请一个或者若干个区,不是一个页一个页的去申请。这里我再思考一下,为什么要用更大的单位去申请空间,其实就是我们上面讲到的,在物理上的连续空间才能够加快读取数据。

页又称为块,常见的有数据页、系统页、事物页、数据页、未压缩的二进制大对象页、压缩的二进制大对象页。这里的二进制大对象页我们可以看成是text格式的字段,也就是我们上面讲到的溢出块,只是名字改变了,设计思想还是一样的。

3.2 表文件

聊完基础概念,我们再来看表文件。

当我们创建完一个表之后,DBMS会创建了哪些文件。

在InnoDB中,表中的数据和索引存放在同一个文件中,表的结构单独存放到另一个文件中。当然还有回滚段,也是一个单独的文件。

3.3 行记录

和大多数存储引擎一样,InnoDB使用页作为磁盘管理的最小单位,数据在InnoDB中是按行存储的,每个16KB的大小的页中,可以存放2-200行的记录。这里需要说明的是,一个页中最少要存放两行记录,原因是InnoDB的底层是B+树,叶节点之间是通过指针相连的,如果一个节点中只有一条记录,那么就退化成了链表,所以最少两条记录。

当InnoDB存储数据时,可以使用不同的行格式进行存储。

两种行记录格式 compact 和 Redundant在磁盘上按照以下方式存储:

1.在 Compact 中,行记录的第一部分倒序存放了一行数据中列的长度(Length),而 Redundant 中存的是每一列的偏移量(Offset)

2.Compact 中对于NULL的存储依靠NULL Indicator(向量),不占用额外的空间。Redundant的VARCHAR类型的NULL不占用空间,而CHAR类型的null占用空间

 

总体上,Compact 行记录格式相比 Redundant 格式能够减少 20% 的存储空间。

 

3.4 行溢出

页大小默认是16KB,也可以设置为4KB、8KB、16KB和32KB时,行记录最大长度应该略小于页大小的一半,页大小为64KB时,行记录最大长度略小于16KB。这里的原因同上面讲到的一个页中最少存放两条记录,防止退化成链表。如果在一个页中不够放两条记录,就会把部分数据溢出到另外的块中。

对于上面介绍的两种行格式,InnoDB都是把前768个字节放在数据页中,后面的数据通过偏移量存放到溢出页中。

目前还有一种新的行格式 Compressed (这是我们数据库目前在用的行格式)或者 Dynamic 时都只会在行记录中保存 20 个字节的指针,实际的数据都会存放在溢出页面中。这种处理方式有一个好处,因为整列数据都在溢出块中,那么就可以将整列数据做压缩,之后如果有机会我们也可以讨论一下数据中,数据的压缩是怎么做的。

下面我们来思考一个问题,如果我的表结构定义了很多列,并且都是varchar这种可变长度的,就算我们的每列都只放前768个字节,那么还是有可能所有列的前768个字节加起来还是大于这个页的长度。对于InnoDB来说,它的解决方案很暴力,就是不会让你产生这种情况,它会报错提示这一行过长,我们平时会经常遇到这一列过长的错误情况。

 

3.5 数据页结构

页是 InnoDB 存储引擎管理数据的最小磁盘单位,这里介绍页是如何组织和存储记录的

一个 InnoDB 页有以下七个部分:

每一个页中包含了两对 header/trailer:内部的 Page Header/Page Directory 关心的是页的状态信息,而 Fil Header/Fil Trailer 关心的是记录页的头信息。

在页的头部和尾部之间就是用户记录和空闲空间了,每一个数据页中都包含 Infimum 和 Supremum 这两个虚拟的记录(可以理解为占位符),Infimum 记录是比该页中任何主键值都要小的值,Supremum 是该页中的最大值。

User Records里就是实际存储的行记录内容,Free Space指空闲空间,它也是个链表数据结构,当一条记录被删除时,该记录的空间就会加入这个链表中。

为了保证插入和删除的效率,整个页面并不会按照主键顺序对所有记录进行排序,它会自动从左侧向右寻找空白节点进行插入,行记录在物理存储上并不是按照顺序的,它们之间的顺序是由记录头部next_record 这一指针控制的。

B+ 树在查找对应的记录时,并不会直接从树中找出对应的行记录,它只能获取记录所在的页,将整个页加载到内存中,再通过 Page Directory 中存储的稀疏索引和 n_owned、next_record 属性取出对应的记录,不过因为这一操作是在内存中进行的,所以通常会忽略这部分查找的耗时。

 

4最佳实战

1.在能够正确存储的前提下,尽量选择最小的数据类型,能用int就不用bigint,无符号代替有符号

2.简单就好,整型比字符串操作更低,使用mysql内建的类型,比如使用dateTime,不用字符串

3.尽量避免使用null,因为查询中使用null会更难优化,会使索引、索引统计更加复杂,当然也有例外情况,比如,业务中确定这些列可能为null,同时这些列也绝对不可能建立索引,比如地址。这种行记录是由位向量来标记的,设置为可为null,存储效率会更高。当然节约1列的意义不大,但是如果有很多列,节约出来的空间会特别大

 

5 后记

这次的介绍就这些,以后如果有机会,还会详细介绍索引结构,查询执行,故障对策,事物管理,并行与分布式数据库,MySQL的内存管理,数据库在线迁移以及我们平时在工作中遇到的典型案例。也希望 大家平时在工作注意收集踩过的坑,注意总结,提高自己的技术水平的同时,也能帮助其他同学一块提高。

 

 

 

你可能感兴趣的:(MySQL)