数据库
首页 > 数据库> > Mysql数据库存取原理及性能优化

Mysql数据库存取原理及性能优化

作者:互联网

一、Mysql的系统架构图

 二、Mysql存储引擎

  Mysql中的数据是通过一定的方式存储在文件或者内存中的,任何方式都有不同的存储、查找和更新机制,这意味着选择不同的方式对于数据的存取有效率的差距。 这种不同的存储方式在 MySQL中被称作存储引擎。

  存储引擎是Mysql数据库系统的底层组件,数据库管理系统通过这些组件来进行创建、查询、更新和删除数据,它操作的对象是表。存储引擎类型在肉眼上体现为表的类型,Mysql的存储引擎类型可以通过运行命令' SHOW ENGINES;  '获得,目前Mysql8支持的存储引擎有如下几种:

  查看当前默认的存储引擎:

SHOW VARIABLES LIKE 'default_storage_engine%';

  修改默认存储引擎(临时生效,重启后恢复InnoDB):

SET default_storage_engine=< 存储引擎名 >

  修改单个数据表的存储引擎:

ALTER TABLE <表名> ENGINE=<存储引擎名>;

  查看数据表的存储引擎:

 SHOW CREATE TABLE tablename \G

  修改表的默认存储引擎,要想永久生效,需要在配置文件my.cnf中  [mysqld] 后面加入:

default-storage-engine=存储引擎名称

  如何根据业务场景来选择合适自己的存储引擎呢?这需要先了解几种存储引擎的特性,以下是几种存储引擎的特性表,可做参考:

特性MyISAMInnoDBMEMORY
存储限制 支持
事务安全 不支持 支持 不支持
锁机制 表锁 行锁 表锁
B树索引 支持 支持 支持
哈希索引 不支持 不支持 支持
全文索引 支持 不支持 不支持
集群索引 不支持 支持 不支持
数据缓存   支持 支持
索引缓存 支持 支持 支持
数据可压缩 支持 不支持 不支持
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 不支持 支持 不支持

 三、InnoDB存储引擎存取原理

  InnoDB是最常用的存储引擎,我们的业务系统在使用Mysql作为数据库的时候,一般都选择InnoDB作为存储引擎,原因是该引擎支持行锁、索引、事务安装、主外键,而这些特性正是业务系统数据所需要的。

  数据在InnoDB中将被划分为若干个页,页的大小一般为16KB,以页作为存取数据的单位,前一节提到InnoDB的数据是存到磁盘上的,而程序代码需要的数据是在内存中的,因此存取数据的操作实际上是磁盘和内存之间数据交换的过程。

   以下是Mysql数据页的结构图,不同的部分代表着不同的功能,其中:

 

   UserRecord这部分空间是可变的,当向数据库中插入记录时,数据会被存到这个位置,UserRecord空间被增加,同时FreeSpace容量会变小,直到空闲空间FreeSpace被用完,如果还有新的数据插入,则引擎会申请新的数据页再进行存储。

 

 

  多条数据被存到UserRecord这个位置,那他们之间的物理位置有什么关系呢?这里牵涉到两种主要关系,一种是同一数据页之间的数据记录位置关系,另一种是不同数据页之间的记录的位置关系。要谈记录的位置关系,我们要了解每条记录的存储结构。

  每条记录都包含我们存储的真实字段数据和记录额外的信息数据,一行记录可以以不同的行格式存在,目前有Compact、Redundant、Dynamic、Compressed等几种格式,以下是Compact行格式的图解:

 

 其中,每个属性说明如下:

名称大小(bit)描述
预留位1 1 空闲
预留位2 1 空闲
delete_mask 1 标记记录是否被删除(0未删除,1已删除),当标记为删除时没有真实从物理磁盘中删除,只是代表这块地址可以被覆写
min_rec_mask 1 标记记录是否为B+树的非叶子节点中的最小记录(索引时用到)
n_owned 4 当前槽管理的记录数
heap_no 13 记录在堆中的相对位置
record_type 3 记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录
next_record 16 下一条记录的相对位置
NULL值列表   用于标记和统一管理值为NULL的列
变长字段长度列表   用于存储可变长度的字段的值占用的字节数

   记录是以单向链表的方式存储的,因此:

   思考一个问题:如果一个数据表的列字段很多或者存储的值太多,一个数据页存不完怎么办?在Compact行格式中,对于占用存储空间很大的列,在记录真实数据的地方只存储一部分数据,把剩余的数据分散存储在其它页中,通过在记录数据的尾部存储指向其它页数据的地址来关联这些分散的数据。

