一、Mysql的架构介绍
1.1 Mysql简介
1.1.1 概述
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。
MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
Mysql是开源的,所以你不需要支付额外的费用。
Mysql是可以定制的,采用了GPL协议,你可以修改源码来开发自己的Mysql系统。
Mysql支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
MySQL使用标准的SQL数据语言形式。
Mysql可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。
MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。
1.1.2 Mysql高手是怎样练成的
- 数据库内部结构和原理
- 数据库建模优化
- 数据库索引建立
- SQL语句优化
- SQL编程(自定义函数、存储过程、触发器、定时任务)
- mysql服务器的安装配置
- 数据库的性能监控分析与系统优化
- 各种参数常量设定
- 主从复制
- 分布式架构搭建、垂直切割和水平切割
- 数据迁移
- 容灾备份和恢复
- shell或python等脚本语言开发
- 对开源数据库进行二次开发
1.2 Mysql Linux版的安装
1.2.1 下载地址
官网下载地址:https://dev.mysql.com/downloads/mysql/
链接:https://pan.baidu.com/s/1NQ5sB9VLb8wg5KTY_Hl5nA#list/path=%2F
提取码:k727
1.2.2 拷贝&解压缩
1.2.3 检查工作
1.检查当前系统是否安装过mysql
2.检查当前mysql依赖环境
3.检查/tmp文件夹权限
由于mysql安装过程中,会通过mysql用户在/tmp目录下新建tmp_db文件,所以请给/tmp较大的权限
执行:chmod -R 777 /tmp
4.安装
5.查看MySQL安装版本
执行 mysqladmin --version命令,类似java -version如果打出消息,即为成功。
通过vim查看mysql用户和mysql组
6.mysql服务的初始化
为了保证数据库目录为与文件的所有者为 mysql 登陆用户,如果你是以 root 身份运行 mysql 服务,需要执行下面的命令初始化
mysqld --initialize --user=mysql
另外 --initialize 选项默认以“安全”模式来初始化,则会为 root 用户生成一个密码并将该密码标记为过期,登陆后你需要设置一个新的密码
查看密码:cat /var/log/mysqld.log
root@localhost: 后面就是初始化的密码
7.mysql服务的启+停
8.首次登录
9.自启动mysql服务
10.Mysq的安装位置
在linux下查看安装目录:ps -ef|grep mysql
11.修改字符集问题
直接插入中文数据报错
1.3 Mysql的用户与权限管理
1.3.1 Mysl的用户管理
1.创建用户
create user test identified by 'test';
表示创建名称为test的用户,密码设为test;
2.了解user表
3.设置密码
修改当前用户的密码:
set password =password('123456')
修改某个用户的密码:
update mysql.user set password=password('123456') where user='li4';
flush privileges; #所有通过user表的修改,必须用该命令才能生效。
4.修改用户名
5.删除用户
1.3.2 权限管理
1.授予权限
2.收回权限
3.查看权限
4.通过工具远程访问
或者把第2步换成关闭防火墙
su root 切换到root用户
systemctl stop firewalld 关闭防火墙
systemctl status firewalld 查看状态
systemctl start firewalld 启动防火墙
1.4 Mysql的一些杂项配置
sql_mode
CREATE TABLE mytbl2 (
id INT,
NAME VARCHAR(200),
age INT,
dept INT
);
INSERT INTO mytbl2 VALUES(1,'zhang3',33,101);
INSERT INTO mytbl2 VALUES(2,'li4',34,101);
INSERT INTO mytbl2 VALUES(3,'wang5',34,102);
INSERT INTO mytbl2 VALUES(4,'zhao6',34,102);
INSERT INTO mytbl2 VALUES(5,'tian7',36,102);
#每个机构年龄最大的人
SELECT NAME,dept,MAX(age) FROM mytbl2 GROUP BY dept;
上面语句是错的
group by使用原则:select 后面只能放 函数 和group by后的字段
1、每个机构最大年龄 2、的人
SELECT * FROM mytbl2 m
INNER JOIN(
SELECT dept,MAX(age)maxage FROM mytbl2 GROUP BY dept)ab ON ab.dept=m.dept AND m.age=ab.maxage
1.5 Mysql逻辑架构介绍
1.总体概览
和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,
插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
1)连接层
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2)服务层
2.1 Management Serveices & Utilities: 系统管理和控制工具
2.2 SQL Interface: SQL接口
接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
2.3 Parser: 解析器
SQL命令传递到解析器的时候会被解析器验证和解析。
2.4 Optimizer: 查询优化器。
SQL语句在查询之前会使用查询优化器对查询进行优化。
用一个例子就可以理解: select uid,name from user where gender= 1;
优化器来决定先投影还是先过滤。
2.5 Cache和Buffer: 查询缓存。
如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。
这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
3)引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介绍MyISAM和InnoDB
4)存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
2.利用show profile查看sql的执行周期
修改配置文件/etc/my.cnf
新增一行: query_cache_type=1
重启mysql: systemctl restart mysqld
查看状态:systemctl status mysqld
select * from talble;
show profiles; #显示最近的几次查询
show profile cpu,block ip for query 编号 #查询程序的执行步骤
3.查询说明
4.SQL执行顺序
手写顺序:
随着Mysql版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。
下面是经常出现的查询顺序:
执行顺序:
1.6 Mysql存储引擎
1.6.1 查看命令
1.6.2 各个引擎简介
1、InnoDB存储引擎
InnoDB是MySQL的默认事务型引擎,它被设计用来处理大量的短期(short-lived)事务。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。
2、MyISAM存储引擎
MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
3、Archive引擎
Archive档案存储引擎只支持INSERT和SELECT操作,在MySQL5.1之前不支持索引。
Archive表适合日志和数据采集类应用。
根据英文的测试结论来看,Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%。
4、Blackhole引擎
Blackhole引擎没有实现任何存储机制,它会丢弃所有插入的数据,不做任何保存。但服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。但这种应用方式会碰到很多问题,因此并不推荐。
5、CSV引擎
CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引。
CSV引擎可以作为一种数据交换的机制,非常有用。
CSV存储的数据直接可以在操作系统里,用文本编辑器,或者excel读取。
6、Memory引擎
如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表是非常有用。Memory表至少比MyISAM表要快一个数量级。
7、Federated引擎
Federated引擎是访问其他MySQL服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。
1.6.3 MyISAM和InnoDB
1.6.4 阿里巴巴、淘宝用哪个
二、索引优化分析
2.1 性能下降SQL慢执行时间长,等待时间长
- 数据过多:分库分表
- 关联了太多的表,太多join:SQL优化
- 没有充分利用到索引:索引建立
- 服务器调优及各个参数设置:调整my.cnf
2.2 SQL预热:常见通用的Join查询
2.2.1 Join图
2.2.2 建表SQL
CREATE TABLE `t_dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `t_emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
empno int not null,
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
2.2.3 7种JOIN
-- 1. 查询所有有门派的人员信息(A、B两表共有)
select * from t_emp a inner join t_dept b on a.deptId = b.id;
-- 2.列出所有用户,并显示其机构信息(A的全集)
select * from t_emp a left join t_dept b on a.deptId = b.id;
-- 3.列出所有门派(B的全集)
select * from t_dept b;
-- 4.所有不入门派的人员(A的独有)
select * from t_emp a left join t_dept b on a.deptId = b.id where b.id is null;
-- 5.所有没人入的门派(B的独有)
select * from t_dept b left join t_emp a on a.deptId = b.id where a.deptId is null;
-- 6.列出所有人员和机构的对照关系(AB全有)
#MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
#left join + union(可去除重复数据)+ right join
SELECT * FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id
UNION
SELECT * FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id
-- 7.列出所有没入派的人员和没人入的门派(A的独有+B的独有)
SELECT * FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id WHERE B.`id` IS NULL
UNION
SELECT * FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id WHERE A.`deptId` IS NULL;
-- 增加掌门字段
ALTER TABLE `t_dept` add CEO INT(11) ;
update t_dept set CEO=2 where id=1;
update t_dept set CEO=4 where id=2;
update t_dept set CEO=6 where id=3;
update t_dept set CEO=8 where id=4;
update t_dept set CEO=9 where id=5;
-- 8.求各个门派对应的掌门人名称:
select * from t_dept as b left join t_emp as a on b.CEO=a.id;
-- 9.求所有当上掌门人的平均年龄
select avg(a.age) from t_emp a inner join t_dept b on a.id=b.CEO ;
-- 10.求所有人物对应的掌门名称
-- 方法一
select c.name, ab.name ceoname from t_emp c left join (select b.id, a.name from t_emp a inner join t_dept b on b.ceo = a.id) ab on c.deptId = ab.id;
-- 方法二
select ab.name, c.name ceoname from (select a.name, b.CEO from t_emp a left join t_dept b on a.deptId=b.id) ab left join t_emp c on ab.ceo=c.id;
-- 方法三
select a.name, c.name ceoname from t_emp a
left join t_dept b on a.deptId = b.id
left join t_emp c on b.CEO = c.id;
-- 方法四
select a.name, (select c.name from t_emp c where c.id=b.CEO) ceoname from t_emp a
left join t_dept b on a.deptId= b.id;
2.3 索引简介
2.3.1 是什么
Mysql官方对索引的定义为:索引(Index)是帮助Mysql高效获取数据的数据结构。可以得到索引的本质:索引是数据结构
索引的目的在于提高查询效率,可以类比字典,
如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从上往下找到y字母,再找到剩下的sql。
如果没有索引,那么你可能需要a----z,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?
是不是觉得如果没有索引,这个事情根本无法完成?
可以简单立即为"排好序的快速查找数据结构"
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
一般来说索引本身也很大,不可能全部存储在内存种,因此索引往往以索引文件的形式存储在磁盘上。
2.3.2 优势
类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本。
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
2.3.3 劣势
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert、update、和delete。因为更新表时,mysql不仅要保存数据,还要保存一下索引文件,每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
2.4 mysql索引结构
2.4.1 BTree索引
1.原理图
2.时间复杂度
2.4.2 B+Tree索引
1.原理图
2.B树和B+树的区别
1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;
B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
2)在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;
而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节
点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。
尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的
所有记录等),这也是很多数据库和文件系统使用B+树的缘故。
思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?
1) B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
2) B+树的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
2.4.3 聚簇索引与非聚簇索引
2.5 索引分类
2.5.1 单值索引
即一个索引只包含单个列,一个表可以有多个单列索引。
-- 随表一起建索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(200),
customer_name varchar(200),
primary key(id),
key(customer_name)
);
-- 单独建单值索引
create index idx_customer_name on customer(customer_name);
-- 删除索引
drop index idx_customer_name on customer;
2.5.2 唯一索引
索引列的值必须唯一,但允许有空值
-- 随表一起建索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(200),
customer_name varchar(200),
primary key(id),
key(customer_name),
unique(customer_no)
);
-- 单独建唯一索引
create unique index idx_customer_no on customer(customer_no);
-- 删除索引
drop index idx_customer_no on customer;
2.5.3 主键索引
设定为主键后数据库会自动建立索引,innodb为聚簇索引
-- 随表一起建索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(200),
customer_name varchar(200),
primary key(id)
);
-- 单独建主键索引
alter table customer add primary key customer(customer_no);
-- 删除主键索引
alter table customer drop primary key;
-- 修改主键索引
必须先删除掉(drop)原索引,再新建(add)索引
2.5.4 复合索引
即一个索引包含多个列
-- 随表一起建索引
create table customer(
id int(10) unsigned auto_increment,
customer_no varchar(200),
customer_name varchar(200),
primary key(id),
key(customer_name),
unique(customer_no)
key(customer_no,customer_name)
);
-- 单独建索引
create index idx_no_name on customer(customer_no,customer_name);
-- 删除索引
drop index idx_no_name on customer;
2.5.5 基本用法
-- 创建
create [unique] index [indxName] on table_name(column))
-- 删除
drop index[indexName] on mytable;
-- 查看
show index from table_name;
-- 使用alter命令
-- 1.该语句添加一个主键,这意味着索引必须是唯一的,且不能为null
alter table tbl_name add primary key(column_list);
-- 2.这条语句创建索引的值必须是唯一的(除了null外,null可能会出现多次)
alter table tbl_name add unique index_name(column_list);
-- 3.添加普通索引,索引值可出现多次
alter table tbl_name add index index_name(column_list);
-- 4.该语句指定了索引为fulltext,用于全文索引
alter table tbl_name add fulltext index_name(column_list);
2.5.6 哪些情况需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询种与其它表关联的字段,外键关系建立索引
- 单键/组合索引的选择问题,组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
2.5.7哪些情况不要创建索引
- 表记录太少
- 经常增删改的表或者字段
- where条件里用不到的字段不创建索引
- 过滤性不好的不适合建索引
2.6 性能分析Explain
2.6.1 是什么(查看执行计划)
使用explain关键字可以模拟优化器执行sql查询语句,从而知道mysql是如何处理你的sql语句的。分析你的查询语句或是表结构的性能瓶颈。
官网介绍
http://dev.mysql.com/doc/refman/5.5/en/explain-output.html
2.6.2 能干吗
- 表的读取顺序
- 哪些索引可以使用
- 数据读取操作的操作类型
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被物理查询
2.6.3 怎么玩
1.Explain+SQL语句
2.执行计划包含的信息
3.建表脚本
CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));
2.6.4 各字段解释
1.id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。
三种情况
id相同,执行顺序由上至下
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
id相同不同,同时存在
关注点
id号每个号码,表示一趟独立的查询。一个sql的查询趟数越少越好。
2.select_type
查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
simple:简单的select查询,查询中不包含子查询或者union
primary:查询中若包含任何复杂的子部分,最外层查询则被标记为primary
derived:在from列表中包含的子查询被标记为derived(衍生),mysql会递归执行这些子查询,把结果放在临时表里。
subquery:在select或where列表中包含了子查询
dependent subquery:在select或where列表中包含了子查询,子查询基于外层
in的区别
uncacheable subquery:不可用缓存的子查询,但凡sql一模一样就可以命中缓存,sql肯定会不一样就不能命中缓存。只要sql中包含系统变量,这块随时都会有变化,默认这种sql为不可用缓存
@@var:系统变量
show variables like '%lower_case_table_names%';
select @@lower_case_table_names from dual;
union:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为:derived
union result:从union表获取结果的select
3.table
显示这一行的数据是关于哪张表的
4.partitions
代表分区表中的命中情况,非分区表,该项为null
5.type
一般来说,得保证查询至少达到range级别,最好能达到ref。
system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计。
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于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。子查询中的唯一索引
6.possible_keys
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
7.key
实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠
8.key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。
key_len字段能够帮你检查是否充分的利用上了索引,值越大,越好
9.ref
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
10.rows
rows列显示mysql认为它执行查询时必须检查的行数。
越少越好
11.filtered
这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数
12.Extra
包含不适合在其他列中显示但十分重要的额外信息
Using filesort:说明myql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。mysql中无法利用索引完成的排序操作称为文件排序
Using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时,使用临时表。常见于排序order by和分组查询group by。
Using index:表示相应的select操作中使用了覆盖索引(Covering index),避免访问了表的数据行,效率不错!如果同时出现using where,表名索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。利用索引进行了排序或分组。
Using where:表明使用where过滤。
Using join buffer:使用了连接缓存。
impossible where:where子句的值总是false,不能用来获取任何元组。
select tables optimized away:在没有group by子句的情况下,基于索引优化min/max操作或者对于myisam存储引擎优化count(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
2.7 查询优化
2.7.1 批量数据脚本
1)往表里插入50w数据
1.建表
create table `dept` (
`id` int(11) not null auto_increment,
`deptname` varchar(30) default null,
`address` varchar(40) default null,
ceo int null ,
primary key (`id`)
) engine=innodb auto_increment=1 default charset=utf8;
create table `emp` (
`id` int(11) not null auto_increment,
`empno` int not null ,
`name` varchar(20) default null,
`age` int(3) default null,
`deptid` int(11) default null,
primary key (`id`)
#constraint `fk_dept_id` foreign key (`deptid`) references `t_dept` (`id`)
) engine=innodb auto_increment=1 default charset=utf8;
2.设置参数log_bin_trust_function_creators
3.创建函数,保证每条数据都不同
随机产生字符串
delimiter $$
create function rand_string(n int) returns varchar(255)
begin
declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzabcdefjhijklmnopqrstuvwxyz';
declare return_str varchar(255) default '';
declare i int default 0;
while i < n do
set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i = i + 1;
end while;
return return_str;
end $$
#假如要删除
#drop function rand_string;
随机产生部门编号
#用于随机产生多少到多少的编号
delimiter $$
create function rand_num (from_num int ,to_num int) returns int(11)
begin
declare i int default 0;
set i = floor(from_num +rand()*(to_num -from_num+1)) ;
return i;
end$$
#假如要删除
#drop function rand_num;
4.创建存储过程
创建往emp表插入数据的存储过程
delimiter $$
create procedure insert_emp(start int, max_num int )
begin
declare i int default 0;
#set autocommit =0 把autocommit设置成0
set autocommit = 0;
repeat
set i = i + 1;
insert into emp (empno, name ,age ,deptid) values ((start+i) ,rand_string(6), rand_num(30,50),rand_num(1,10000));
until i = max_num
end repeat;
commit;
end$$
#删除
# delimiter ;
# drop procedure insert_emp;
创建往dept表中插入数据的存储过程
#执行存储过程,往dept表添加随机数据
delimiter $$
create procedure `insert_dept`(max_num int )
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i + 1;
insert into dept (deptname,address,ceo) values (rand_string(8),rand_string(10),rand_num(1,500000));
until i = max_num
end repeat;
commit;
end$$
#删除
# delimiter ;
# drop procedure insert_dept;
5.调用存储过程
dept
#执行存储过程,往dept表添加1万条数据
delimiter ;
call insert_dept(10000);
emp
#执行存储过程,往emp表添加50万条数据
delimiter ;
call insert_emp(100000,500000);
2)批量删除某个表上的所有索引
1.存储过程
delimiter $$
create procedure `proc_drop_index`(dbname varchar(200),tablename varchar(200))
begin
declare done int default 0;
declare ct int default 0;
declare _index varchar(200) default '';
declare _cur cursor for select index_name from information_schema.statistics where table_schema=dbname and table_name=tablename and seq_in_index=1 and index_name <>'primary' ;
declare continue handler for not found set done=2 ;
open _cur;
fetch _cur into _index;
while _index<>'' do
set @str = concat("drop index ",_index," on ",tablename );
prepare sql_str from @str ;
execute sql_str;
deallocate prepare sql_str;
set _index='';
fetch _cur into _index;
end while;
close _cur;
end$$
2.执行存储过程
call proc_drop_index("dbname","tablename");
2.7.2 单表 使用索引及常见索引失效
1.全值匹配我最爱
where后面有几个字段,最好就建几个字段的索引
系统中经常出现的sql语句如下:
explain select sql_no_cache * from emp where emp.age=30;
explain select sql_no_cache * from emp where emp.age and deptid=4;
explain select sql_no_cache * from emp where emp.age=30 and deptid=4 and emp.name='abcd';
索引应该如何建立?
-- 清除索引
call proc_drop_index("myDB", "emp");
call proc_drop_index("myDB", "dept");
explain select sql_no_cache * from emp where emp.age=30;
create index idx_age on emp(age);
explain select sql_no_cache * from emp where emp.age and deptid=4;
create index idx_age_deptid on emp(age, deptid);
explain select sql_no_cache * from emp where emp.age=30 and deptid=4 and emp.name='abcd';
create index idx_age_deptid_name on emp(age, deptid, name);
创建索引前
创建索引后
2.最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
4.存储引擎不能使用索引中范围条件右边的列,索引中范围条件右边的列失效
5.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
6. is not null也无法使用索引,但是is null是可以使用索引的
7.like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作
8.字符串不加单引号索引失效
mysql内部有自动类型转换,导致索引失效
9. 小总结
10.一般性建议
- 对于单键索引,尽量选择针对当前query过滤性更好的索引
- 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引
- 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
- 书写sql语句时,尽量避免造成索引失效的情况
2.7.3 关联查询优化
1.建表sql
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
2.案例
3.建议
- 1.保证被驱动表的join字段已经被索引
- 2.left join时,选择小表作为驱动表,大表作为被驱动表。
- 3.inner join时,mysql会自己帮你把小结果集的表选为驱动表
- 4.子查询尽量不要放在被驱动表,有可能使用不到索引。
- 5.能够直接多表关联的尽量直接关联,不用子查询。
2.7.4 子查询优化
尽量不要使用not in 或者not exists
用left join on xxx is null替代
2.7.5 排序分组优化
以下是否能使用到索引,能否去掉usingfilesort
总结:无过滤,不索引
总结:顺序错,必排序
总结:方向反,必排序。
order by子句,尽量使用Index方式排序,避免使用fileSort方式排序
索引的选择:
执行案例前先清除emp上的索引,只留主键
如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序:
单路的问题
group by关键字优化
group by使用索引的原则几乎跟order by一致,唯一区别是group by即使没有过滤条件用到索引,也可以直接使用索引
2.7.6 最后使用索引的手段
覆盖索引
2.7.7 8个sql练习
-- 1.列出自己的掌门比自己年龄小的人员
select a.name, a.age, c.name ceoname, c.age ceoage from t_emp a
left join t_dept b on a.deptId = b.id
left join t_emp c on b.ceo = c.id
where c.age < a.age;
-- 优化
explain select sql_no_cache a.name, a.age, c.name ceoname, c.age ceoage
from emp a
left join dept b on a.deptId = b.id
left join emp c on b.ceo = c.id
where c.age < a.age;
-- 清除索引
call proc_drop_index("myDB", "emp");
call proc_drop_index("myDB", "dept");
-- 2.列出所有年龄低于自己门派平均年龄的人员
select c.name, c.age, aa.avgage from t_emp c inner join
(select a.deptId, avg(a.age) avgage from t_emp a where a.deptId is not null group by a.deptId) aa
on c.deptId = aa.deptId
where c.age < aa.avgage;
-- 优化
explain select sql_no_cache c.name, c.age, aa.avgage from emp c inner join
(select a.deptId, avg(a.age) avgage from emp a where a.deptId is not null group by a.deptId) aa
on c.deptId = aa.deptId
where c.age < aa.avgage;
create index idx_deptId on emp(deptId);
create index idx_deptId_age on emp(deptId, age);
-- 3.列出至少有2个年龄大于40岁的成员的门派
select b.deptName,b.id, count(*) 数量 from t_emp a
inner join t_dept b on a.deptId = b.id
where a.age > 40
group by b.deptName, b.id
having 数量 >=2;
-- 优化
explain select sql_no_cache b.deptName, b.id, count(*)
from dept b straight_join emp a on a.deptId = b.id
where a.age>40
group by b.deptName, b.id
having count(*) >=2;
-- straight_join 1.明确概念,2.明确前后两表的数量级,明确谁是驱动表谁是被驱动表
create index idx_deptName on dept(deptName);
create index idx_deptId_age on emp(deptId,age);
-- 清除索引
call proc_drop_index("myDB", "emp");
call proc_drop_index("myDB", "dept");
-- 4.至少有2位非掌门人成员的门派
select c.deptName,c.id,count(*) from t_emp a
inner join t_dept c on a.deptId = c.id
left join t_dept b on a.id = b.CEO
where b.id is null
group by c.deptName,c.id
having count(*) >=2;
-- 优化
explain select sql_no_cache c.deptName,c.id,count(*) from emp a
inner join dept c on a.deptId = c.id
left join dept b on a.id = b.CEO
where b.id is null
group by c.deptName,c.id
having count(*) >=2;
explain select sql_no_cache c.deptName,c.id,count(*)
from dept c straight_join emp a on a.deptId = c.id
left join dept b on a.id = b.CEO
where b.id is null
group by c.deptName,c.id
having count(*) >=2;
create index idx_deptName on dept(deptName);
create index idx_deptId on emp(deptId);
create index idx_CEO on dept(CEO);
-- 5.列出全部人员,并增加一列备注"是否为掌门",如果是掌门人显示是,不是掌门人显示否
select a.name, a.age, (case when b.id is null then '否' else '是' end) '是否为掌门'
from t_emp a left join t_dept b on a.id = b.CEO;
-- 6.列出全部门派,并增加一列备注"老鸟or菜鸟",若门派的平均值年龄>50显示"老鸟",否则显示"菜鸟"
select b.deptName, b.id, if(avg(a.age)>50, '老鸟', '菜鸟') '老鸟or菜鸟'
from t_emp a
inner join t_dept b on a.deptId = b.id
group by b.deptName, b.id;
-- 7.显示每个门派年龄最大的人
select c.name, c.age, aa.maxage from t_emp c inner join
(select a.deptId, max(a.age) maxage from t_emp a where a.deptId is not null group by a.deptId) aa
on c.deptId=aa.deptId and c.age = aa.maxage;
-- 8.显示每个门派年龄第二大的人
set @rank=0;
set @last_deptid=0;
set @last_age=0;
select t.*, if(@last_deptid=deptid, if(@last_age=age,@rank,@rank:=@rank+1), @rank:=1) as rk,
@last_deptid:=deptid as last_deptid, @last_age := age as last_age from t_emp t order by deptid, age desc;
1.
2.
3.
4.
三、查询截取分析
3.1 慢查询日志
3.1.1 是什么
mysql的慢查询日志是mysql提供的一种日志记录,它用来记录在mysql中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的sql,则会被记录到慢查询日志中。
具体指运行时间超过long_query_time值的sql,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。
由他来查看哪些sql超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢sql,希望能收集超过5秒的sql,结合之前explain进行全面分析。
3.1.2 怎么玩
1.说明
默认情况下,mysql数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
2.查看是否开启及如何开启
默认
开启
3.那么开启了慢查询日志后,什么样的sql才会记录到慢查询日志里面呢
4.case
-- 一、查看当前多少秒算慢
show variables like 'long_query_time%';
-- 二、设置慢的阈值时间
使用命令
set long_query_time = 1
修改为阈值到1秒钟就是慢sql
5.记录慢sql并后续分析
6.查询当前系统中有多少条慢查询记录
7. 配置版
3.1.3 日志分析工具mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析sql,显然是个体力活,mysql提供了日志分析工具mysqldumpslow
工作常用参考
3.1.4 show processList
能干什么:查询所有用户正在干什么
如果出现不顺眼的:kill [id]
四、mysql视图
4.1 是什么
将一段查询sql封装为一个虚拟的表
这个虚拟表只保存了sql逻辑,不会保存任何查询结果。
4.2 作用
1.封装复杂sql语句,提高复用性
2.逻辑放在数据库上面,更新不需要发布程序,面对频繁的需求变更更灵活
4.3 适用场景
很多地方可以共用的一组查询结果
报表
4.3 语法
-- 创建
create view view_name
as
select column_name(s)
from table_name
where condition
-- 查询
select * from view_name
-- 更新
create or replace view view_name
as
select column_name(s)
from table_name
where condition
4.4 注意事项
(适用5.5)
mysql的视图中不允许有from后面的子查询,但orale可以