文章目录
MySQL体系结构
以下都围绕着这张图来说
- 整体概览: 作为一个合格的Java开发,我们都知道MVC三层架构,那么以MVC来类比Mysql的体系结构(只看Mysql服务器的这边架构,连接层没啥好看的)
SQL层就好比是MVC中的控制层,即接受用户的请求然后进行处理
存储引擎层是一个中间层,而最底层即文件系统层,就相当于三层架构中的Dao层,即这里才是真实的数据存储。
然后我们来详细说说 - 连接者(Client Connectors)
不同语言的代码程序和mysql的交互 - 连接池(Connection Pool)
用于管理、缓冲用户的连接,线程处理等需要缓存的需求 - 管理服务和工具组件(Services & utilities)
mysql提供的系统管理和控制工具,例如备份恢复、Mysql复制、集群等 - SQL接口(SQL Interface)
接收用户的SQL命令,并返回用户需要的查询结果 - 查询解析器(Parser)
SQL命令传递到解析器式会被解析器验证和解析(如权限、语法结构等),简单点说呢,就是把像select这样的关键字解析成方法进行调用 - 查询优化器(Optimizer)
在查询前使用查询优化器对查询进行优化,如排序啊,是否使用索引啊之类的 - 缓存(Caches)
例如我们会发现,同样的sql,查询第二遍比查询第一遍要快,这就是因为有缓存的缘故,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据 - 插入式存储引擎
存储引擎是Mysql的核心部分,存储引擎说白了就是如何管理操作数据(存储数据、如何更新,如何查询等)的一种方法。
因为在关系型数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称之为表类型(即存储和操作此表的类型)
在Oracle和SQL Server等数据库中,所有数据存储管理的机制都是一样的。而Mysql数据库就比较强了,它提供了多种存储引擎,用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据自己的需要编写自己的存储引擎。甚至一个库中不同的表可以使用不同的存储引擎。
术语浅解
Mysql中有很多术语,要先浅浅了解一下,便于我们理解下面的东西
一些缩写
- DB:数据库
- DBMS: 数据库管理系统,DB通过DBS来创建和操作,例如MySQL就是一种开源的DBMS
- DBA:数据库管理员
- SQL【Strcuture Query Language】:结构化查询语言,是目前广泛使用的关系型数据库 标准语言
- DDL【Data Definition Language】:数据定义语言,定义数据库涉及的各种对象,定义数据的完整性约束,保密限制等约束
- DML【Data Manipulation Language】:数据操作语言,实现对数据的操作,数据的基本操作有两类,检索(查询)和更新(增删改)
- DCL【Data Control Language】:数据控制语言,实现对数据库的控制,包含数据完整性控制、数据安全性控制和数据库恢复等
- TPL【Transaction Processing Language】:事务处理语言,它的语句能确保被DML语句影响的表的所有行及时得以更新。TPL语句同时包括BEGIN TRANSACTION、COMMIT和ROLLBACK
- DQL【Data Query Language】:数据查询语言,对数据进行操作
Mysql系统自带的数据库
Mysql自带了四个数据库(information_schema、mysql、performance_schema、sys)
-
information_schema
虚拟库,不占用磁盘空间,存储元数据
换句话说,它保存着关于MySQL服务器所维护的所有其他数据库的信息,它其中所维护的表指的是视图,而不是基本表
以下是该库里的表
解释几张比较重要的表:- TABLES:提供了关于数据库中的表的信息(包括视图),详细描述了某个表属于那个schema(在mysql中schema的意义等同于数据库)、表类型、表引擎、创建时间等信息,执行
show tables from schemaname
(schemaname替换成需要查询的数据库名称)的结果来自于这张表 - COLUMNS:提供了表的列信息,详细表述了某张表的所有列以及每个列的信息,执行
show columns from schemaname.tablename
的结果取自这张表 - STATISTICS:提供了关于表索引的信息,
show index from schemaname.tablename
的结果取自此表 - USER_PRIVILEGES:提供了全部用户的权限信息,这些信息源自mysql.user授权表
- SCHEMA_PRIVILEGES:提供了数据库权限的信息,该信息来自于mysql.db授权表
- TABLE_PRIVILEGES:提供关于表权限的信息,该信息来源于mysql.tables授权表
- COLUMN_PRIVILEGES:提供了关于列权限的信息,该信息源自mysql.columns_priv授权表
- CHARACTER_SETS:提供了mysql实例可用字符集的信息,
SHOW CHARACTER SET
- COLLATIONS:提供了关于各字符集的对照信息
- COLLATION_CHARACTER_SET_APPLICABILITY:指明了可用于校对的字符集,这些列等效于
show COLLATION
显示的前两个显示字段 - TABLE_CONSTRAINTS:描述了存在约束的表,以及表的约束类型
- KEY_COLUMN_USAGE:描述了具有约束的列
- ROUTINES:提供了关于存储子程序(存储程序和函数)的信息,此时,ROUTINES表中不存储自定义函数
- VIEWS:提供了关于数据库中的视图的信息,需要有show views权限,否则无法查看视图信息
- TRIGGERS:提供了关于触发程序的信息,必须有super权限才能查看该表
- TABLES:提供了关于数据库中的表的信息(包括视图),详细描述了某个表属于那个schema(在mysql中schema的意义等同于数据库)、表类型、表引擎、创建时间等信息,执行
-
mysql
mysql的核心数据库,主要负责存储数据库用户、权限设置、关键字等mysql自己需要使用的控制和管理信息 (常用的,我们可以在mysql.user表中修改root用户的密码) -
performance_schema
主要用于收集数据库服务器性能参数。并且库里表的存储引擎均为PERFORMANCE_SCHEMA,而用户是不能创建存储引擎为 PERFORMANCE_SCHEMA 的表。 -
sys
sys库中所有的数据源均来自performance_schema,目的是把performance_schema的复杂度降低,让DBA能更好的阅读这个库里的内容,从而快速了解数据库的运行情况
元数据
- MetaData,元数据,网上大多解释为数据的数据(不知道你们能不能理解什么叫做数据的数据,反正我理解不了)
- 可以看下这篇文章对元数据的举例
- 简单的来说,元数据是数据的描述和上下文,有助于组织和理解数据
- 举个例子,一张照片,本身就是一份数据,而这个照片什么时候拍的,什么地方拍的,拍照时的相机参数等等这些,就是这张照片的元数据
- 那么在关系型数据库中,一张表本身所存储的数据我们叫它数据,而这张表本身的结构,如表的名称、表包含哪些列、列所对应的数据类型,表所支持的约束、表与表之间的关系等等这些就称之为元数据
- 获取元数据的方式一般有三种思路:
1、各种show(如 show databases;)
2、各种select (使用select语句从information_schema表中查询)
3、mysql命令行(如列出所有数据库可使用mysqlshow )
具体的用到的时候去查
MySQL数据库文件
mysql的每个数据库都对应存放在一个与数据库同名的文件夹中,MySQL数据库文件包括MySQL所建数据库文件和MySQL所用存储引擎创建的数据库文件
- MySQL如果使用MyISAM存储引擎,数据库文件类型就包括.frm、.MYD、.MYIMySQL
- MySQL如果使用InnoDB存储引擎,数据库类型文件就包括.frm、.ibdata1、.ibd
- .frm文件:存储数据表的框架结构,文件名与表名相同,每个表对应一个同名的frm文件,与操作系统和存储引擎无关,即不管MySQL运行在何种操作系统上,使用何种存储引擎,都有这个文件
其余文件我们根据存储引擎的不同分开来说:
- MyISAM下
- .MYD文件:即My Data,表数据文件
- .MYI文件:即My Index,索引文件
- InnoDB
- InnoDB采用表空间管理数据,存储表数据和索引
- InnoDB数据库文件一般存储于mysql的数据目录下,如ibdata1、ibdata2等系统表空间文件,存储InnoDB系统信息和用户数据库表数据和索引,所有表共用
- .ibd文件:单表表空间文件,每个表使用一个表空间文件,存放用户数据库表数据和索引
存储引擎
mysql中有一个存储引擎的概念,针对不同的需求可以选择最优的存储引擎
通过命令查看一下引擎信息
Mysql存储引擎的特性概览
特点 | MyISAM | InnoDB | MEMORY | MERGE | NDB |
---|---|---|---|---|---|
存储限制 | 有 | 64TB | 有 | 没有 | 有 |
事务安全 | 支持 | ||||
锁机制 | 表级锁 | 行级锁 | 表级锁 | 表级锁 | 行级锁 |
B树索引 | 支持 | 支持 | 支持 | 支持 | 支持 |
哈希索引 | 支持 | 支持 | |||
全文索引 | 支持 | ||||
集群索引 | 支持 | ||||
数据缓存 | 支持 | 支持 | 支持 | ||
索引缓存 | 支持 | 支持 | 支持 | 支持 | 支持 |
数据可压缩 | 支持 | ||||
空间使用 | 低 | 高 | N/A | 低 | 低 |
内存使用 | 低 | 高 | 中等 | 低 | 高 |
批量插入的速度 | 高 | 低 | 高 | 高 | 高 |
支持外键 | 支持 |
重点理解MyISAM、InnoDB,其余的只要了解一下即可
MyISAM
- 不支持事务
- 查询性能较好:因为InnoDB不支持全文索引,而MyISAM支持全文索引,所以查询效率较高
- MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。之间索引和辅助索引是独立的
以下图做理解(图源网络)
InnoDB
- 支持事务:默认事务自动提交,如果需要手动commit,则需要使用
start transaction
开启事务 - 支持外键:从上述的概览图也可以看到,InnoDB是MySQL数据库中唯一支持外键约束的数据库存储引擎
- InnoDB文件存储:MySQL8开始删除了原来的frm文件【frm文件是"表定义",用于描述数据表结构的文件,Mysql中建立任何一张数据表,在其数据目录对应的数据库目录下都有对应表的.frm文件,.frm文件是用来保存每个数据表的元数据信息,包括表结构的定义】,并采用Serialized Dictionary Information(简称SDI),是MySQL8.0重新设计数据词典后引入的新产物,并开始已经统一使用InnoDB存储引擎来存储表的元数据信息【SDI文件存储了表结构的元数据的备份,MySQL8.0通过在元数据发生变化时序列化元数据,提供了崩溃安全性,sdi输出的是json格式】。同时将数据和索引同时存储在*.ibd文件中
- InnoDB聚集索引:InnoDB按照主键构建了一棵B+树(如果没有主键,则会选择一个唯一且非空的索引代替,如果没有这样的索引,InnoDB则会隐式地定义一个主键来作为聚集索引),同时叶子节点中存放整张表地行记录数据,也可以将聚集索引地叶子节点称为数据页,非叶子叶节点可以看做是叶子节点的稀疏索引
以下图做理解(图源网络)
MEMORY
- 使用存在于内存中的内容来创建表,所以一旦服务关闭,表中数据就会丢失,但表不会丢失,因为表是元数据
- 每个memory表只实际对应一个磁盘文件,格式是.frm
- memory类型的表的访问非常的快,因为它的数据是放在内存中的,并且默认使用HASH索引
- memory类型的存储引擎主要用于那些内容变化不频繁的代码表,或者作为统计操作的中间结果表,便于高效地对中间结果进行分析并得到最终的统计结果
- 对于存储引擎为memory的表进行更新操作时要慎重,因为数据并没有实际写入到磁盘,所以一定要对下次重新启动服务后如何获得这些修改后的数据有所考虑
MERGE
- merge存储引擎是一组MyISAM表的组合,这些MyISAM表的结构必须完全相同
- 采用merge存储引擎的表本身并没有数据,对merge类型的表可以进行查询、更新、删除操作,这些操作实际上是对内部的MyISAM表进行的
- 举个例子:假设我们建立两张结构完全相同的表
-- Table "article_0" DDL
CREATE TABLE `article_1` (
`id` bigint(20) NOT NULL,
`subject` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- Table "article_1" DDL
CREATE TABLE `article_2` (
`id` bigint(20) NOT NULL,
`subject` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
然后建立一个总表
-- Table "article_total" DDL
CREATE TABLE `article_total` (
`id` bigint(20) NOT NULL,
`subject` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MRG_MyISAM DEFAULT CHARSET=utf8 UNION=(`article_1`,`article_2`);
那么article_total表的内容就包含了article_1,article_2的内容
当执行 select * from article_total
表的结果就是各个表数据合并的内容
存储引擎怎么选
其实就是一句话:合适的才是最好的
除非我们需要InnoDB不具备的一些特性,并且没有其他办法代替,否则都应该优先考虑InnoDB,又或者,不需要InnoDB的特性,并且其他引擎更加合适当前的情况,例如多读少些,对数据恢复的要求不高,反而对存储空间要求较高
尤其需要注意的是:不要低估数据崩溃后恢复数据的重要性,MyISAM将数据写入内存中,然后等待操作系统定期将数据刷回磁盘
以下给出各引擎较为适合被选择的场景:
-
MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么就请选择MyISAM吧,MyISAM是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一
-
InnoDB(默认引擎) :用于事务处理应用程序,支持外键。如果应用对事务的完整性有较高要求,在并发条件下要求数据的一致性,数据操作除了插入和查询外,还有很多更新、删除操作,那么InnoDB是较为合适的选择。
InnoDB存储引擎除了可以有效降低由于删除和更新导致的锁定,还可以确保事务的完整提交和回滚,对于类似计费系统或财务系统等对数据准确性要求比较高的系统,InnoDB都是合适的选择。因为其采用事务日志,在系统崩溃后易于完成数据恢复 -
MEMORY:将所有的数据都保存在内存中,且支持HASH索引,在需要快速定位记录和其他类似数据的环境下,可提供极快的访问。
MEMORY的缺陷是对表的大小有限制,太大的表无法缓存在内存中,并且不支持BLOB、TEXT类型,而且使用的是表级锁,并发性能很低,现在很少使用,一般作为中间表来保存中间数据 -
MERGE:用于将一系列等同的MyISAM表以逻辑方式组合在一起,并作为一个对象引用它们。MERGE表的优点在于可以突破单个MyISAM表大小的限制,并且通过将不同的表分布在多个磁盘上,可以有效地改善MERGE表的访问效率。这对于诸如数据仓储等VLDB环境十分适合
SQL优化步骤
首先我们要了解为什么需要优化SQL
举个例子:一个项目的上线初期,由于业务数据量相对较少,一些SQL的执行效率对程序运行效率的影响不太明显,而开发和运维人员也无法准确判断SQL对程序的运行效率的影响有多大,但随着时间的积累,业务数据量的增多,SQL的执行效率对程序的运行效率的影响逐渐增大,那么此时我们就要对SQL进行优化来改善程序运行慢的问题
查询SQL的执行效率
- 需要优化SQL,那么第一步必然是需要知道优化哪一句SQL
- 我们可以通过
show [session|global] status
命令可以查询到服务器状态信息 show [session|global] status
可以根据需要加上的参数session 或 global 来显示session级(当前连接)的统计结果和global级(自数据库上次启动至今)的统计结果。如果不写,默认使用session
查看全局的统计结果:
查看当前会话的统计结果
也可以根据引擎来查看(如下Innodb的行的影响结果)
定位低效率的SQL
主要可以通过以下方式定位执行效率低的sql语句
- 慢日志:需要手动取my.ini中开启并设置路径,但需要注意的是它既然是个日志就说明是在查询结束后才记录,所以在应用反映执行效率出现问题的时候查询慢日志并不能及时定位到问题
show processlist
:这个东西类似于Windows系统中的任务管理器,可以查看mysql当前在进行的线程,包括线程的状态,是否缩表等,也可以实时地查看sql的执行情况,同时对一些锁表操作进行优化
字段说明:
名称 | 描述 |
---|---|
id | 当用户登录MySQL的时候系统会自动分配一个连接ID(可以理解为会话id),跨域通过 select connection_id();查看 |
user | 显示当前用户 |
host | 客户端Ip地址 |
db | 显示当前这个客户端连接的哪个数据库 |
command | 当前指定的命令:Sleep、Query、connect |
time | 显示当前这个命令状态持续的时间 |
state | 显示当前SQL的状态:coping to tmp table、sorting resulet、sending data等 |
info | 显示正在执行的SQL |
接下来我们准备一份拥有百万级数据的表
DROP TABLE IF EXISTS `system_user`;
CREATE TABLE `system_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_phone` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_address` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_status` smallint(2) NOT NULL,
`user_sex` smallint(2) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
向表中插入百万级数据
explain命令分析执行计划
通过上述步骤可以查询到低效率的SQL语句,然后通过explain执行查询计划即可查看SQL的执行计划
-
执行计划包含的信息
1、分析出表的读取顺序
2、数据读取操作的操作类型
3、哪些索引可以使用
4、哪些索引被实际使用
5、表之间的引用
6、每张表有多少行被优化器查询 -
执行计划详解
以上述百万条数据的system_user表为例
我们分别使用执行计划,查看查询有索引字段的语句和无索引字段的语句的区别
有索引(id):
无索引字段(user_uuid)
然后我们创建一个数据量少一点的表方便查看执行计划:
DROP TABLE IF EXISTS `tb_dept`;
CREATE TABLE `tb_dept` (
`id` int(11) NOT NULL,
`dname` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into `tb_dept`(`id`,`dname`) values (1,'开发'),(2,'测试'),(3,'运维');
DROP TABLE IF EXISTS `tb_emp`;
CREATE TABLE `tb_emp` (
`id` int(11) NOT NULL,
`ename` varchar(32) DEFAULT NULL,
`eage` int(11) DEFAULT NULL,
`eaddr` varchar(100) DEFAULT NULL,
`did` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_did` (`did`),
CONSTRAINT `fk_did` FOREIGN KEY (`did`) REFERENCES `tb_dept` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into `tb_emp`(`id`,`ename`,`eage`,`eaddr`,`did`) values (1,'K1',20,'南京',1),(2,'K2',21,'南京',1),(3,'C1',22,'南京',2),(4,'C2',21,'南京',2),(5,'Y1',23,'南京',3),(6,'Y2',24,'南京',3),(7,'Y3',24,'南京',3);
ID列
例如我们执行如下查询计划,获得了ID列有相同值,这说明ID列不是唯一标识,而是显示执行顺序的列:
执行下述执行计划,获得ID列值不同:
- ID列中如果数据为一组数字,表示执行SELECT语句的顺序,如果为NULL,则说明这一行数据是由另外两个SQL语句进行UNION操作后产生的结果集
- ID值相同时,说明SQL的执行顺序是按照显示从上至下执行的
- ID值不同时,ID值越大代表优先级越高,则越先被执行(一般出现在子查询)
- ID相同和不同,同时存在时,遵循优先级高的优先执行,优先级相同的按照由上到下的顺序执行
SELECT_TYPE列
- 该列描述的时查询的类型,主要用于区别普通查询、联合查询、子查询等复杂查询
- simple:简单的select查询,查询中不包含子查询或union查询
- primary:查询中若包含任何复杂的子部分,最外层查询则被标记为primary
- subquery:在select 或 where 列表中包含了子查询
- derived(中文含义:派生):在from列表中包含的子查询被标记为derived,mysql会递归这些子查询,把结果放在临时表里
- union:做第二个select出现在union之后,则被标记为union,若union包含在from子句的子查询中,外层的select将被标记为derived
- union result:从union表中获取结果的select
table和type列
- table:显示数据来源于哪一张表
- type:查询类型从最好到最差依次是:system>const>eq_ref>ref>range>index>All,一般情况下,需要至少保证达到range级别,最好能达到ref以上
- null:不查询任何表,直接访问数据,如函数
- system:表只有一行记录,这时const类型的特例,平时不会出现
- const:表示通过索引一次就找到了,const即常量,它用于比较primary key或unique索引,因为只匹配一行数据,所以效率很快,如将主键置于where条件中,mysql就能将该查询转换为一个常量(注意如果所有类型是字符串,则必须加上单引号,否则就不会使用索引)
- eq_ref:使用主键的关联查询(1对1),唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
- ref:非唯一性索引扫描,返回匹配某个单独值的行,它可能会找到多个符合条件的行,所以它应该处于查找和扫描的混合体
- range:只检索给定范围的行,使用一个索引来选择行,如where语句中出现的between,<,>,in等查询,这种范围扫描要比全表扫描好,因为它只需要开始于索引的某一点,而结束于另一个点,不必扫描全部索引
- index:index类型只遍历索引树,这通常比All快,因为索引文件通常比数据文件小,index是从索引中读取,all是从硬盘中读取
- all:全表扫描,最差的一种查询类型
key、prossible_key、rows和extra列
- key:实际使用的索引,如果未使用索引,则值为null
- possible_key:有可能被用到的索引
- rows:根据表统计信息及索引选用情况,估算出找到所需记录所需要读取的行数
- extra:其他情况
- Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,mysql中无法利用索引完成的排序操作称之为"文件排序”
- Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表,常见于order by和分组查询group by
- Using index:表示相应的select操作中使用了覆盖索引(Covering Index),避免访问表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找,如果没有同时出现using where,表明索引用来读取数据而非执行查找动作,其中覆盖索引的含义是指所查询的列和建立的索引字段和个数是一一对应的
- Using where:表明使用了where过滤
- Using join buffer:表明使用了连接缓存,如在查询的时候会有多次join,则可能会产生临时表
- impossible where:表示where子句的值总是false,不能用来获取任何元素
partitions和filtered列
mysql8后加的两个列
- partitions:使用的哪个分区,需要结合表分区才可以看到,对于非分区表值为null
- filtered:通过过滤条件之后的数据对比总数居条数的百分比
show profile的使用
explain关键字主要用于定性分析索引的使用情况,以及SQL语句的优劣,但是是无法得知SQL语句的实际执行情况的
而show profile命令可以做到定量分析 SQL语句的执行情况,即使用者可以明确知道一条SQL到底执行了多久
需要注意的是show profile从5.6.7开始不推荐使用,并且在以后的版本中会被删除,改用Performance Schema
- show profile的完整语法
SHOW PROFILE [type [, type] ... ]
[FOR QUERY query_id]
[LIMIT row_count [OFFSET offset]]
type: {
ALL
| BLOCK IO
| CONTEXT SWITCHES
| CPU
| IPC
| MEMORY
| PAGE FAULTS
| SOURCE
| SWAPS
}
参数说明:
type | 说明 |
---|---|
ALL | 显示所有性能信息 |
BLOCK IO | 显示块io的次数 |
CONTEXT SWITCHES | 显示上下文切换的次数,包括主动和被动 |
CPU | 显示系统和用户CPU使用时间 |
IPC | 显示发送和接收消息的次数 |
MEMORY | 暂未实现 |
PAGE FAULTS | 显示页面错误的数量 |
SOURCE | 显示源代码中的函数名称以及该函数所在文件的名称和行号 |
SWAPS | 显示swap的次数 |
- 使用步骤
1、首先开启show profile
2、执行一个查询
3、执行show profile
从上图中可以看到查询一个SQL所需要的全部过程,以及每个步骤所用的时间,从而可以得知到底是哪一步太过耗费时间,进而想办法解决
4、执行show profiles;
5、也可以根据show profile语法查看每个步骤使用的io和cpu的情况
关于Performance Schema的使用,开启这项功能的时候需要去配置文件进行开启,暂不在这里赘述了,以后用到去看。
优化器trace工具的使用
从MySQL5.6版本开始,optimizer_trace可支持把MySQL查询执行计划树打印出来,对DBA进行深入分析SQL执行计划,计算成本都非常有用,打印的内部信息比较全面,默认是关闭的,功能支持动态开关,因为对性能有20%左右影响,所以建议只在分析问题的时候,临时开启
- 浅用一下
1、查看功能是否开启
show variables like 'optimizer_trace';
2、设置为临时开启
set session optimizer_trace="enabled=on",end_markers_in_json=on;
3、执行一条查询
select * from tb_emp;
4、查看trace信息,实际上就是一个json文件
select * from information_schema.optimizer_trace;
!!注意,执行的时候两句一起查询,不要一条一条执行
索引使用
合适的索引可以帮助我们提升查询效率
准备工作
- 准备点数据先
create table staffs(
id int primary key auto_increment,
name varchar(24) not null default '' comment '姓名',
age int not null default 0 comment '年龄',
pos varchar(20) not null default '' comment '职位',
add_time timestamp not null default current_timestamp comment '入职时间'
)charset utf8 comment '员工记录表';
insert into staffs(name,age,pos,add_time) values('jack',22,'manager',now());
insert into staffs(name,age,pos,add_time) values('tom',23,'dev',now());
insert into staffs(name,age,pos,add_time) values('ketty',23,'dev',now());
select * from staffs;
alter table staffs add index idx_staffs_nameAgePos(name,age,pos);
可以看到我们最后创建了一个复合索引
- 看下这张表的索引
避免索引失效
两条比较重要的准则来避免索引失效
全值匹配:匹配的时候用全值
最佳左前缀法则:如果索引了多列,要遵循最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列(简单来说是带头大哥不能挂,中间兄弟不能断)
- 全值匹配
全值匹配的意思是对每个列都指定具体的值
explain select * from staffs where name='jack' and age = 22 and pos = 'manager';
-
最左前缀法则
当我们三个索引列都作为查询条件时,可知我们用到了复合索引
但当我们取消对name的匹配时,就会发现查询时没有用到索引
而当我们保留name字段,但取消age的条件时,发现虽然用到了索引,但键长发生了变化
那么当我们仅保留name字段去查询时,可以看到虽然也是用到了复合索引,但键长和上一个查询一致,这说明上一个查询和这个查询用到的索引一致,也就是只有一个字段的索引生效
所以,带头大哥不能挂,中间兄弟不能断的含义就是最左的索引必须成为查询条件,否则不会使用索引,中间的字段不能不被使用,否则也只会使用最左边字段的索引,其索引列不生效 -
不在索引列上做任何操作(如计算、函数、(自动/手动)类型转换),这些操作会导致索引失效而转向全表扫描
自动类型转换,指的是如果某个字段是字符串,记得加单引号,否则查询不会使用索引
可以看到如果索引字段使用函数,查询时将不会使用索引 -
存储引擎不能使用索引范围条件右边的列
即复合索引虽然有3个字段,但由于age用了范围查询,所以右边的列pos上的索引会失效 -
尽量使用覆盖索引(即只访问索引的查询(索引列和查询列一致)),减少使用select *
当时有select *时
当查询列和索引列一致时
说明搜索和匹配都使用到了索引 -
or查询会使索引失效
-
索引使用like时%加在右边,不要加在左边
加在左边时
加在右边时
-
使用覆盖所有解决like
-
字符串不加单引号会导致索引失效
字段是字符串,但查询时没有用单引号
字段是字符串,并且查询时使用单引号
-
is null,is not null有时索引失效,大多数情况都会失效
失效的情况
我们新增一列地址addr字段,并在该字段上创建索引
表中数据如下:
-
mysql评估使用索引反而会慢则不使用索引
mysql会去评估使全表扫描和使用索引时的速度,如果发现全表扫描反而会比使用索引快的时候,就会放弃使用索引,这也是上面is null和is not null有时生效,有时不生效的症结所在
实际上,我们也可以通过使用force index(ide_addr)强制让它使用索引 -
in使用索引,not in不使用索引(mysql8似乎not in也可以使用索引)
in使用了主键索引
not in (mysql8使用到了索引)
-
单列索引和复合索引的选择
尽量使用复合索引,少使用单列索引,单列索引在多条件使用的时候,只会选择最优的那个索引,而不会使用全部的索引
查看索引的使用情况
可以通过执行以下指令查看所有的使用情况,但仅作为参考,而不能作为优化的依据
show status like 'Handler_read%';
show global status like 'Handler_read%';
- 指标的含义
SQL优化
批量插入数据的优化
对于存储引擎是InnoDB的优化
- 主键插入顺序: 因innodb的索引使用的是B+树,而B+树是有序的,所以通过有序的主键可以达到优化效果,因为当你无序插入时它还需要去调整树,从而导致了效率低下
- 索引的设置: 当数据量较少的时候不要设置索引,因为全文查找比索引要快,当插入大量数据的时候,预先不要设置索引以及一些约束,在批量数据插入完成后进行设置,因为预先设置,每次插入数据时都会去进行验证和维护索引
- 关闭唯一性校验: 通过set unique_checks=0进行关闭。关闭后再通过set unique_checks=1打开
- 手动提交事务: set autocommit (0为关闭,1为打开)
insert语句的优化
- 单值插入改为多值插入
- 在事务中进行插入,插入前开启事务使用commit
- 保证数据的有序插入
order by优化
-
结论: 尽量避免使用额外的排序,尽量使用using index排序,因为索引本身是有序的,而在升降序的时候尽量保持一致,要么都用升序,要么都用降序
-
两种排序:
filesort: 对返回结果排序,不通过索引进行排序
using index:通过索引顺序扫描返回有序数据,效率较高 -
filesort文件排序
- 文件排序是通过相应的排序算法,将取得的数据在内存中进行排序
MySQL需要将数据在内存中进行排序,所使用的内存区域也就是我们通过sort_buffer_size系统变量所设置的排序区。这个排序区是每个Thread的,所以说在同一时刻在MySQL中可能存在多个sort_buffer内存区域 - MySQL中filesort的实现算法实际上有两种:
双路排序:首先根据相应的条件去除相应的排序字段和可以定位行数据的行指针信息,然后在sort buffer空间进行排序
单路排序:一次性取出满足条件行的所有字段,然后在sort buffer中进行排序
单路排序的出现是为了减少双路排序中需要两次访问表数据的IO操作,但相应的也会耗更多的sort buffer空间
MySQL主要通过比较我们所设定的系统参数max_length_for_sort_data的大小和Query语句中所取出的字段类型大小总和来判定需要使用哪一种排序算法。
如果max_length_for_sort_data更大,则使用单路排序,反之则使用双路排序
- 文件排序是通过相应的排序算法,将取得的数据在内存中进行排序
-
filesort优化
1、加大max_length_for_sort_data参数:在MySQl中决定用何种排序算法是通过参数max_length_for_sort_data来决定的。当所有返回字段的最大长度小于这个参数值,MySQL就会选择单路排序
所以如果有充足的内存让MySQL存放需要返回的非排序字段,就可以加大这个参数的值来让MySQL选用单路排序
2、去掉不必要的返回字段
当内存不是很富裕的时候,不能简单地通过强行加大上面的参数来强迫MySQL选用单路排序。因为这可能造成MySQL不得不将数据分成很多段,然后进行排序,反而得不偿失
这个时候就需要去掉不必要的返回字段,让返回结果长度尽可能地适应max_length_for_sort_data参数的限制
3、增大sort_buffer_size参数
增大这个参数不是为了让MySQL选择单路排序算法,而是为了让MySQL尽量减少在排序过程中对需要排序的数据进行分段,因为分段会造成MySQL不得不使用临时表来进行交换排序
group by 优化
- 当我们执行group by操作在没有合适的索引可用的时候,通常先扫描整个表提取数据并创建一个临时表,然后按照group by指定的列进行排序
- 在这个临时表中,对于每一个group的数据行来说是连续在一起的,完成排序后就可以发现所有的groups,并可以执行聚集函数(aggregate function)
- 可以看到在没有使用索引的时候,需要创建临时表和排序,在执行计划中通常可以看到“Using temporary;Using filesort"
- 需要注意的是mysql8取消了group by中的排序
- We have a try
先进行一个分组操作
然后创建索引进行优化
即尽量在有索引的列上进行group by操作
子查询优化
- 嵌套查询(即子查询)可以使用Select 语句创建一个单列的查询结果,然后将这个结果作为过滤条件用在另一个查询中
- 嵌套查询写起来简单,且易理解,但有时候可以被更有效率的Join替代
- 尽量使用连接查询来替代嵌套查询
以下是mysql5
使用连接优化查询
但mysql8中,连接和子查询似乎相差不大
or优化
- 前文也提到过or查询会使索引失效
- 尽量使用union来替代or进行优化
or不使用索引
虽然使用了索引,但是是range类型
使用union优化后,不但使用了索引,还使用type是const类型
limit优化
- MySQL里的分页查询,不是跳过offset行,而是取offset+N行,在返回时抛弃offset行,仅返回N行
- 这个的弊端就在于,当offset特别大的时候,效率非常的低下,例如“limit 1000,20"时,此时Mysql排序出前1020条数据后仅仅需要第1001到1020条记录,前1000条数据就会被抛弃,查询和排序的代价都非常高。
- 那么在优化时,两种方案:
控制返回的总页数
对超过特定阈值的页数进行SQL改写
未优化前的查询语句
-
优化1:在索引上完成排序分页的操作,最后根据主键关联回表查询所需要的其他列内容
-
优化2:将LIMIT查询转换成某个位置的查询,减少分页翻页的压力(需要注意的是:条件必须是唯一索引)
索引提示
- MySQL数据库支持索引提示,显式的告诉优化器使用了哪个索引
- 查看索引
-让优化器自己选择使用索引
- 通过use index使用指定索引(只是建议)
- 使用ignore index忽略所有
- 使用force index强制使用索引