四、InnoDB索引原理

  我们知道,数据库使用索引的目的是为了加快查询速度,那么MySQL是如何实现索引的呢?

  对于数据行的插入,默认情况下是顺序存储(按主键排序)的,查询的时候将按照插入的顺序显示结果。当一个数据页存储字段较少且字段值内容较少的时候,很有可能这个数据页就能够同时存储上千行记录,前一节我们说过,数据是以单向链表的方式存储的,这种存储方式对于插入来说比较快,而对于查询来说就比较慢,如果我们需要使用where查询条件从上千行记录中获取满足条件的记录,如何才能加快查找速度呢?

  InnoDB引擎为我们提供了一个叫做PageDirectory的页目录,这个概念在前面介绍数据页结构的时候提到过,它用于为数据页的行记录提供目录索引,类似书籍的章节目录,能够帮助我们快速定位数据记录所在的分组,从而加快查询速度,其实现原理是:将页内所有非删除的记录划分为N个组,每个组里最后一条记录(主键最大的记录)的n_owned属性记录了组内的记录数量,将这条记录的偏移地址取出按序从File Trailer位置开始向前写入形成PageDirectory,其中偏移地址称为'槽',图示如下:

   根据上图所示.如果我们要获取id=4的记录,可以先通过二分法从目录页中快速找到这一记录所在的页,然后在快速定位数据记录所在的位置。

.  通过上面的解析,我们理解了从同一个数据页中快速查找记录的方式,再来想一想,如果数据行较多,分散存储到了多个数据页里,那又如何快速的确定数据在哪一个数据页的哪一个分组呢?

  InnoDB为我们提供了"数据页"概念的同时,也给我们提供了一个叫做"目录页"的概念,目录页用于存储数据页的页号和页对应的记录的最小主键(相当存了一组{key,value}键值对集合,key主键,value为页号)。因此在执行条件查询时,先通过主键确定记录所在的页,在根据页内的页目录定位(通过主键定位)到分组,从而快速获取结果。结构图示如下:

  

   分析结构图,发现结构是一棵B+树,B+树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。B+树的叶子节点用于存储真实数据,非叶子节点用于存储主键值和指针(页码)。

  前面我们提到数据在插入时是按照主键的顺序来进行排序存储的,所以这就是为什么我们在设计表时建议设置主键自增的原因(新增记录时不会对之前的数据进行重新排列,这会加快插入的速度)。其实一个数据行字段除了我们自己创建的字段之外,还存在三个隐藏的字段row_id(行ID)、transaction_id(事务ID)、roll-pointer(回滚指针),如果我们设计表时没有设置主键,也没有设置唯一索引,那么它会自动以隐藏的字段row_id来作为自增主键进行排序存储。

  思考一个问题:如果一个列的值占用空间较多,会发生什么?当一行记录存储的数据较多时,意味着要使用更多的数据页,更多的数据页意味着更多的目录页,这会增加" B+结构的高度 ",这也会隐形的降低查询效率。

  到这里我们已经知道,对于主键字段来说,它会通过页目录和目录页的方式来增加数据查询速度,但是在实际开发中,我们可能还需要通过其他字段的查询条件来筛选数据,那么InnoDB引擎又是如何通过什么方式来增加查询速度的呢?

  其实,我们也可以为非主键字段来创目录页,这些目录页同样组成了B+树结构,只不过它的叶子节点存储的是主键值,当通过非主键字段查询记录时,首先会通过非主键目录页B+树结构查找到主键,再去调用主键的目录页B+树,去查找真实数据。我们知道,除了可以为单独为非主键字段的某一个字段创建索引,还可以使用联合多个字段来创建一个索引,这两种方式的实现方式都是相同的。

  根据前面的分析和探究,我们需要知道如下几点:

