外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风 险; 外键影响数据库的插入速度。还有就是增加了复杂性、增加了额外工作、对分库分表不友好 。
好处是:
E-R 图 也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。
存储过程看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。适用于DB开发工程师。
MySQL 数据库区别于其他数据库的最重要的一个特点就是其插件式的表存储引擎。存储引擎是基于表的,而不是数据库。用户可以根据 MySQL 预定义的存储引擎接口编写自己的存储引擎。
MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。MySQL的架构分为连接层、服务层、引擎层、存储层。
MySQL主要分为 Server层 和 存储引擎层:
Server层:主要包括连接器、查询缓存(MySQL8.0移除)、分析器、优化器、执行器等,所有的跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图、函数等,还有一个通用的日志模块binglog
存储引擎:主要负责数据的存储和读取,采用可以替换的插件式架构,支持InnoDB、MyISAM、Memory等多个存储引擎,其中InnnoDB有属于自己的日志模块。现在最常用的存储引擎是InnoDB,它从MySQL5.5.5版本开始被当做默认的存储引擎了。
是一些客户端和连接服务,包含本地 socket 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的通信。主要完成一些类似于建立连接、授权认证、及相关的安全方案。
1)Poll、Select模型
mysql在启动后,创建了socket server,绑定了3306端口,并对其进行监听。和java里的写法类似,用一个while循环来监听新来的connection,如果有了新连接,就创建一个新的连接线程去处理(不能超过max-connections)。这种就是典型的BIO的模式,为每一个连接创建一个线程。并且mysql在这里采用了IO多路复用,会有一个if语句来判断当前系统是否支持Poll模式,否则就走Select模型(各个系统默认都有实现)。
Poll模型和Select非常类似,只是少了1024个fd的限制,都是采用遍历数组轮询有没有新IO事件的方式,在连接数较少的情况下,性能优异,要好于epoll。
2) Max-connections
mysql基于BIO模式,本质上是不接受大量的socket连接的,所以设置了max-connections这个限制,超过设置的max,那么新来的连接会被拒绝。DB采用session作为一个连接会话,这一个session里,SQL的执行必须是串行、同步、有序的,而不能是异步乱序的。一个session内可能有多个操作,增删改查、事务隔离,必须保证顺序不能乱。
3) connection复用
mysql能支撑的连接数是有限的,那么就需要应用程序来利用好连接池。对于连接池来说,就是做好这10个连接的管理就好。那么就有了连接复用的模式。
Mysql连接基本原则:IO密集的时候,要减少连接数。
包括查询解析、优化、缓存、以及所有的内置函数,所有跨存储引擎的功能也都在这一层实现,包括触发器、存储过程、视图等。
MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。如果没有命中查询缓存,就要开始真正执行语句了。
注意:
- 只要该表的结构或者数据被修改,那使用该表的所有高速缓存查询都将变为无效并从缓存中删除
- 虽然可以提升系统性能,但也不得不因维护这块缓存而造成一些开销。从MySQL 5.7.20开始,不推荐使用查询缓存,并在MySQL 8.0中删除。
MySQL查询缓存是MySQL中比较独特的一个缓存区域,用来缓存特定Query的整个结果集信息,且共享给所有客户端。为了提高完全相同的Query语句的响应速度,MySQL 会对查询语句进行hash计算后,把得到的hash值与Query查询的结果集对应存放在Query Cache中。
当开启Query Cache之后,MySQL 会对接收到的每一个SELECT语句通过特定的hash算法计算该Query的hash值,然后通过该hash值到Query Cache中去匹配。如果通过hash值匹配到了一样的Query,则直接将cache中相应的Query结果集返回给客户端。
由于Query Cache是针对SELECT语句的hash值作为key值进行存储的,意味着SQL语句哪怕出现一个字符的不同,缓存也无法进行命中。
Query Cache的劣势:
1、查询SQL的命中。Query Cache是基于字节级别的,只要有一点点误差就会导致无法命中。
2、缓存过期。
Query Cache
的淘汰策略过于苛刻,任何对于表中数据的修改,都会使得缓存失效。3、分区表禁用。如果数据表使用了分区,
Query Cache
将会被自动的禁用,无法生效4、增加额外的负载。当开启
Query Cache
选项后,如果查询请求没有命中Query Cache
时,MySQL会需要额外的性能开销去处理结果集,写入Query Cache
中,最糟糕的情况下,这个额外的性能开销是13%
**分析器先会做“词法分析”
,词法分析完后就要做“语法分析”
。**根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒。分析完了将要查询的表、各种查询条件都提取出来放到MySQL服务器内部使用的一些数据结构上来。
分析器收到一条SQL,其执行过程:词法解析,语法分析,构造执行树,生成执行计划,计划的执行。
一般来说,词法和语法解析都是通过 Flex 与 Bison 完成的;而在 MySQL 中,词法分析使用自己的程序,而语法分析使用的是 Bison;Bison 会根据 MySQL 定义的语法规则,进行语法解析。完成语法解析后,会将解析结果生成的数据结构保存在 struct LEX 中,该结构体在 sql/sql_lex.h 文件中定义。
解析过程:
1、语法解析。SQL 解析入口会调用 MYSQLparse ,而在 sql/sql_yacc.cc 中有如下的宏定义,也就说,在预编译阶段,会将 yyparse 替换为 MYSQLparse ,所以 实际调用的仍是 yyparse 函数。
2、词法解析。MYSQL 的词法分析并没有使用 LEX,而是有自己的一套词法分析,代码详见 sql/sql_lex.cc 中的实现,其入口函数是 MYSQLlex() 。
经过了分析器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。优化器会根据解析结果体 sql/sql_lex.h 文件, 找出执行该语句所有可能使用的执行方案,并找到其中成本最低的方案,这个成本最低的方案就是所谓的执行计划,之后才会真正的执行查询。
执行方案的成本计算:
**1)IO 成本:从磁盘中加载数据页需要的成本。**MySQL 中规定了成本常数,读取一个页的成本默认是1
2)CPU 成本:读取记录、将记录与条件进行比较、排序等操作都算作 CPU 成本,一条记录的读取或比较成本是0.2
成本计算:
1)全表扫描
**首先计算全表扫描的 IO 成本,全表扫描访问聚簇索引。**因此要计算 IO 成本就需要估算出聚簇索引占用的页面数。这个信息可以从表的统计信息中得到,里面有一个Data_length字段表示聚簇索引的存储空间大小,用Data_length除以 MySQL 默认 16KB 的页面大小,就能得到聚簇索引占用的页面数了。用这个数乘以成本常数1再加上微调(忽略就行),就是 MySQL 计算的 IO 成本。
2)索引+回表
如果通过索引的方式来执行查询,需要计算下面这些成本:
2-1)根据索引列的查询条件通过二级索引定位主键ID。通过这种方式大概估计出记录条数,得到记录条数之后,乘以CPU成本常数0.2就得到了CPU成本。IO 成本的计算比较简单,MySQL 认为一个范围区间的IO成本就是1。
2-2)对这些主键ID进行回表。回表可以看作是随机 IO。 根据上面估算的记录条数,MySQL 把每条记录的 IO 成本都看作是访问一个页面的成本,所以回表操作的IO成本就是记录条数乘以IO成本常数。
**2-3)回表得到的记录用其他查询条件进行过滤。**这一步只考虑 CPU 成本,直接用之前预估的记录条数乘以 CPU 成本常数。
3)其他(表连接的成本计算)
上面的内容是针对单次连接,因此连接查询的成本就是驱动表访问一次的成本加上被驱动表访问多次的成本。对于内连接,驱动表和被驱动表的位置是可以互换的,而不同的表作为驱动表的查询成本可能是不同的
4)执行器
进入了执行器阶段,开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误。如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
5)SQL接口
用于接收客户端发送的各种 SQL 命令,返回用户需要查询的结果,比如 DML、DDL、存储过程、视图、触发器这些。
**存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。**不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。MySQL常见的存储引擎有InnoDB、MyISAM。从MySQL5.5.5版本开始被当做默认的存储引擎了。
InnoDB是基于磁盘存储的引擎,其真正的数据最终都是保存在文件中的,并且以页为最小存储单位。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。
InnoDB
存储引擎主要由两个部分组成,分别是内存架构和磁盘架构,这两个部分都有自己不可或缺的功能。
内存架构(英文名称:In-Memory Structures
),在InnoDB
存储引擎中主要包括四个部分,分别是自适应哈希索引、Buffer pool
、Change buffer
和Log Buffer
四个部分。
磁盘架构也就是表空间。InnoDB
存储引擎的表空间主要分为:系统表空间、独立表空间、普通表空间、Undo表空间以及临时表空间。底层逻辑架构
InnoDB的内存结构和磁盘存储结构图总结如下:
InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。InnoDB存储引擎会监控对表上各索引页的查询。自适应哈希索引(AHI)是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。 InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。
1)索引被查询的次数足够多。如果查询次数用N1来表示的话那么N1应该大于17
(N1>17)
,此过程无时间限制。 2)基于查询的sql语句创建一个hash信息(hash info)其中包含了三部分内容:
2-1)匹配索引的列数
2-2)下一列匹配的字节数
2-3)是否从左匹配
如果根据此条件生成的hash info被使用的次数足够多
(N2 > 100)
3)生成的hash info能够命中某个数据页,且命中的该页上的记录数要大于该页上总记录数的1/16
(N3 > 页记录数的1/16)
满足以上三点要求那么就会将hash info生成一个key,value就是指向数据页上记录的指针。这样就建立起了mysql的自适应哈希索引
缓冲池是 MySQL 数据库中最重要的一个部分。**在数据库启动之时,首先会初始化这块内存区域,它占用了 MySQL 数据库总内存空间的80%
以上。它的主要作用是提高数据库查询的效率,其中主要使用了LRU
算法。在MySQL
数据库中,LRU
算法的底层主要是一个链表。**不过该链表被分为了两个区域,分别是新子链表和旧子链表,而且新子链表占用总空间的5/8
,旧子链表占用总空间的3/8
。
写缓冲主要的功能是记录数据库的数据修改操作的结果的。主要目的是提高数据库的写性能。
数据修改操作的步骤:
- 第一步:修改一条数据时,首先判断该条数据是否存在于
Buffer Pool
之中。
- 如果在,直接修改
Buffer Pool
中的相关数据。- 如果不在,首先在磁盘中读取该条数据到
Change Buffer
之中,而后在Change Buffer
中修改该数据,同时写入Redo Log
之中(为了防止数据丢失),等下一次查询该条数据时,合并至Buffer Pool
中。- 第二步:
Change Buffer
中数据修改之后,什么时候合并数据呢?
- 第一种方式:当修改的这条数据被查询的时候,合并到
Buffer Pool
。- 第二种方式:MySQL 数据库中的
Master Thread
合并(周期默认:10s)。- 第三种方式:当 MySQL 数据库关闭时,通过
Redo Log
合并到磁盘中。
如果在Change Buffer
修改完数据之后,仅仅保存在内存中,那么如果这个时候数据库宕机,也就意味着我们刚刚修改的数据也随即丢失,而这一点是不能被允许的。
MySQL 给我们提供了一种写日志的方案,也就是说修改完的数据会保存到一个叫Redo Log
的日志中。它是一个物理日志,当数据宕机时,它会将数据直接保存在磁盘之上;当数据库开启时,自动写入到数据库的磁盘中,以至于数据不会丢失。
但Redo Log
是一个物理日志,如果把大量的数据直接写进磁盘,还是会导致数据库性能低下,故采用Log Buffer来保存需要写入
Redo log`的数据,这样有利于提高数据库的性能。
MySQL 数据库在系统磁盘上保存的数据是有序的(典型就是按照主键 ID),如果每一次修改数据直接操作磁盘的话,会导致很多数据的位置发生更改(也就是我们常说的:随机 IO),但是
Redo log
中保存的数据是有序的(顺序IO),不会产生随机 IO,所以使用Redo log
暂时保存数据是确保数据不丢失时的最好办法。
系统表空间是InnoDB
存储引擎中最重要的表空间之一,它的主要作用是存储InnoDB
数据字典、双写缓冲、更改缓存以及撤销日志。
系统表空间一般存放于 MySQL 数据库目录中,名称为:ibdata1
。系统表空间一般不一定只有一个,也可能有多个,系统表空间的大小和数量由innodb_data_file_path
控制。InnoDB 数据字典在 MySQL 8.0 版本以后合并至 MySQL 数据字典中了,不再存储在系统表空间中了。
对于innodb
存储引擎来说,我们通常创建数据表的时候,会在 MySQL 数据目录中创建两个文件,分别是.ibd
和.frm
两个文件。.ibd
文件主要用来存储表数据,而.frm
文件主要用来存储索引。
这种做法可以将所有的数据表分开管理,也能够实现快速数据迁移,当数据出现故障之时也可以提高数据恢复的成功率。不过这样的做法又会增加磁盘的碎片,对系统处理表文件的性能有一定的影响。
普通表空间的本质其实就是一个共享的表空间。其具体文件在 MySQL 数据库的数据目录中是以.ibd
结尾的文件。跟系统表空间类似,它支持所有 MySQL 数据库中的数据表的结构,它是将数据库的一些元数据保存在内存之中,进而能够减少独立表空间对于内存的消耗。
Undo 表空间主要是用来保存撤销日志(即:Undo Log
)的空间。它默认情况下存储在 MySQL 数据库的根目录。在MySQL 8.0
版本之后,undo 表空间会在 MySQL 数据库的数据根目录生成 undo_001
和 undo002
共两个文件。
临时表空间主要是用来保存数据库会话中的临时数据的。在 MySQL 数据库的数据根目录中保存以ibtmp1
命名的文件。最主要的是我们在使用 join 连表查询的时候,会在临时表空间内创建临时数据表用来辅助查询。
在内存架构中,自适应哈希索引有利于提高查询速度;Buffer pool
主要提供了一个内存池,将经常查询的数据存放于内存中,这样做有利于提高数据库的查询性能和降低系统的磁盘 IO;Change buffer
主要是将修改好的数据存放于内存之中,下一次查询的时候合并到Buffer pool
之中,这样做的好处是可以降低修改数据时的磁盘 IO,进而提高数据库的性能;Log Buffer
是将所有修改的数据存放在其中,之后写入到Redo Log
之中,防止数据丢失。
大概来说,MySQL的数据变更:Buffer Pool-》Change Buffer-》Log Buffer-》Redo log-》磁盘
在磁盘架构中,系统表空间是用来修改和撤销日志的地方,之前的数据库版本中还存放InnoDB
数据字典以及双写缓冲;独立表空间主要是用来存储表数据和索引的地方;普通表空间是一个共享的表空间,能够减少独立表空间对于内存的消耗;Undo 表空间主要作用于事务回滚的,在使用未提交之前,用来保存原来的数据,一旦事务回滚则用 Undo 表空间中的内容替换修改过后的数据,进而达到回滚的目的;临时表空间主要是一个过渡的表空间,通常的一些操作需要有这种过渡来辅助操作,例如连表查询。
从内存架构到磁盘架构,InnoDB
存储引擎为我们提供了一个高性能、高安全的数据库存储引擎。通常在实际应用过程中,InnoDB
存储引擎是我们的首选存储引擎,但是在使用过程中一定要把Buffer pool
的空间设置得足够大,这样有利于提高数据的查询性能。
CPU的读写速度和磁盘的速度是不能比拟的,所以直接从磁盘取数据无疑是降低了数据库的性能,所以InnoDB提供了一块内存来弥补磁盘读写速度慢的缺陷,这块内存就是缓冲池。底层结构
工作方式
**数据库读取页时,首先去缓冲池中查找该页是否存在,若不存在再去磁盘查找是否存在,若存在则将页放在缓冲池中,**以便下次查找时,可以直接取出来。
数据库修改页时,首先修改缓冲池中的页,然后再以一定的频率刷新到磁盘中。并不是每次修改都去修改磁盘,而是通过一种称为Checkpoint的机制将数据刷新回磁盘。已在缓冲池修改而还未更新到磁盘的页称之为脏页。
由此可见,缓冲池的大小直接影响着数据库的整体性能,缓冲池可以使用参数innodb_buffer_pool_size来配置,在32位系统中,最多只能设置为3GB,而在64位系统中则不限。
从InnoDB 1.0版本开始,允许有多个缓冲池实例。每个页根据hashCode分配到不同的缓冲池实例中,这样做的好处是减少数据库内部的资源竞争,增加并发能力,可以通过参数innodb_buffer_pool_instances进行配置,其默认值是1。
重做日志(redo log)是数据库的重要组成,他保证了数据库宕机并重启后数据恢复的正确性。重做日志共有两个文件ib_logfile0和ib_logfile1。每个文件的大小一直且循环写入,也就是说先写ib_logfile0,写满之后再写ib_logfile1,又写满后,继续写ib_logfile0。
redo log日志里面记录的主要是:表空间+数据页号+偏移量+修改几个字节的值+具体的值。根据你修改了数据页里的几个字节的值,redo log就划分为了不同的类型,MLOG_1BYTE类型的日志指的就是修改了1个字节的值,MLOG_2BYTE类型的日志指的就是修改了2个字节的值,以此类推,还有修改了4个字节的值的日志类型,修改了8个字节的日志类型。如果修改了一大串的值,类型就是:MLOG_WRITE_STRING,就是代表你一下子在哪个数据页的某个偏移量的位置插入或者修改了一大串的值。Redo log详细内容可通过此进行了解
所以redo log看起来的结构如下:
日志类型(类似MLOG_1BYTE之类的数据),表空间ID,数据页号,数据页中的偏移量,修改数据长度(MLOG_WRITE_STRING类型的才有),具体修改的数据
redo log:为了持久化数据,当内存中的数据还没写入到磁盘而宕机时,会读取该日志持久化数据到磁盘
undo log:为了保证原子性,事务的操作都会记录一条相反的sql到该日志,出现错误就会根据该文件恢复事务之前的数据
binlog:为了复制和恢复数据,mysql重启可以通过该日志恢复
当数据库修改页时,会将修改信息放入重做日志缓冲区,然后按一定频率(默认每秒一次)写入到重做日志文件,重做日志缓冲区一般不需要设置很大,只要保证每秒产生的事务量在这个缓冲区大小内即可,缓冲区大小可以通过参数innodb_log_buffer_size配置,默认为8MB。
一般情况下,对于通常的事务提交,分为三个阶段:
**主要过程是,在事务提交时候, 会发生强制的将 redo log buffer的日志缓存的数据,强制写入 redo log file中, 通常会调用一次操作系统的fsync()的操作。**其中还会经过操作系统的内核空间, OS buffer ,因为MySQL的进行和日志缓存都工作在操作系统中环境下
本质:
事务提交的过程中,必须将日志缓存的数据持久化到磁盘的日志文件中,期间还需要经过操作系统的 “内核空间缓存区”–,也就是OS Buffer区域,Redo log从用户空间的 Log buffer 写入磁盘的Redo Log文件时,需要要内核空间的OS buffer;日志文件,没有使用 O_DIRECT标识,如果有这个标识,就可以不经过这个os buffer的内核空间,直接写入磁盘数据,;
Mysql对于数据的修改总是先修改内存中的数据,按照功能划分为,数据buffer,索引buffer,锁buffer,relog等。
1)主线程Master Thread每秒会写入一次
2)每个事务提交时会写入一次
3)当重做日志缓冲池剩余空间小于50%时,会写入一次
在InnoDB存储引擎中,对内存的管理是通过一种称之为内存堆的方式进行的,在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中申请。
主线程负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、undo页的回收等等
Master Thread具有最高的线程优先级别,其内部由多个循环组成:主循环Loop、后台循环Background Loop、刷新循环Flush Loop、暂停循环Suspend Loop。Master Thread会根据数据库的状态在主循环、后台循环、刷新循环、暂停循环中切换。
主循环Loop有两大操作——每秒和每10秒,并且是通过线程的sleep实现循环的,可见当负载很大时,会出现延迟的情况,所以并不是很精确的每秒和每10秒操作
每秒的操作如下:
- 日志缓冲写入到磁盘:即使事务还未提交,都会每秒将重做日志缓冲区的内容写入到重做日志文件
- 合并插入缓冲:先判断前一秒发生的IO次数是否小于5,如果小于5则认为IO压力很小,可以进行合并插入缓冲的操作
- 至多写入100个缓冲池中的脏页到磁盘:InnoDb会判断当前缓冲池中脏页的比例(buf_get_modified_ratio_pct)是否超过了参数innodb_max_dirty_pages_pct(默认为90,代表90%),如果超过则认为需要做磁盘同步操作,就将100个脏页写入磁盘
- 如果当前没有用户活动,则切换到background loop
注意:
插入缓冲是指插入数据前,非聚集索引会先缓存到缓冲池的插入缓冲区,然后按一定频率刷新到数据库,并非每插入一条,就更新一次索引树,这样效率同样很低。
每10秒的操作如下:
- 写入100个脏页到磁盘:InnoDB会先判断前10秒内的IO操作次数是否小于200,是则认为磁盘IO压力较小,可以将100个脏页写到磁盘。
- 合并至多5个插入缓冲
- 日志缓冲写入到磁盘
- 删除无用的undo页
- 写入100个或10个脏页到磁盘:如果缓冲池中的脏页比例(buf_get_modified_ratio_pct)超过70%,会刷新100个脏页,否则刷新10个脏页
在InnoDB中大量使用了异步IO(Async IO)来处理读写请求,而IO Thread就是负责处理这些请求的回调。在InnoDB 1.0版本 之前共有4个读写线程:write、read、insert buffer和log IO。在InnoDB 1.0之后write和read分别增大到4个,可以使用innodb_read_io_threads和innodb_write_io_threads参数进行设置
当事务成功提交后,事务所关联的undo log已经不再需要,故需要使用回收线程去回收所分配的undo页。回收线程的数量默认为1个,可以通过参数innodb_purge_threads进行配置
这是从主线程中分离出来,专门用于刷新脏页到磁盘的线程,其目的是减轻Master Thread的工作及对于用户查询线程的阻塞,进一步提高性能。
数据在InnoDB采取的方式是**将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。**当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出。
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为行格式。InnoDB存储引擎设计了4种不同类型的行格式,分别是Compact、Redundant、Dynamic和Compressed行格式。COMPACT行格式示意图如下:
MySQL5.7的默认行格式就是Dynamic,Dynamic和Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有所不同。Compressed行格式和Dynamic不同的一点是,Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。
页是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。InnoDB为了不同的目的而设计了许多种不同类型的页,这种存放记录的页为索引(INDEX)页,不过要理解成数据页也没问题,毕竟存在着聚簇索引这种索引和数据混合的东西。
一个InnoDB数据页的存储空间大致被划分成了7个部分:
- File Header 文件头部 38字节 页的一些通用信息
- Page Header 页面头部 56字节 数据页专有的一些信息
- Infimum + Supremum 最小记录和最大记录 26字节 两个虚拟的行记录
- User Records 用户记录 大小不确定 实际存储的行记录内容
- Free Space 空闲空间 大小不确定 页中尚未使用的空间
- Page Directory 页面目录 大小不确定 页中的某些记录的相对位置
- File Trailer 文件尾部 8字节 校验页是否完整
InnoDB是以页为单位管理存储空间的,聚簇索引(也就是完整的表数据)和其他的二级索引都是以B+树的形式保存到表空间的,而B+树的节点就是数据页。
任何类型的页都有File Header这个部分,File Header中专门的地方(FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID)保存页属于哪个表空间,同时表空间中的每一个页都对应着一个页号(FIL_PAGE_OFFSET),这个页号由4个字节组成,也就是32个比特位,所以一个表空间最多可以拥有2³²个页,如果按照页的默认大小16KB来算,一个表空间最多支持64TB的数据。
在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page)。默认情况下,表空间中的页大小都为 16KB,当然也可以通过改变 innodb_page_size
选项对默认大小进行修改。一个区的大小最小为 1MB,页的数量最少为 64 个。
数据存储层,主要是将数据存储在运行于该设备的文件系统之上,并完成与存储引擎的交互。InnoDB存储表时会将表的定义和数据索引等信息分开存储,其中表的定义存储在 .frm
文件中,数据索引存储在 .ibd
文件中。
以后补充吧
问题:
重做日志中记录的是对页的物理操作,而不是页面的全量记录,而如果发生partial page write(部分页写入)问题时,出现问题的是未修改过的数据,此时重做日志(Redo Log)无能为力。
Doublewrite Buffer(双写缓冲)的出现就是为了解决上面的这种情况,虽然名字带了Buffer,但实际上双写缓冲是内存+磁盘的结构。
Doublewrite Buffer是一种特殊文件flush技术,带给InnoDB存储引擎的是数据页的可靠性。它的作用是,在把页写到数据文件之前,InnoDB先把它们写到一个叫doublewrite buffer(双写缓冲区)的共享表空间内,在写doublewrite buffer完成后,InnoDB才会把页写到数据文件的适当的位置。如果在写页的过程中发生意外崩溃,InnoDB在稍后的恢复过程中在doublewrite buffer中找到完好的page副本用于恢复。
所以在正常的情况下,MySQL写数据页时,会写两遍到磁盘上,第一遍是写到doublewrite buffer,第二遍是写到真正的数据文件中,这就是“Doublewrite”的由来。
Doublewrite Buffer内存结构由128个页(Page)构成,大小是2MB。Doublewrite Buffer磁盘结构在系统表空间上是128个页(2个区,extend1和extend2),大小是2MB。
双写缓冲区是如何恢复数据的?
双写缓冲区存在的目的就是 Innodb 为了保证 MySQL 数据的原子性。在数据库异常关闭的情况下启动时,都会做数据库恢复(redo)操作,恢复的过程中,数据库都会检查页面是不是合法(校验等等),如果发现一个页面校验结果不一致,则此时会用到双写这个功能,并对有问题的页进行数据恢复。
当有页数据要刷盘时:
- 页数据先通过memcpy函数拷贝至内存中的Doublewrite Buffer中;
- Doublewrite Buffer的内存里的数据页,会fsync刷到Doublewrite Buffer的磁盘上,分两次写入磁盘共享表空间中(连续存储,顺序写,性能很高),每次写1MB;
- Doublewrite Buffer的内存里的数据页,再刷到数据磁盘存储.ibd文件上(离散写);
InnoDB Doublewrite Buffer是InnoDB的一个重要特性,用于保证MySQL数据的可靠性和一致性。它的实现原理是通过将要写入磁盘的数据先写入到Doublewrite Buffer中的内存缓存区域,然后再写入到磁盘的两个不同位置,来避免由于磁盘损坏等因素导致数据丢失或不一致的问题。Doublewrite Buffer对于保证MySQL数据的安全性和一致性具有重要意义。
用于缓存数据库查询出来的数据,便于下次快速查询。
**Buffer Pool 缓冲池。**InnoDB 为了缓存磁盘中的页,在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,叫做 Buffer Pool。默认情况下 Buffer Pool 只有 128M 大小。启动服务器的时候可以通过配置 innodb_buffer_pool_size
参数的值来控制 Buffer Pool 大小。缓存页大小和在磁盘上默认的页大小是一样的,都是 16KB。
InnoDB 为每一个缓存页都创建了一些所谓的控制信息,包括该页所属的表空间编号、页号、 缓存页在 Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息,当然还有一些别的控制信息,我们称之为控制块。
**缓冲页的链表结构有Free 链表、Flush 链表、LRU 链表。**Free 链表主要是空闲的缓冲页;Flush 链表为脏链;LRU 链表是淘汰链(用来淘汰不常使用的数据),假如链表满了,需要淘汰,就从链表尾淘汰。
InnoDB内部监控索引表,如果被认为是热数据,会自动创建自适应索引。创建以后,如果下次又查询到这个索引, 那么直接通过 hash 算法推导出记录的地址。用于提高 MySQL 的查询效率。
自适应哈希索引。MySQL 在哈希索引的设计上还采用了热点分散技术,这样的哈希索引在 MySQL 上默认是启动 8 个的,热点数据会分散到不同的哈希索引上,因此热数据访问时,能将请求分散到不同的哈希索引上,提高了并发访问的性能。
每个MyISAM表存储在磁盘上的三个文件中 。这些文件的名称以表名开头,并有一个扩展名来指示文件类型 。.frm
文件存储表的格式。 .MYD
(MYData
) 文件存储表的数据。 .MYI
(MYIndex
) 文件存储索引。**
**MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但 MyISAM 不支持事务和行级锁,而且有一个毫无疑问的缺陷就是崩溃后无法安全恢复。**但是对于只读的数据,或者表比较小、可以忍受修复(repair)操作,则依然可以继续使用 MyISAM。
1、将 LRU 链表分为两部分,前面为热数据去(Young 区),后面为冷数据区(Old 区),Old 区大小占 37%。优点:冷热链的切分,排除了全表扫描等类似的大数据量查询,直接把热门数据淘汰出缓冲区的情况。
2、对冷链数据移动到热链上做了时间限定。限定时间内对冷链上数据的访问不会移动到热数据区,只有超过这个时间,再次访问冷链上的数据,才会被移动到热数据区。
1、每个MyISAM表最大索引数是64,这可以通过重新编译来改变。每个索引最大的列数是16。
2、可以将数据文件和索引文件放在不同物理设备上的不同目录中,以更快地使用数据目录和索引目录表选项来创建表。
3、BLOB和TEXT列可以被索引。
4、NULL被允许在索引的列中
5、等等
MyISAM 和 INNODB 的区别(重点):
查看存储引擎
-- 查看支持的存储引擎
SHOW ENGINES
- Support列表示该存储引擎是否可用
- DEFAULT值代表是当前服务器程序的默认存储引擎
- Transactions列代表该存储引擎是否支持事务处理
- Comment列是对存储引擎的一个描述
- XA列代表着该存储引擎是否支持分布式事务
- Savepoints代表着该存储引擎是否支持部分事务回滚
-- 查看默认存储引擎
SHOW VARIABLES LIKE 'storage_engine'
--查看具体某一个表所使用的存储引擎,这个默认存储引擎被修改了!
show create table tablename
--准确查看某个数据库中的某一表所使用的存储引擎
show table status like 'tablename'
show table status from database where name="tablename"
设置存储引擎
-- 建表时指定存储引擎。默认的就是INNODB,不需要设置
CREATE TABLE t1 (i INT) ENGINE = INNODB;
CREATE TABLE t2 (i INT) ENGINE = CSV;
CREATE TABLE t3 (i INT) ENGINE = MEMORY;
-- 修改存储引擎
ALTER TABLE t ENGINE = InnoDB;
-- 修改默认存储引擎,也可以在配置文件my.cnf中修改默认引擎
SET default_storage_engine=NDBCLUSTER;
重要的字符集
ASCII
- 128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符
ISO 8859-1
- 256个字符,是在ASCII字符集的基础上又扩充了128个西欧常用字符
GB2312
汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母
兼容ASCII字符集
- 如果该字符在ASCII字符集中,则采用1字节编码
GBK
- 范围上对GB2312字符集作了扩充,编码方式上兼容GB2312
utf8
- 收录地球上能想到的所有字符,而且还在不断扩充。这种字符集兼容ASCII字符集,采用变长编码方式,编码一个字符需要使用1~4个字节
MySQL支持的字符集和排序规则
utf8和utf8mb4
utf8字符集表示一个字符需要使用1~4个字节,常用的一些字符使用1~3个字节就可以表示了
MySQL中字符集表示一个字符所用最大字节长度在某些方面会影响系统的存储和性能
- utf8mb3:阉割过的utf8字符集,只使用1~3个字节表示字符
- utf8mb4:正宗的utf8字符集,使用1~4个字节表示字符。
在MySQL中utf8是utf8mb3的别名,在MySQL中提到utf8就意味着使用1~3个字节来表示一个字符
有使用4字节编码一个字符的情况,比如存储一些emoji表情啥的,应该使用utf8mb4。
字符集的查看
- SHOW CHARSET;
比较规则的查看
- SHOW COLLATION LIKE ‘utf8_%’
4个级别的字符集和比较规则
服务器级别
- SHOW VARIABLES LIKE ‘character_set_server’;
- SHOW VARIABLES LIKE ‘collation_server’;
数据库级别
- SHOW VARIABLES LIKE ‘character_set_database’;
- HOW VARIABLES LIKE ‘collation_database’;
- 在创建和修改数据库的时候可以指定该数据库的字符集和比较规则
InnoDB的整体架构类似Java程序架构,但是多了一层数据存储,存储是在磁盘上面。而对磁盘的读写是非常耗费IO,也就有了内存结构的划分。为了读写方面的问题,有了缓冲池;为了奔溃恢复,有了重做日志缓冲;但是由于考虑避免在内存分配过程中,空间不足,但是也不能像Java一样OOM,有了额外内存池用于分配资源;而对数据的处理是基于内存,但是最终要落库,便有的后台线程的存在,负则刷盘到磁盘中;读写;垃圾回收;脏页清理等。