MySQL高级
1. MySQL逻辑架构
1.1 概览
相较于其他数据库,MySQL的架构可以应用于多种不同的场景并发挥良好的作用。主要体现在存储引擎的架构上,==插件式的存储引擎架构将查询处理和其他的系统任务以及数据的存储提取相分离。==这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
1.1.1 连接层
最上层时一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端通供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
1.1.2 服务层
(1) Management Services & Utilities:系统管理和控制工具
(2) SQL Interface:SQL接口。接受用户的SQL命令,并且返回用户需要查询的结果。比如select from 就是调用SQL interface
(3) Parser :解析器。SQL命令传递到解析器的时候会被解析器验证和解析
(4) Optimizer:查询优化器。SQL语句在查询之前会使用查询优化器对查询进行优化。用一个例子理解:select uid,name from user where gender=1;
优化器来决定先投影还是先过滤
(5) Cache 和 Buffer:查询缓存。如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存、记录缓存、key缓存、权限缓存等。
1.1.3 引擎层
存储引擎层,存储引擎真正负责MySQL中数据的存储和提取,服务器通过API与存储引擎进行通讯。不同的存储引擎具有的功能不同,这样我们可以根据自己的需要进行选取。
1.1.4 存储层
数据存储层,主要是将数据存储在运行与裸设备的文件系统之上,并完成与存储引擎的交互。
1.2 查看SQL的执行周期
利用show profile 查看SQL的执行周期
第一步:修改配置文件 /etc/my.cnf。新增一行:query_cache_type=1
第二步:重启MySQL
第三步:开启profiling
show variables like '%profiling%';
set profiling = 1;
第四步:显示最近几次查询
show profiles;
第五步:查看程序的执行步骤
show profiles cpu,block io for query 编号;
1.3 查询流程
查询流程:
- MySQL客户端通过协议与MySQL服务器建立连接,发送查询语句,先检查查询缓存,如果命中,则直接返回结果,否则进行语句解析。也就是说,在解析查询之前,服务器会先访问查询缓存(query cache)——它存储select语句以及相应的查询结果集。如果某个查询结果以及位于缓存中,服务器就不会再对查询进行解析、优化以及执行。它仅仅将缓存中的结果返回给用户即可,这可以大大提高系统的性能。
- 语法解析器和预处理:首先MySQL通过关键字将SQL语句进行解析,并生成一棵对应的“解析”。MySQL解析器将使用MySQL语法规则验证和解析查询;预处理器则根据一些MySQL规则进一步检查解析树是否合法。
- 查询优化器当解析树被认为是合法时,由优化器将其转换成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
- 然后MySQL默认使用的BTREE索引,并且一个大致方向是:无论怎么折腾SQL,至少在目前来说,MySQL最多只用到表中的一个索引。
1.4 SQL执行顺序
实际写SQL语句顺序
select distinct
<select_list>
from
<left_table> <join_type>
join <right_table> on <join_condition>
where
<where_condition>
group by
<group_by_list>
having
<having_condition>
order by
<order_by_condition>
limit <limit_number>
实际机器读取顺序
- 随着MySQL版本的更新迭代,优化器也在不断地升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序
from <left_table>
on <join_condition>
<join_type> join <right_table>
where <where_condition>
group by <group_by_list>
having <having_condition>
select
distinct <select_list>
order by <order_by_condition>
limit <limit_number>
2. MySQL存储引擎
2.1 查看存储引擎
查看MySQL支持的存储引擎
show engines;
查看MySQL默认存储引擎
show variables like '%storage_engine%';
2.2 存储引擎
2.2.1 InnoDB
InnoDB是MySQL的默认存储引擎,它被设计用来处理大量的短期(short-lived)事务。除非有非常特别的原因需要使用其他的存储引擎。否则优先考虑InnoDB存储引擎
2.2.2 MyISAM
MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
2.2.3 Archive
Archive 档案存储引擎只支持insert和select操作,在MySQL5.1之前不支持索引。Archive表适合日志和数据采集类应用。根据英文的测试结论:Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表要小83%。
2.2.4 Blackhole
Blackhole引擎没有实现任何存储机制,它丢弃所有插入的数据,不做任何保存。但服务器会记录Blackhole表的日志,所有可以用于复制数据到备库,或者简单的记录到日志。但这种应用方式会碰到很多问题,因此并不推荐。
2.2.5 CSV
CSV存储引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引。CSV引擎可以作为一种数据交换的机制,非常有用。CSV存储的数据直接可以在操作系统里,用文本编辑器或excel读取。
2.2.6 Memory
如果需要快速的访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表是非常有用的。Memory表至少比MyISAM表快一个数量级。
2.2.7 Federated
Federated存储引擎是访问其他MySQL服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认禁用。
2.3 MyISAM与InnoDB区别
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发 | 行锁,操作时只锁某一行,不对其他行一影响,适合高并发操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性影响 |
关注点 | 节省资源、消耗少、简单业务 | 并发写、事务、更大资源 |
默认安装 | Y | Y |
默认使用 | N | Y |
自带系统表使用 | Y | N |
3. MySQL用户与权限管理
3.1 MySQL用户管理
3.1.1 创建用户
create user 用户名 identified by '密码';
3.1.2 修改用户
update mysql.user set user='新用户名' where user='用户名';
flush privileges;# 所有通过user表的修改必须用该命令才能生效
3.1.3 删除用户
drop user 用户名;
注:不要通过delete from user 用户名 where user = '用户名';
进行删除,系统会有残留
3.1.4 设置密码
修改当前用户的密码
set password = password('密码');
修改某个用户的密码
update mysql.user set password=password('密码') where user = '用户名';
flush privileges;
# 高版本密码为 authentication_string,即
update user set authentication_string=PASSWORD('新密码') where user='用户名';
3.1.5 user表
查看MySQL中的用户:MySQL的用户表存放在数据库mysql中的user表中
select host,user,authentication_string,Select_priv from mysql.user;
- host : 表示连接类型
- % : 表示所有远程通过TP方式连接
- loaclhost:本地方式,通过命令行方式的连接,比如
mysql -uroot -p123456
- user:用户名
- 同一用户通过不同方式连接的权限时不一样的
- authentication_string :密码
- 所有密码串为 明文密码生成的密文字字符串,加密算法为MYSQLSHA1,不可逆
- select_priv,insert_priv等为该用户拥有的权限
3.2 MySQL权限管理
3.2.1 查看权限
查看当前用户权限
show grants;
查看某用户的全局权限
select * from mysql.user where user = '用户名'\G;
# \G可以使数据按列显示
查看某用户的某个表的权限
select * from mysql.tables_priv;
3.2.2 授予权限
若发现没有此用户,则会直接新建一个用户
grant 权限1,权限2,...权限n
on 数据库名.表名
to 用户@用户地址
[identified by '密码'];
案例:给testUser用户用本地命令行方式下,授予test_database这个数据库下所有表的增删改查权限
grant select,insert,delete,drop on test_database.* to testUser@localhost;
案例:给testUser用户用本地命令行方式下,授予通过网络方式登录的testUser用户,对所有库,所有表的所有权限,密码设置为123456
grant all privileges on *.* to testUser@'%' identified by '123456';
3.2.3 收回权限
用户必须重新登录
revoke 权限1,权限2,...权限n
on 数据库名.表名
from 用户名@用户地址;
案例:回收test_User下test_db库全表的所有权限
revoke all privileges on test_db.* from test_User@localhost;
3.3 通过工具远程访问
第一步:ping一下数据库服务器的ip地址,确认网络通畅
ping IP地址
第二步:关闭数据库服务防火墙
service iptables stop
第三步:确认MySQL中已经有可以通过远程登录的账户
select host,user from mysql.user where host='%';
如果没有可以远程登录的用户,则执行以下语句
grant all privileges on *.* to 用户名@'%' identified by '123456';
第四步:测试连接
4. 索引优化
4.1 一般性能下降原因及应对措施
原因 | 措施 |
---|---|
数据过多(单表可存储500万条记录) | 分库分表 |
关联了太多的表,太多join | SQL优化 |
没有充分利用到索引 | 索引建立 |
服务器调优及参数设置 | 调整my.cnf |
4.2 索引
4.2.1 什么是索引
- 官方定义:索引(index)是帮助MySQL高校获取数据的数据结构。索引的目的在于提高查询效率,可以类比查字典(查询MySQL,先查m再查y,依次往下查询)。
- 可以简单理解为:“排好序的快速查找数据结构。”
- 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上
在数据本身之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法。这种数据结构就是索引。下图是一种可能的索引方式示例:
左边是数据表,一共两列七条记录,最左边的是数据记录的物理地址。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据物理记录的指针,这样就可以运用二叉查找树查找在一定发复杂度内获取到相应数据,从而快速的检索出复合条件的记录
4.2.2 索引优缺点
优点
- 类似大学图书馆建数目索引,提高数据检索的效率,降低数据库的IO成本
- 通过索引列对大数据进行排序,降低数据排序的成本,降低CPU的消耗
缺点:
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert、update和delete。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
4.2.3 MySQL索引结构
4.2.3.1 BTree索引
初始化介绍
- 浅蓝色的块为磁盘块,每个磁盘块包含几个数据项(深蓝色)和指针(黄色)
- 如图磁盘块1包含数据项17和35,包含指针P1,P2,P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
- 真实的数据存在于叶子节点,即3,5,9,10,13,… ,99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17,35并不真实存在与数据表中
查找过程
- 如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相较于磁盘的IO)可以忽略不计。通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
- 真实情况时,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的。如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
4.2.3.2 B+Tree索引
B+Tree与BTree的区别
(1)BTree树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+Tree的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
(2)在BTree树中,越靠近根节点的记录查找时间越快,只要找到·关键字即可确定记录的存在;而每个B+Tree中每个记录的查找时间基本是是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看BTree的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比BTree树多,树高比BTree树小,这样带来的好处是减少磁盘访问次数。尽管B+Tree树找到一个记录所需的比较次数比BTree树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+Tree树的性能可能还会好些,而且B+Tree树的叶子节点使用指针连接在一起,方便顺序遍历(例如查找一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+Tree树的缘故。
btree
- 数据
- 向下的指针
- 指向数据的指针
b+ tree
- 数据
- 向下的指针
为什么B+Tree比BTree更适合实际中操作系统的文件索引和数据库索引?
(1)B+Tree树的磁盘读写代价更低。
B+Tree树的内部节点并没有指向关键字具体信息的指针。因此其内部节点相对BTree树更小。如果把所有同一内部节点的关键字存放再同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
(2)B+Tree树的查询效率更加稳定
由于非终结节点并不是最终指向文件内容的节点,而至少叶子节点中关键字的索引。索引任何关键字的查找必须走一条从根节点到叶子节点的路。所有关键字查询的路径长度相同,导致每个数据的查询效率相当。
4.2.3.3 聚簇索引与非聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。
聚簇:表示数据行和相邻的键值聚簇的存储在一起
聚簇索引的好处
- 安装聚簇索引排列顺序,查询显示一定范围数据的时候,拥有数据都是紧密相连的,数据库不用从多个数据块中提取数据,所以节省了大量的IO操作
聚簇索引的限制
- 对于MySQL数据库目前只有InnoDB数据引擎支持聚簇索引,而MyISAM并不支持聚簇索引
- 由于数据物理存储排列方式只能有一种,所以每个MySQL的表只能有一个聚簇索引。一般情况下就是该表的主键
- 为了充分利用聚簇索引的聚簇的特性,所以InnoDB表的主键列尽量选用有序的顺序ID,而不建议用无需的ID,比如uuid这种
4.2.4 MySQL索引分类
4.2.4.1 单值索引
- 即一个索引列只包含单个列,一个表可以有多个单列索引
- 索引名一般以“idx_”开头
语法:
随表一起建立索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(20),
customer_name varchar(20),
primary key(id), # 主键索引
key(customer_name) # 单值索引
);
单独创建单值索引
create index 索引名 on 表名(字段名);
# 举例
create index idx_customer_name on customer(customer_name);
删除索引
drop index 索引名 on 表名;
# 举例
drop index idx_customer on customer;
4.2.4.2 唯一索引
即索引列的值必须唯一,但允许有空值
语法:
随表一起建立索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(20),
customer_name varchar(20),
primary key(id), # 主键索引
key(customer_name), # 单值索引
unique(customer_no) # 唯一索引
);
单独创建唯一索引
create unique index 索引名 on 表名(字段名);
# 举例
create unique index idx_customer_no on customer(customer_no);
删除索引
drop index 索引名 on 表名;
4.2.4.3 主键索引
设定主键后数据库会自动建立索引,InnoDB为聚簇索引
语法:
随表一起建立索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(20),
customer_name varchar(20),
primary key(id), # 主键索引
key(customer_name) # 单值索引
);
单独创建主键索引
alter table 表名 add primary key 表名(字段);
删除主键索引
alter table 表名 drop primary key;
修改主键索引必须先删除(drop)原主键索引,再建立(add)新主键索引
4.2.4.4 复合索引
一个索引包含多个列
语法:
随表一起建立索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(20),
customer_name varchar(20),
primary key(id), # 主键索引
key(customer_name,customer_no) # 复合索引,包含多个字段
);
单独创建复合索引
create index 索引名 on 表名(字段1,字段2,...,字段n);
删除索引
drop index 索引名 on 表名;
4.2.4.5 基本语法
创建索引
# 使用creat 添加唯一索引、单值索引、复合索引
create [unique] index 索引名 on 表名(字段);
# 使用alter 添加 主键索引、唯一索引、单值索引、全文索引
alter table 表名 add [primary key]|[unique]|[index]|[fulltext] 索引名 (字段);
/*
主键索引:索引值唯一,且不能为null
唯一索引:索引值唯一,可以出现多次null
普通索引:索引值可以出现多次
*/
删除索引
drop index 索引名 on 表名;
# 删除主键索引
alter table 表名 drop primary key;
查看索引
show index from 表名;
4.2.5 索引创建时机与非创建时机
需要创建索引的情况
- 主键自动创建唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 单键/组合索引的选择问题,组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
不需要创建索引的情况
- 表记录太少
- 经常增删改的表或字段:提高了查询速度,同时却会降低更新表的速度。因为更新表时,不仅要保存数据,还要保存索文件
- where条件里用不到的字段不创建索引
- 过滤性不好的不适合创建索引
4.3 SQL性能分析——explain
4.3.1 explain 概述
查看执行计划。使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句。分析查询语句或表结构的性能瓶颈
用处:
- 查看哪些索引被实际使用
- 查看每张表有多少行被物理查询
- 查看表达读取顺序
- 查看哪些索引可以使用
- 查看数据读取操作的操作类型
- 查看表之间的引用
使用:
explain SQL语句;
4.3.2 执行计划包含的信息
执行计划包含的信息
本小节使用的建表脚本语句
# 创建表t1,t2,t3,t4
create table t1(
id int(10) auto_increment,content varchar(50) null,primary key(id)
);
create table t2(
id int(10) auto_increment,content varchar(50) null,primary key(id)
);
create table t3(
id int(10) auto_increment,content varchar(50) null,primary key(id)
);
create table t4(
id int(10) auto_increment,content varchar(50) null,primary key(id)
);
# 分别往四个表中插入一条数据
insert into t1(content) values(concat('t1_',floor(1+rand()*1000)));
insert into t2(content) values(concat('t1_',floor(1+rand()*1000)));
insert into t3(content) values(concat('t1_',floor(1+rand()*1000)));
insert into t4(content) values(concat('t1_',floor(1+rand()*1000)));
4.3.2.1 id
- select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
- id的每个号码表示一趟独立的查询。一个SQL的查询趟数越少越好
分为以下三种情况
-
(1)id相同。执行顺序由上至下
explain select * from t1,t2,t3 where t1.id = t2.id and t2.id = t3.id;
-
(2)id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
explain select t1.id from t1 where t1.id = (select t2.id from t2 where t2.id = (select t3.id from t3 where t3.content='') );
-
(3)id既有相同也有不同,同时存在。id不同先执行大的,id相同则从上往下执行
explain select t1.id from t1 where t1.id = (select t2.id from t2 where t2.id in (select t3.id from t3 where t3.content='') );
4.3.2.2 select_type
查询类型,主要是用于区别普通查询,联合查询,子查询等的复杂查询
- SIMPLE:简单的select查询,查询中不包含子查询或union
- PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为primary
- DERIVED:在from列表中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,把结果放在临时表里
- SUBQUERY:在select 或where列表中包含了子查询
- DEPENDENT SUBQUERY:在select 或where列表中包含了子查询,子查询位于外层
- UNCACHEABLE SUBQUERY
- UNION:若第二个select出现在union之后,则被标记为union,若union包含在from子句的子查询中,外层select将被标记为DERIVED
- UNION RESULT:从UNION表获取结果的select
4.3.2.3 table
显示这一行的数据是关于哪张表的
4.3.2.4 partitions
代表分区表中的命中情况,非分区表该项为null
4.3.2.5 type
显示查询使用了哪种类型
- system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计
- const:表示通过索引一次就找到了,const用于比较primary key 或者union索引。因为只匹配一行数据,所以很快。如将主键至于where列表中,MySQL就能将该查询转换为一个常量
- eq_ref:唯一性索引扫描。对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
- ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而它可能会会找到多个复合条件的行,所以它应该属于查找和扫描的混合体
- range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。一般就是在你的where语句中出现了between,<,>,in等的查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引
- index:出现index是SQL使用了索引,但是没有通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组。
- all:Full Table Scan,将遍历全表以找到匹配的行
- index_merge:在查询过程中需要多个索引组合使用,通常出现在有or的关键字的SQL中
- ref_or_null:对于某个字段既需要关联条件,也需要null值的情况下。查询优化器会选择用ref_or_null连接查询
- index_subquery:利用索引来关联子查询。不再全表扫描
- unique_subquery:该连接类型类似于index_subquery。子查询中的唯一索引
type显示的是访问类型,结果值从最好到最坏依次是
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > all
system > const > eq_ref > ref > range > index > all
一般来说,得保证查询至少达到range级别,最好达到ref
4.3.2.6 possible_key
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
4.3.2.7 key
实际使用的索引。如果为null,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠
4.3.2.8 key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。key_len字段可以检查是否充分利用上了索引。
如何计算:
- (1)先看索引字段的类型+长度。比如int 为4,varchar(20)为20
- (2)如果是varchar或char这种字符串字段,字符集不同则要乘不同的值,比如utf8 要乘3,GBK要乘2
- (3)varchar这种动态字符要加两个字节
- (4)允许为空的字段要加一个字节
4.3.2.9 ref
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量值被用于查找索引列上的值
4.3.2.10 rows
显示mysql认为它执行查询时必须检查的行数。越少越好
4.3.2.11 filtered
这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数
4.3.2.12 extra
包含不适合在其他列中显示但十分重要的额外信息
- using filesort:说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”
- using temporary:使用了临时表保存最中间结果,MySQL在对查询结果排序时使用临时表。常见于排序和分组查询
- using index:利用索引进行了排序或分组。表示相应的select操作中使用了覆盖索引,避免访问表的数据行,效率不错。如果同时出现using where,表名索引被用来执行索引键值的查找。如果没有同时出现using where ,表名索引只是用来读取数据而非利用索引执行查找
- using where :表名使用了where过滤
- using join buffer:使用了连接缓存
- impossible where:where子句的值总是false,不能用来获取任何元组
- select tables optimized away:在没有group by子句的情况下,基于索引MIN/MAX操作或者对于MyISAM存储引擎优化count(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即完成优化。
4.4 查询优化
4.4.1 单表使用索引
常见索引失效情况
- 全值匹配我最爱
- 最佳左前缀法则
- 不在索引列上做任何操作(计算、函数、(手动或自动)类型转换)会导致索引失效
- 存储引擎不能使用索引中范围条件右边的列
- MySQL在使用不等于(<>,!=)的时候无法使用索引,会导致全表扫描
- is not null 无法使用索引,is null 可以使用索引
- like以通配符开头(’%abx…’)MySQL索引失效导致全表扫描
- 字符串不加单引号索引失效
一般性建议
- 对于单键索引,尽量选择针对当前query过滤性更好的索引
- 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中的位置越靠前越好
- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引
- 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
- 尽量避免索引失效情况
举例总结
假设index(a,b,c)
where语句 | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 | and b = 5 |
where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
where b = 3 或者where b = 3 and c = 4 或者 where c = 5 | N |
where a = 3 and c = 5 | 使用到a,但是c不可以,因为b中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到a,b,但是c不可以,因为c不能用在范围之后 |
where a is null and b is not null | a可以,b不可以 |
where a <> 3 | 不能使用索引 |
where abs(a) = 3 | 不能使用索引 |
where a = 3 and b like ‘kk%’ and c = 4 | 使用到a,b,c |
where a = 3 and b like ‘%kk’ and c = 4 | 只使用到a,b中like以通配符开头了 |
4.4.2 关联查询优化
(1)保证被驱动表的join字段已经被索引
(2)left join 时,选择小表作为驱动表,大表作为被驱动表
(3)inner join 时,MySQL或自动帮你把小结果集的表选为驱动表
(4)子查询尽量不要放在被驱动表,有可能使用不到索引
(5)能够直接多表关联的尽量直接关联,不用子查询
4.4.3 子查询优化
尽量不要使用not in 或者not exists。使用left outer join on xxx is null 代替
4.4.4 排序分组优化
- order by子句,尽量使用index方式排序,避免使用filesort方式排序
- group by 使用索引的原则几乎同order by一致,唯一区别是group by即使没有过滤条件用到索引,也可以直接使用索引
- 当范围条件和group by或者order by 的字段出现二选一时,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之亦然
- 如果不在索引列上,filesort有两种算法:双路排序和单路排序
- 双路排序:MySQL4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据。读取行指针和order by列,对他们进行排序,任何扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
- 从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段
- 取一批数据,要对磁盘进行了两次扫描,I\O是很费时的,所以在MySQL4.1之后出现了改进的算法:单路排序
- 单路排序:从磁盘读取查询需要的所有列,按order by 列在buffer对他们进行排序。然后扫描排序后的列表进行输出,它的效率更快一些,避免了二次读取数据。并且把随机I\O变成了顺序I\O,但是他会使用更多的空间,因为他把每一行都保存在内存中了
结论及引申出的问题
由于单路是后出的,总体而言好过双路,但是使用单路有问题:
在sort_buffer中,单路排序要比双路排序多占用很多空间,因为单路排序是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_buffer容量大小,再进行排序。。。从而多次I/0
本来想省一次I\0操作,反而导致了大量的I/O操作
优化策略
- 增大sort_buffer_size参数的设置
- 增大max_length_for_sort_data参数的设置
- 减少select后面的查询的字段
增大sort_buffer_size参数的设置
- order by 时select * 是一个大忌。只query需要的字段,这点非常重要
- (1)当query的字段大小总和小于max_length_for_sort_data而且排序字段不是text | blob类型时,会用改进后的算法——单路排序,否则会使用老算法——多路排序。
- (2)两种算法的数据都有可能超出sort_buffer的容量,超出之哈,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会大一些,所以要提高sort_buffer_size。
- (3)不管用哪种算法,提高这个参数都会提高效率,当然要根据系统的能力去提高,因为这个参数时针对每个进程的 1M-8M 之间调整
增大max_length_for_sort_data参数的设置
- 提高这个参数,会增加用改进算法的概率。但是如果设置的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状时高的磁盘I/O活动和低的处理器使用频率。1024-8192之间调整
4.4.5 覆盖索引
最后使用索引的手段:覆盖索引
简单说就是,select 到 from 之间查询的列 <= 使用的索引列 + 主键
5. 查询截取分析
5.1 慢查询日志
5.1.1 概述
MySQL的慢查询日志时MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL语句,则会被记录到慢查询日志中。默认为10s
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数
如果不是调优需要,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响,慢查询日志支持将日志记录写入文件。
5.1.2 慢查询日志开启
# 查询慢查询日志状态
show variables like '%slow_query_log%';
# 开启慢查询日志
set [global | session] slow_query_log = 1;
-
global :全局变量设置,对当前连接不影响,重启失效
-
session :对当前连接立刻生效
-
永久生效:修改配置文件my.cnf 。[mysqld]下增加修改参数
slow_query_log = 1 slow_query_log_file = /var/lib/mysql/atguigu-slow.log
若不指定慢查询日志文件存放路径,系统默认会给一个缺省的文件host_name-slow.log
5.1.3 查询当前系统有多少条慢查询记录
show [global] status like '%Slow_queries%';
5.1.4 mysqld下配置
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/atguigu-slow.log
long_query_time = 3
log_output = file
5.2 日志分析工具mysqldumpslow
5.2.1 mysqldumpslow 参数信息
使用命令mysqldumpslow --help
查看参数信息(bash下输入)
参数 | 说明 |
---|---|
-a | 不将数字抽象成N,字符串抽象成S |
-s | 表示按何种方式排序 |
c | 访问次数 |
l | 锁定时间 |
r | 返回记录 |
t | 查询时间 |
al | 平均锁定时间 |
ar | 平均返回记录数 |
at | 平均查询时间 |
-t | 即为返回前面多少条的数据 |
-g | 后面搭配一个正则表达式,大小写不敏感 |
5.2.2 常用参考
(1)得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
(2)得到按时间排序的前10条中含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
(3)使用命令时结合 | more使用,否则可能出现爆屏情况
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log | more
5.3 show processlist
show processlist;
查询用户正在干什么,可以使用命令kill [id]
终止进程
6. 主从复制
6.1 复制的基本原理
slave 会从master读取binlog来进行数据同步
步骤:
- (1)master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志时间(binary log events)
- (2)slave将master的binary log events拷贝到它的中继日志(relay log)
- slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL复制是异步的且串行化的。
6.2 复制的基本原则
基本原则:
- 每个slave只能有一个master
- 每个slave只能有一个唯一的服务器ID
- 每个master可以有多个slave
复制的最大问题——延时
6.3 一主一从常见配置
配置步骤:
-
MySQL版本一致且后台以服务运行
-
主从配置在[mysqld]节点下,都是小写
-
主机修改my.ini配置文件
# 1.主服务器唯一ID server-id=1 # 2.启用二进制文件 log-bin=自己本地的路径/data/mysqlbin log-bin=D:/software/MySQL/data/mysqlbin # 举例,从 为Windows下的二进制文件 # 3.设置不要复制的数据库 binlog-ignore-db=mysql # 4.设置需要复制的数据库 binlog-do-db=数据库名 # 5.设置logbin格式 binlog_format-STATEMENT(默认)
-
MySQL主从复制起始时,从机不继承主机数据
-
从机配置文件修改my.cnf的[mysqld]栏下
server-id=2 # 注意my.con中有server-id=1 relay-log=mysql-relay
-
主机、从机重启后台mysql服务
-
主机、从机关闭防火墙
Windows手动关闭:安全管家也要关闭 关闭虚拟机Linux防火墙:systemctl stop firewalld
-
在Windows主机上建立账户并授权slave
# mysql中 grant replication slave on *.* to '用户名'@'从机数据库ip' identified by '密码'; # 查询master状态 show master status; 记录下File和position的值 执行完此步骤后不要再操作主服务器MySQL,防止主服务器状态值变化
-
在Linux从机上配置需要复制的主机
# mysql中 change master to MASTER_HOST='主机IP', MASTER_USER='用户名', MASTER_PASSWORD='密码', MASTER_LOG_FILE='File名字'; MASTER_LOG_POS=position数字; # 启动从服务器复制功能 start slave; # 检查slave状态 show slave status\G /* 下面两个参数都是yes,则说明主从配置成功 Slave_IO_Running:YES Slave_SQL_Running:YES */
-
主机新建库、新建表、insert记录。从机复制
停止从服务器复制功能
stop slave;
如何重新配置主从
stop slave;
reset master;
7. 其他
7.1 修改字符集
mysql 5.7 直接插入中文会报错
ERROR 1366 (HY000): Incorrect string value: '\xE6\x9D\x9C\xE5\xB0\x91' for column 'username' at row 1
解决
第一步:修改配置文件
vim /etc/my.cnf
在最后加上中文字符集配置
character_set_server=utf8
第二步:重新启动mysql
第三步:修改以生成库表的字符集
修改数据库字符集
alter database 数据库名 character set 'utf8';
修改数据表字符集
alter table 数据库名 convert to character set 'utf8';
7.2 把字段按列显示
字段太多,显示不全
加入SQL语句末尾加上 \G
select * from 表名\G;
7.3 杂
-
实际项目中不加外键:效率低,。。。
-
一般用左外连接,很少用右外连接
-
上下没有重复的数据时用union all 效率更高。union会去重,必须保证上下两个字段一致
-
创建索引一般以 idx_字段名 命名