五、表的扫描

  我们已经了解了索引的原理,知道了可以为表建立多种索引方式,来加快我们的查询速度,但是不同的索引类型的查询效率是有区别的,mysql有几种表的扫描机制:

在进行数据查询是应该尽量使用最小的扫描代价去获得结果,这是SQL性能优化的核心思想,需要注意的是并非避免全表扫描就能获得最佳效果,反之在具有多个索引的中,过多的索引组合反而可能导致效果不如全表扫描实在,这就是在创建索引时并非越多越好的原因。

  Mysql给我提供了一个关键字explain用于分析和测试sql语句的性能,在SQL语句前增加该关键字,运行后会返回SQL执行计划的信息,通过这些信息,可以帮助我们更好的选择索引和优化SQL语句,例如运行如下语句得到结果如图:

 其中,结果集的每个字段和可能的值说明如下:

id

查询语句的序号:相同ID:多条记录表示执行顺序由上而下;不同ID:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。

select_type

SIMPLE:简单的select查询,查询中不包含子查询或者UNION。

PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY。

SUBQUERY:在SELECT或WHERE列表中包含了子查询。

DERIUED:在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。

UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中外层SELECT将被标记为:DERIVED。

UNION RESULT: 从UNION表获取结果的SELECT。

table 表名:数据来自那张表。
partitions 分区表信息,没有分区表则为NULL。
type 访问类型指标,查询速度由快到慢:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL。
possible_keys 可能选择的索引,一个或多个。
key 实际使用的索引。如果为NULL,则没有使用索引。
key_len 选择的索引的长度,通常来说越小越好。
ref 和索引匹配的列。
rows 估算的扫描行数,越小越好。
filtered 被条件过滤行数的百分比。
extra

Using filesort:使了用临时表保存中间结果。

Using index:查询使用了索引。

Using where:使用了where过滤。

Using join buffer:使用了连接缓存。

impossible where:where子句的值总是false,不能用来获取任何数据。

六、数据库优化

  我们已经了解了索引的原理,明白了数据页的存储结构是一棵B+树,同时目录页存储结构也是一棵B+树,能够通过索引定位数据所在的父节点,减少表的扫面,从而快速找到记录。但是当存在的索引越多时,查找数据的实现方案就越多,那如何才能尽量让我们编写的SQL尽量使用最优的方案呢?

  文章头部的Mysql架构图中我们可以看到,我们编写的SQL语句在经过分析器进行语法分析后,传递给优化器,优化器帮助我们选择索引并生成执行计划,然后再交给执行器获取操作结果,但是优化器并非万能的,它只能为我们执行一些可控的优化。我们还需要从减少数据的碎片存储、不同类型数据的索引效率、表的扫面机制等方面考虑,特此总结出如下几个优化和设计规则:

七、SQL的慢查询分析

  SQL的执行总是有一个执行时间的,我们可以给数据库设置一个时间节点参数long_query_time,当SQL执行超过这个值时,便认为本次查询是慢查询,将被记录到慢查询日志中,有利于我们进行性能排查。默认的long_query_time默认值为10s。

开启慢查询日志:

[mysqld]
log-slow-queries=/data/mysqldata/slow-query.log  #慢查询日志的位置
long_query_time=10   #表示查询超过10s就作为慢查询
set global slow_query_log='ON';  #开启慢查询

set global slow_query_log_file='/usr/local/slow.log'; #指定日志文件路径

set global long_query_time=10;  #自定慢查询的时长  

八、常用的SQL算法设计

with RECURSIVE  tmp as
(
    select * from work_order_question where id=4
    union all
    select c.* from work_order_question as c,tmp t where c.parent_id=t.id
)
select id from tmp

 

标签:存储,记录,存取,数据库,查询,索引,Mysql,数据,主键
来源: https://www.cnblogs.com/zqhIndex/p/16520275.html