MySQL相关基础与SQL索引优化分析
一、MySQL基本知识
1. MySQL简介
1.1 什么是MySQL
MySQL是一个开源的关系型数据库,由MySQL AB公司开发,目前已被Oracle收购。其遵循GPL协议,用户可根据需求定制化开发资金的MySQL。MySQL可移植性高,支持多种语言,例如:Java、PHP、C++、Python、Perl、Eiffel、Rubby等。其使用标准的SQL数据语言形式,可通过如下数据语言进行使用:
-
DQL:数据查询语言,select、from、where;
-
DML:数据操作语言,insert、delete、update;
-
DDL:数据定义语言,create、alter、drop、truncate;
-
DCL:数据控制语言,grant、rollback、commit
MySQL常用的SQL说明:
SQL | 描述 | 备注 |
---|---|---|
show databases | 列出所有数据库 | |
create database test01 | 创建数据库test01 | |
create database test01 character set utf8 | 创建数据库,并设置字符集为utf-8 | |
show create database test01 | 查看数据库字符集 | |
show variables like ’%char%‘ | 查询参数 | |
set [字符集属性]=utf8 | 设置字符集属性为utf8 | 仅临时更改,如需彻底修改需改配置文件 |
alter database test01 character set ‘utf8’ | 修改数据库字符集 | |
alter table table01 convert to character set ’utf8‘ | 修改数据表字符集 |
MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大支持4GB,64位系统表文件最大支持8TB。在200万条记录下不加索引性能依旧较好。
注意:
字符集需要安装之后立即修改,如果插入数据之后再进行修改则之前数据库、表格及数据依然为原编码格式!
2. 安装MySQL
2.1 Windows安装
以5.7.28为例:参考博文MySQL 5.7.28安装最稳教程
2.2 Linux安装
2.2.1 docker安装
2.2.2 yum 安装
yum install mysql
2.2.3 rpm安装
rpm -qa|grep mysql
rpm -ivh MySQL-client-xxx.linu.xxx.rpm
rpm -ivh MySQL-server-xxx.linu.xxx.rpm
2.3 Linux下常用命令
tips:
- 查看MySQL所属用户:cat /etc/passwd/|grep mysql
- 查看MySQL所属组:cat /etc/group/|grep mysql
- 查看MySQL当前服务状态:service mysql status
- 启动关闭MySQL: service mysql start、service mysql stop、service mysql restart
- 设置MySQL开机启动:chkconfig mysql on、chkconfig --list|grep mysql、cat /etc/inittab或者netsysv图形化操作
- 设置MySQL用户名密码:mysqladmin -u root -p 123456
- 进程查看:ps -ef|grep mysql
- 设置大小写不敏感:show variables like ’%lower_case_table_names%‘,修改my.cnf下lower_case_table_names=1,即大小写不敏感。
2.4 sql_mode
sql_mode定义了对MySQL中的语法校验规则,其默认值是空值,这种情况下是可以进行一些非法操作的,生产环境下必须将该值设置为严格模式,以下为sql_mode常用的值。
参数 | 说明 |
---|---|
ONLY_FULL_GROUP_BY | 若select未选中group by的字段,则SQL不合法 |
NO_AUTO_VALUE_ON_ZERO | 自增长列可插入0或者null |
STRICT_TRANS_TABLES | 若一个值不能插入到事务表中,则中断,对非事务表不做限制 |
NO_ZERO_IN_DATE | 严格模式下不允许日期和月份为零 |
NO_ZERO_DATE | 日期不允许插入零 |
ERROR_FOR_DIVISION_BY_ZERO | insert或update中,若数据被零除,则报错,如果未给出该模式,则返回null |
NO_AUTO_CREATE_USER | 禁止grant创建密码为空的用户 |
NO_ENGINE_SUBSTITUTION | 存储引擎被禁用则报错,若未设置则使用默认的存储引擎代替 |
2.4.1 查看和修改sql_mode
查看:select @@sql_mode
修改:set @@sql_mode=’’;
2.5 用户管理
命令 | 说明 | 备注 |
---|---|---|
create user z3 identified by ’123456‘ | 创建名称为z3,密码为12346的用户 | |
select host,user,password,select_priv,insert_priv,drop_priv from mysql.user | 查看用户和权限信息 | |
set password = password(‘123456’) | 修改当前用户密码 | |
update mysql.user set password=password(‘123456’) where user = ‘z3’ | 修改其他用户密码 | 通过user表的修改需要flush privileges才能生效 |
update mysql.user set user = ‘li4’ where user =‘z3’ | 修改用户名 | 通过user表的修改需要flush privileges才能生效 |
drop user li4 | 删除用户 | 不要通过user表删除,系统会有残留 |
host:表示连接类型
- %表示所有远程通过TCP连接
- IP地址
- 机器名
- ::1 ipv6本机地址
- localhost
2.6 权限管理
命令 | 说明 |
---|---|
grant select,insert,delete,drop on mydb.* to z3@localhost | 给本地的z3用户的mydb数据库所有表授予增删改查权限 |
grant all privileges on *.* to z3@’%’ identified by ‘123’ | 授予通过网络登录的z3用户对所有库的权限,密码为123 |
show grants | 查看权限 |
revoke all privileges on mysql.* from z3@localhost | 收回z3全库全表所有权限 |
二、SQL优化
一般使用SQL操作数据库时,当遇到SQL执行时间太长怎么办呢?一般我们都是从下面四个方面进行查摆问题一一优化:
- 查询语句过于粗糙
- 索引失效,明明增加了单值索引或者复合索引,但是explain查看执行计划却发现依旧是全表扫描
- 系统设计问题导致join关联查询太多
- 硬件方面需要优化,各缓冲参数设置过小(比如order by所使用的sort_buffer_size)
在优化开始之前,我们需要了解MySQL的逻辑架构是怎样的,也就是MySQL是如何处理我们的SQL语句的呢?
1. MySQL架构分析
上图为MySQL逻辑架构图,其主要分为连接层、服务层、引擎层及存储层。
-
连接层,MySQL最上层,它是一些客户端和连接服务,包含本地socket通信和基于客户端连接的类似tcp/ip通信,主要完成连接处理、授权认证及相关安全方案,该层引入了线程池的概念为安全认证客户端提供线程,例如驱动连接。
-
服务层:
Management Services & Utilities 系统管理和控制工具 SQL Interface SQL接口:接收SQL返回查询结果 Parser 解析器:验证和解析SQL语句 Optimizer 查询优化器:MySQL对SQL会根据优化器结果进行优化 Cache&Buffer 查询缓存:提高查询效率 -
引擎层
可插拔式引擎的选择,负责MySQL中数据的存储和查询,目前MySQL最常用的两种数据库存储引擎为InnoDB与MyISAM,下表为二者的区别。
特性 InnoDB MyISAM 是否支持事务 支持 不支持事务 是否支持外键 支持 不支持 索引类型 聚簇索引 非聚簇索引 是否保存表的行数 否 是(通过变量保存,避免select count(*) 时进行全表扫描) 全文索引 5.7以后支持 支持 是否可压缩后查询 否 是 锁的粒度 行锁 表锁 主键是否必须 是(聚簇索引特性) 否 存储文件 frm(表结构),ibd(数据文件) frm(表结构),myd(数据文件),myi(索引文件)
tips:
1、聚簇索引与非聚簇索引
聚簇索引:数据与索引是在同一B+Tree上的,非叶子节点存放的是索引(数据的指针),叶子节点存放的是数据,该索引类型必须有主键,数据可通过主键进行查询,之后建立的索引为辅助索引,辅助索引需要两次查询,先查询到主键,然后通过主键查询数据,所以主键最好设置为自增,否则主键过大会带来额外开销;
非聚簇索引:B+Tree的非叶子节点和叶子节点存放的都是数据的引用地址,数据文件与索引文件是分开保存的。
2、InnoDB的行锁是实现在索引上的,而不是锁在物理上,如果索引失效则行锁会退化为表锁。
- 存储层
数据库文件的数据存储在文件系统上的方式,并通过该存储方式与存储引擎交互。
2. SQL执行分析
2.1 SQL查询流程
通过对MySQL逻辑结构的分析,我们可以知道SQL查询的流程大致为:
- MySQL客户端通过协议与MySQL服务器建立连接,发送查询语句;
- MySQL检查查询缓存,缓存如果命中则直接返回,未命中则将语句交由解析器处理;
- 解析器通过关键字对SQL语句预处理,生成解析树并验证SQL语法,若语法通过则交由优化器;
- 优化器将SQL转化成执行计划并选择最好的执行;
- 优化器将最终的查询结果返回。
SQL手写为:
优化器优化之后的顺序:
tips:
- show engines可查看所有数据库引擎
- show variables like ‘%storage_engine%’ 可查看当前默认数据库引擎
2.2 show profile
2.2.1 SQL执行周期
通过show profile可以查看SQL的详细执行周期,可以作为优化SQL最强有力的工具。
- 通过show variables like '%profiling%'查看是否开启该功能;
- set profiling = 1;开启show profile;
- show profile cpu,block io for query Query_id;查询SQL详细执行信息。
show profiles可以提供SQL执行的详细信息,但是实际中我们更多使用explain对SQL进行解释优化,查看SQL是否使用索引等等,下文更多是针对explain的使用进行介绍。
3. SQL索引优化
如何提高SQL执行速度?许多人第一反应就是增加索引,什么是索引呢?如何增加索引且增加的索引不会因为SQL问题导致失效呢?
我们知道MySQL底层使用B+树实现,索引作为数据的另一种表现形式同样也会耗费存储空间的。
如果单纯的为了提升某个字段的查询效率而在全表字段上都增加索引,那么无疑给存储空间带来了巨大负担。而且索引增加之后虽然给查询带来了便利,但是在增加和删除方面,为了维护索引而带来的工作量也是极为繁重的,这说明了不经研究而随意建立索引的方法是不可取的。
3.1 索引简介
3.1 什么是索引?
索引是帮助MySQL高效获取数据的数据结构,所以索引可以简单理解为排好序的快速查找数据结构。
3.2 索引的优缺点
优点:
- 提高数据检索的效率,降低数据库的IO成本;
- 索引是一种排好序的数据结构,通过索引进行排序可以降低数据的排序成本。
劣势:
- 降低更新表的速度,加了索引的字段在insert、update、delete时会带来额外的开销;
- 占用多余的空间。
3.2 适合创建索引的情况
- 主键自动建立唯一索引
- 频繁作为查询条件的字段需要建立索引
- 查询中与其他表关联的字段,外键关系建立索引
- 组合索引性价比优于单值索引
- 查询中排序的字段
- 查询中统计或者分组的字段
3.3 不适合创建索引的情况
- 表记录太少
- 经常增删改的表或者字段
- where条件里用不到的字段不创建索引
- 字段中重复内容较多,过滤性不好的不适合建立索引
3.4 explain性能参数分析
在日常工作中,使用最为频繁的就是通过explain+SQL模拟优化器执行SQL语句,从而知道MySQL是如何处理SQL语句的,并根据情况分析SQL的性能瓶颈以优化SQL。
那么,通过explain我们可以获得什么信息呢?
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
3.4.1 explain参数
我们通过explain+SQL的方式可以获得如下所示的列表:
可以看出主要参数即:id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra。
以下针对第一行参数进行详细分析:
-
id:select查询的序列号,包含一组数字,表示查询中select子句或操作表的顺序,主要分以下三种情况:
- id相同,执行顺序由上至下;
- id不同,如果是子查询,id的序号会较大,此时优先级越高,也就越先被执行;
- id相同不同,同时存在时先根据不同定优先级,再由上至下进行执行;
-
select_type:查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询,具体参数如下:
- SIMPLE:简单的select查询,不包含子查询或者UNION;
- PRIMARY:查询中若包含其他子查询,则最外层会被标记为PRIMARY;
- SUBQUERY:select或者where中包含了子查询;
- DERIVED:From中包含的子查询,临时表;
- UNION:第二个select出现在UNION之后则会被标记为UNION;
- UNION RESULT:从UNION中获取结果的select;
-
table:显示改行的数据来源于哪张表
-
partitions:是否分区
-
type:优化器定义的访问类型,从最好到最差依次是scerria(system>const>ref_eq>ref>range>index>all),具体含义如下:
- system:表中只有一行记录,相当于const类型的特例,平时不会出现;
- const:通过索引一次就找到了,const用于比较primary key或者unique索引;
- eq_ref:唯一性索引,对于每个索引键,表中只有一条记录与之匹配,简单地说是const是直接按主键或唯一键读取,eq_ref用于联表查询的情况,按联表的主键或唯一键联合查询;
- ref:非唯一性索引,返回匹配某个值的所有行(一值找多行);
- range:索引给定范围的行,例如where中的between、in,这种范围索引较全表扫描要好,因为范围决定了不用全表扫描;
- index:full index,全表索引扫描,效果与all的区别就是index扫描的是全表的索引,而all扫描的是数据;
- all:全表数据扫描,匹配到之后返回结果。
一般来说,保证查询达到range或者ref效果为佳。
-
possible_keys:显示可能应用在这张表中的索引,一个或多个。查询所涉及字段若存在索引,则索引将被列出,但不一定被使用
-
key:查询中实际使用的索引,如果没有则为null,查询中如果使用了覆盖索引,则索引和查询的字段一一吻合
-
key_len:表示索引使用到的字节数,可通过该值计算查询中使用的索引长度。在不损失精度的情况下,该值越小越好。该值为索引最大可能长度,并非实际使用长度。
-
ref:显示索引哪一列被使用了,哪些列或者常数被用于索引进行查询(最好是常数)。也就是说key 列是实际使用的 index , 但 index 可能建立在数据表的若干列上。ref 列列出具体哪些列或常数被使用了。
-
rows:根据表统计信息及索引使用情况大致估算除所需要读取的行数
-
filtered:返回结果的行占需要读到的行(rows列的值)的百分比,因为对于join操作,前一个表的结果集大小直接影响了循环的次数
-
Extra:包含不适合在其他列中显示但十分重要的额外信息,其主要会有以下几个常数:
参数 说明 Using filesort MySQL会对数据使用外部的文件索引排序,而不是按照表内索引顺序读取,文件的建立删除会耗费大量资源,出现该项一定要优化 Using temporary 使用临时表保存中间结果,常见于order by、group by,临时表的创建与删除会耗费大量资源,出现该项一定要优化 Using index 相应的select中使用了覆盖索引,避免访问冗余数据,效果不错,同时出现Using where则表明索引被用来执行索引键的查找,若没有出现Using where 则表明索引用于读取数据而非执行查找 Using where 表明使用了where过滤 Using join buffer 使用了连接缓存,可在my.cnf中增加缓存大小 impossible where where的值总是false,不能获取任何值 select tables optimized away 在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。 distinct 优化distinct,找到第一匹配值即停止
以上就是对explain参数的分析,通过对这些参数的学习,将使得我们以后在SQL优化方面无论遇到什么问题都能对症下药,迎刃而解。
3.5 索引失效情况分析
有时候我们明明在字段上建立了索引,但是explain之后发现优化器并没有使用到索引,那什么情况下会导致索引失效?写SQL语句时应该注意些什么才能避免索引失效呢?
3.5.1 索引失效
以下是常见导致索引失效的几种情况:
- 最左前缀法则:建立的复合索引在使用时没有遵循最左前缀法则,例如索引使用了(a,b,c)三个字段,但是使用的时候a没有使用到,索引失效;
- 在索引字段上进行计算,函数处理,类型转换等二次操作;
- 范围条件右边的列索引失效,即范围之后全失效,例如索引为(id,age,name),使用时如果出现where id=xx and age>10 and name=xx,则age之后索引失效;
- 尽量使用覆盖索引,避免select *,即复合索引的字段与查询字段一一吻合;
- !=或<>会导致索引失效;
- is null,is not null会导致索引失效;
- 使用like模糊查询时如果%出现在最左边则索引失效,解决办法就是使用覆盖索引;
- 字符串不加单引号;
- 多次使用or会导致索引失效。
以上就是会造成MySQL索引失效的一些情况,为了避免这些情况的出现而造成索引失效,建议:
- 对于单值索引,尽量选择针对查询过滤性更好的字段来建立索引,例如性别只有男、女、未知的三类就没有必要建立索引;
- 复合索引过滤性最好的字段要放在最前面;
- 复合索引尽可能包含查询字段中where子句中更多的字段;
- 通过分析统计信息和调整SQL查询的写法来选择建立合适的索引。
完!如有不妥欢迎批评指正!