1.MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。
2.MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
3.Mysql是开源的,所以你不需要支付额外的费用。
4.Mysl支持大型的数据库,可以处理拥有上千万条记录的大型数据库。
5.MySQL使用标准的SQL数据语言形式。
6.Mysql可以允许于多个系统上,并且支持多种语言,这些编程语言包括C、C++、Python、Java、Ped、PHP、Eifel、Ruby和TCL等。
7.Mysql对PHP有很好的支持,PHP是目前最流行的Web开发语言。
8.MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。
9.Mysql是可以定制的,采用了GPL协议,你可以修改源码来开发自己的Mysql系统。
2.数据库的基本结构理解:
数据库名词 | 描述 |
---|---|
database(数据库) | 用于保存有类型,有分类的数据,一般是一个文件,或是一组文件 |
table(数据库表) | 某种数据类型的结构化清单,数据库表存在数据库当中 |
column(列) | 表中的一个字段(列) |
row(行) | 表中的一条记录(行) |
dataType(数据类型) | 数据库表中所规定的数据类型 |
primary key(主键) | 一列或一组,其中的值能够用于区分表中的每一行记录 |
sql子句的执行顺序:
子句 | 说明 | 是否必须存在 |
---|---|---|
select | 查询要返回的列,表达式,函数 | 是 |
from | 从中检索的数据库表 | 仅在选择数据库表的时候使用 |
where | 行级过滤 (将查询出来的数据进行筛选) | 否 |
group by | 对查询出来的数据进行分组 | 仅在按组计算的时候使用 |
having | 组级过滤,对分组后的数据再次进行筛选 | 否 |
order by | 对查询完后的数据进行排序 | 否,仅在需要排序的时候使用 |
limit | 需要检索的行数(也就是分组查询) | 否,分组查询时,使用 |
3.linux下安装mysql:
1.首先检查是否安装过mysql:
yum list installed | grep mysql
如果有的话,就卸载掉该版本:
yum -y remove mysql-libs.x86_64
这里介绍一个安装mysql的工具方便快捷,linux宝塔,其余的不再概述,网上看教程,简单的很。
我安装的是mysql5.7版本
linux宝塔面板:地址
1.就是入 下图这个网站
2.然后点击导航栏的linux面板,拉倒底部
3.选择你的linux系统版本,然后将命令复制到你的linux系统终端命令行运行即可安装;
4.安装完成之后,会出现地址,将面板地址复制到浏览器,根据提供的密码账号登录就可以
5.登录进linux宝塔之后,进入软件商店,选择对应的版本进行安装即可,然后点击数据库,配置数据库密码
6.登录mysql
mysql服务的停止与启动
mysql 服务的启动
service mysqld start
mysql 服务的停止
service mysqld stop
查看 mysql 服务的状态
service mysqld status
查看安装时创建的 mysql 用户和 mysql 组
查看 mysql 用户
cat /etc/passwd|grep mysql
查看 mysql 用户组
cat /etc/group|grep mysql
设置 mysql 开机启动
开机自启动
chkconfig mysqld on
查看是否设置成功:2、3、4都是on代表开机自动启动
chkconfig --list | grep mysqld
如何关闭mysql服务开机自启动
如果想要关闭 mysql 的开机自启动,输入ntsysv,找到 mysqld ,*代表该服务为开机自启动,按空格可取消掉,tab键可以切换到取消和确,
ntsysv:以图形化界面的方式,查看哪些服务是否是开机自启动。
注意:按照这种方式安装的:mysql的配置文件是在:etc/my.cnf 中
查看数据库的编码格式:
如果数据库表中插入中文乱码,大多数是因为数据库的编码格式有问题,改为utf-8就好了
在my.cnf中配置
路径 | 解释 |
---|---|
/www/server/data | mysql 数据库文件的存放路径 |
/etc/my.cnf | 配置文件目录位置 |
/www/server/mysql/bin | 相关命令目录 |
/etc/init.d | 服务启停相关 |
MySQL 用户组管理
创建用户 :
创建一个admin 用户,并且初始密码为 admin
create user admin identified by "admin";
查看用户组权限(mysql7.0是没有password字段的,之前的版本有)
select host, user, select_priv, insert_priv,drop_priv from mysql.user;
1.host :表示连接类型
% :表示所有远程通过 TCP 方式的连接
IP :地址 如 (192.168.1.2,127.0.0.1) 通过制定 ip 地址进行的 TCP 方式的连接
机器名: 通过制定 i 网络中的机器名进行的 TCP 方式的连接
::1 :IPv6 的本地 ip 地址 等同于 IPv4 的 127.0.0.1
localhost :本地方式通过命令行方式的连接 , 比如 mysql -u xxx -p 123xxx 方式的连接。
select_priv :该用户是否拥有查询权限 insert_priv :该用户是否拥有插入权限 drop_priv :该用户是否拥有删除权限 Update_priv:该用户是否拥有修改权限 user:就是保存当前数据库用户的字段
mysql库里面的user表就是管理mysql用户的,用户的信息都存在该表中
修改当前用户的密码为:admins
set password =password('admins');
修改admin用户的密码为 123456
注意点:5.7的password字段被 authentication_string 取代了
update mysql.user set authentication_string =password("123456") where user="admin";
修改用户名:把admin用户的名称改为:tom
update mysql.user set user='tom' where user='admin';
删除用户: 删除 tom 用户
drop user tom
注意:权限管理的操作,只有root用户才可以进行操作,而且只需完权限命令之后需要,执行flush privileges
进行权限的刷新
4.MySQL 的权限管理
查看当前用户的权限
show grants;
该权限如果发现没有该用户, 则会直接新建一个用户。
语法:grant 权限 1,权限 2,…权限 n on 数据库名称.表名称 to 用户名@用户地址 identified by '连接口令'
示例:新建一个admin用户,并且给 admin用户 授予 test_one这个库下的所有 表的插删改查的权限。
grant select,insert,delete,drop on test_one.* to admin@localhost ;
授予通过网络方式登录的的 admin 用户,对所有库所有表的全部权 限, 密码设为 admin123
示例:
grant all privileges on *.* to admin@'%' identified by 'admin123';
权限收回
收回admin用户全库全表的全部权限:
REVOKE ALL ON *.* FROM admin@localhost;
注意:在你赋予用户权限后想到撤销权限时,你赋予什么权限,就收回什么权限,当然你收回的时候可以放大,但是不能缩小。 比如你赋予了database. 那么你收回的时候可以写all,*.*
收回admin用户的增删改查权限
REVOKE select,insert,update,delete ON *.* FROM admin@localhost;
MySQL 配置文件
二进制日志文件 log-bin
二进制日志文件 log-bin :用于主重复制
错误日志 log-error
默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等
查询日志 log
默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源的
数据文件
mysql配置文件
5.MySQL 逻辑架构介绍
mysql 的分层思想
1.和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上。
2.插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
mysql 四层架构
1.连接层:最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2.服务层:第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
2 .引擎层:存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过APl与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介绍MyISAM和InnoDB
3.存储层:数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
服务层:详细概述
MySQL 部件
Connectors:指的是不同语言中与SQL的交互
Management Serveices & Utilities: 系统管理和控制工具
Connection Pool:连接池
管理缓冲用户连接,线程处理等需要缓存的需求。负责监听对 MySQL Server 的各种请求,接收连接请求,转发所有连接请求到线程管理模块。
每一个连接上 MySQL Server 的客户端请求都会被分配(或创建)一个连接线程为其单独服务。而连接线程的主要工作就是负责 MySQL Server 与客户端的通信。接受客户端的命令请求,传递 Server 端的结果信息等。线程管理模块则负责管理维护这些连接线程。包括线程的创建,线程的 cache 等。
SQL Interface:SQL接口。接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
Parser:解析器
SQL命令传递到解析器的时候会被解析器验证和解析。解析器是由Lex和YACC实现的,是一个很长的脚本。
在 MySQL中我们习惯将所有 Client 端发送给 Server 端的命令都称为 Query,在 MySQL Server 里面,连接线程接收到客户端的一个 Query 后,会直接将该 Query 传递给专门负责将各种 Query 进行分类然后转发给各个对应的处理模块。
解析器的主要功能:
将SQL语句进行语义和语法的分析,分解成数据结构,然后按照不同的操作类型进行分类,然后做出针对性的转发到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的
Optimizer:查询优化器
SQL语句在查询之前会使用查询优化器对查询进行优化。就是优化客户端发送过来的 sql 语句 ,根据客户端请求的 query 语句,和数据库中的一些统计信息,在一系列算法的基础上进行分析,得出一个最优的策略,告诉后面的程序如何取得这个 query 语句的结果
他使用的是“选取-投影-联接”策略进行查询。
用一个例子就可以理解: select uid,name from user where gender = 1;
这个select 查询先根据where 语句进行选取,而不是先将表全部查询出来以后再进行gender过滤
这个select查询先根据uid和name进行属性投影,而不是将属性全部取出以后再进行过滤
将这两个查询条件联接起来生成最终查询结果
Cache和Buffer:查询缓存
他的主要功能是将客户端提交 给MySQL 的 Select 类 query 请求的返回结果集 cache 到内存中,与该 query 的一个 hash 值 做一个对应。该 Query 所取数据的基表发生任何数据的变化之后, MySQL 会自动使该 query 的Cache 失效。在读写比例非常高的应用系统中, Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。
如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
存储引擎接口
存储引擎接口模块可以说是 MySQL 数据库中最有特色的一点了。目前各种数据库产品中,基本上只有 MySQL 可以实现其底层数据存储引擎的插件式管理。这个模块实际上只是 一个抽象类,但正是因为它成功地将各种数据处理高度抽象化,才成就了今天 MySQL 可插拔存储引擎的特色。
从上图还可以看出,MySQL区别于其他数据库的最重要的特点就是其插件式的表存储引擎。MySQL插件式的存储引擎架构提供了一系列标准的管理和服务支持,这些标准与存储引擎本身无关,可能是每个数据库系统本身都必需的,如SQL分析器和优化器等,而存储引擎是底层物理结构的实现,每个存储引擎开发者都可以按照自己的意愿来进行开发。
注意:存储引擎是基于表的,而不是数据库。
SQL 大致的查询流程
mysql 的查询流程大致是:
1.mysql 客户端通过协议与 mysql 服务器建连接, 发送查询语句, 先检查查询缓存, 如果命中, 直接返回结果,否则进行语句解析,也就是说, 在解析查询之前, 服务器会先访问查询缓存(query cache)——它存储 SELECT 语句以及相应的查询结果集。 如果某个查询结果已经位于缓存中, 服务器就不会再对查询进行解析、 优化、 以及执行。 它仅仅将缓存中的结果返回给用户即可, 这将大大提高系统的性能。
2.语法解析器和预处理: 首先 mysql 通过关键字将 SQL 语句进行解析, 并生成一颗对应的“解析树”。 mysql 解析器将使用 mysql 语法规则验证和解析查询; 预处理器则根据一些 mysql 规则进一步检查解析数是否合法。
3.查询优化器当解析树被认为是合法的了, 并且由优化器将其转化成执行计划。 一条查询可以有很多种执行方式,最后都返回相同的结果。 优化器的作用就是找到这其中最好的执行计划。
4.然后, mysql 默认使用的 BTREE 索引, 并且一个大致方向是:无论怎么折腾 sql, 至少在目前来说, mysql 最多只用到表中的一个索引。
MySQL 存储引擎
查看 mysql 支持的存储引擎
show engines;
show variables like ‘%storage_engine%’;
1.Percona为MySQL数据库服务器进行了改进,在功能和性能上较MySQL有着很显著的提升。该版本提升了在高负载情况下的InnoDB的性能、为DBA提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。
2.该公司新建了一款存储引擎叫xtradb完全可以替代innodb,并且在性能和并发上做得更好,阿里巴巴大部分mysql数据库其实使用的percona的原型加以修改。
1.慢 SQL
性能下降、 SQL 慢、执行时间长、等待时间长的原因分析
2、join 查询
SQL 执行顺序
MySQL 实际执行 SQL 顺序
mysql 执行的顺序:随着 Mysql 版本的更新换代, 其优化器也在不断的升级, 优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。下面是经常出现的查询顺序:
总结:mysql 从 FROM 开始执行
JOIN 连接查询
常见的 JOIN 查询图
CREATE TABLE t_dept(
id INT(11) NOT NULL AUTO_INCREMENT,
deptName VARCHAR(30) DEFAULT NULL,
locAdd 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,
deptId INT(11) DEFAULT NULL,
PRIMARY KEY (id),
KEY fk_dept_Id (deptId)
#CONSTRAINT 'fk_dept_Id' foreign key ('deptId') references 'tbl_dept'('Id')
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(deptName,locAdd) VALUES('RD',11);
INSERT INTO t_dept(deptName,locAdd) VALUES('HR',12);
INSERT INTO t_dept(deptName,locAdd) VALUES('MK',13);
INSERT INTO t_dept(deptName,locAdd) VALUES('MIS',14);
INSERT INTO t_dept(deptName,locAdd) VALUES('FD',15);
INSERT INTO t_emp(NAME,deptId) VALUES('z3',1);
INSERT INTO t_emp(NAME,deptId) VALUES('z4',1);
INSERT INTO t_emp(NAME,deptId) VALUES('z5',1);
INSERT INTO t_emp(NAME,deptId) VALUES('w5',2);
INSERT INTO t_emp(NAME,deptId) VALUES('w6',2);
INSERT INTO l_emp(NAME,deptId) VALUES('s7',3);
INSERT INTO t_emp(NAME,deptId) VALUES('s8',4);
INSERT INTO t_emp(NAME,deptId) VALUES('s9',51);
7 种 JOIN 示例
笛卡尔积
t_emp 表和 t_dept 表的笛卡尔乘积:
select * from t_emp,t_dept;
其结果集的个数为:5*7=35
t_emp 表和 t_dept 的交集部分(公共部分)
select * from t_emp e inner join t_dept d on e.deptId = d.id;
2.left join
t_emp 与 t_dept 的公共部分 + t_emp 表的独有部分(t_emp主表,t_dept从表)
left join:取左表独有部分 + 两表公共部分
select * from t_emp e left join t_dept d on e.deptId = d.id;
也就是说查询的时候t_emp是主表,t_dept是从表,把t_emp的数据全部查出来,把t_dept表中满足的数据查出来,不满足的用null补齐,这样的坏处就是容易丢失数据
t_emp 与 t_dept 的公共部分 + t_dept表的独有部分
right join:取右表独有部分 + 两表公共部分(操作和left join相反)
select * from t_emp e right join t_dept d on e.deptId = d.id;
4.left join without common part
tbl_emp 表的独有部分:将 left join 结果集中的两表公共部分去掉即可:where d.id is null
select * from t_emp e left join t_dept d on e.deptId = d.id where d.id is null;
5.right join without common part
tbl_dept表的独有部分:将 right join 结果集中的两表公共部分去掉即可:where e.id is null
select * from t_emp e right join t_dept d on e.deptId = d.id where e.id is null;
mysql 不支持 full join ,但是我们可以通过其他操作实现 full join ,union 关键字用于连接结果集,并且自动去重
t_emp 与 t_dept 的公共部分 + t_emp 表的独有部分 + t_dept表的独有部分:将 left join 的结果集和 right join 的结果集使用 union 合并即可
select * from t_emp e left join t_dept d on e.deptId = d.id union select * from t_emp e right join t_dept d on e.deptId = d.id;
7.full join without common part
t_emp 表的独有部分 + t_dept表的独有部分
select * from t_emp e left join t_dept d on e.deptId = d.id where d.id is null union select * from t_emp e right join t_dept d on e.deptId = d.id where e.id is null;
什么是索引?
1.MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构
2.你可以简单理解为"排好序的快速查找数据结构",即索引 = 排序 + 查找
3.一般来说索引本身占用内存空间也很大,不可能全部存储在内存中,因此索引往往以文件形式存储在硬盘上
4.我们平时所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。
5.聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。
索引原理
将索引理解为
"排好序的快速查找数据结构"
1.在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
左边是数据表,一共有两列七条记录,最左边的十六进制数字是数据记录的物理地址
为了加快col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
索引优势
索引的劣势
MySQL 索引分类
1.普通索引:是最基本的索引,它没有任何限制,即一个索引只包含单个列,一个表可以有多个单列索引;建议一张表索引不要超过5个,优先考虑复合索引
2.唯一索引:与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一
3.主键索引:是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:
4.复合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
5.全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配
MySQL 索引语法
创建索引四种添加表的索引方式:
1.添加主键索引,索引的值必须唯一,并且不能为null
alter table_name add primary key (colunm_list);
2.这条语句创建的索引值必须是唯一的(null除外因为null可能会出现多次)
alter table_name add unique (colunm_list);
3.添加普通索引,索引值可以出现多次
alter table_name add index (colunm_list);
4.该索引为FULL TEXT, 用于全局索引
alter table_name add fulltext (colunm_list);
5.如果是char和varchar类型,length可以小于字段实际长度;
如果是BLOB和TEXT类型,必须指定length。
CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));
ALTER my table ADD [UNIQUE] INDEX [indexName] ON(columnname(length));
删除索引
drop index [indexName] ON mytable;
查看某张表的索引
方式1:
show index from tableName;
方式2:
show index from tableName\G;
(\G表示将查询到的横向表格纵向输出,方便阅读)
MySQL 索引结构
初始化介绍】
1、一颗 b 树, 浅蓝色的块我们称之为一个磁盘块, 可以看到每个磁盘块包含几个数据项(深蓝色所示) 和指针(黄色所示)
2、磁盘块 1 包含数据项 17 和 35, 包含指针 P1、 P2、 P3
3、P1 表示小于 17 的磁盘块, P2 表示在 17 和 35 之间的磁盘块, P3 表示大于 35 的磁盘块
4.、真实的数据存在于叶子节点和非叶子节点中
【查找过程】
1、如果要查找数据项 29, 那么首先会把磁盘块 1 由磁盘加载到内存, 此时发生一次 IO, 在内存中用二分查找确定 29在 17 和 35 之间, 锁定磁盘块 1 的 P2 指针, 内存时间因为非常短(相比磁盘的 IO) 可以忽略不计
2、通过磁盘块 1的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存, 发生第二次 IO, 29 在 26 和 30 之间, 锁定磁盘块 3 的 P2 指针
3、通过指针加载磁盘块 8 到内存, 发生第三次 IO, 同时内存中做二分查找找到 29, 结束查询, 总计三次 IO。
B+tree 索引
B+tree 索引搜索过程
B-树的关键字(数据项)和记录是放在一起的; B+树的非叶子节点中只有关键字和指向下一个节点的索引, 记录只放在叶子节点中。
【B+Tree 与 BTree 的查找过程】
1.在 B 树中, 越靠近根节点的记录查找时间越快, 只要找到关键字即可确定记录的存在; 而 B+ 树中每个记录的查找时间基本是一样的, 都需要从根节点走到叶子节点, 而且在叶子节点中还要再比较关键字。
2.从这个角度看 B 树的性能好像要比 B+ 树好, 而在实际应用中却是 B+ 树的性能要好些。 因为 B+ 树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比 B 树多, 树高比 B 树小, 这样带来的好处是减少磁盘访问次数。
3.尽管 B+ 树找到一个记录所需的比较次数要比 B 树多, 但是一次磁盘访问的时间相当于成百上千次内存比较的时间, 因此实际中B+ 树的性能可能还会好些, 而且 B+树的叶子节点使用指针连接在一起, 方便顺序遍历(范围搜索), 这也是很多数据库和文件系统使用 B+树的缘故
【性能提升】
真实的情况是, 3 层的 B+ 树可以表示上百万的数据, 如果上百万的数据查找只需要三次 IO, 性能提高将是巨大的,如果没有索引, 每个数据项都要发生一次 IO, 那么总共需要百万次的 IO, 显然成本非常非常高。
【思考: 为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?】
B+树的磁盘读写代价更低:B+树的内部结点并没有指向关键字具体信息的指针。 因此其内部结点相对 B 树更小。 如果把所有同一内部结点的关键字存放在同一盘块中, 那么盘块所能容纳的关键字数量也越多。 一次性读入内存中的需要查找的关键字也就越多。 相对来说 IO 读写次数也就降低了。
B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点, 而只是叶子结点中关键字的索引。 所以任何关键字的查找必须走一条从根结点到叶子结点的路。 所有关键字查询的路径长度相同, 导致每一个数据的查询效率相当。
何时需要建索引?
1.主键自动建立唯一索引
2.频繁作为查询的条件的字段应该创建索引
3.查询中与其他表关联的字段,外键关系建立索引
4.频繁更新的字段不适合创建索引
5.Where 条件里用不到的字段不创建索引
6.单间/组合索引的选择问题,Who?(在高并发下倾向创建组合索引)
7.查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度
8.查询中统计或者分组字段
哪些情况不要创建索引?
1.表记录太少
2. 经常增删改的表
3. 数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
案例分析:
1.假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。
2.索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。
2.一个索引的选择性越接近于1,这个索引的效率就越高。
性能分析
性能优化概述
MySQL Query Optimizer 的作用
1.MySQL 中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(MySQL认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
2.当客户端向MySQL 请求一条Query,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQL Query Optimizer时,MySQL Query Optimizer 首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint 或Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。
MySQL 常见瓶颈?
1.CPU 瓶颈:CPU在饱和的时候一般发生在数据装入在内存或从磁盘上读取数据时候
2. IO 瓶颈:磁盘I/O瓶颈发生在装入数据远大于内存容量时
3. 服务器硬件的性能瓶颈:top、free、iostat和vmstat来查看系统的性能状态
Explain 概述
是什么?Explain 是查看执行计划
1.使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是结构的性能瓶颈
2.官网地址:mysql官方文档
explain能干嘛?
如何使用?
explain +sql
1.id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
id 取值的三种情况:
1.id相同,执行顺序由上至下
2. id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3.id相同不同,同时存在:id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行;衍生=DERIVED
2.select_type:查询的类型,主要用于区别普通查询、联合查询、子查询等复杂查询
1.SIMPLE:简单的select查询,查询中不包含子查询或者UNION
2.PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY
3.SUBQUERY:在SELECT或者WHERE列表中包含了子查询
4.DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里
5.UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
6.UNION RESULT:从UNION表获取结果的SELECT
3.table:显示这一行的数据是关于哪张表的
4.type:访问类型排列,显示查询使用了何种类型
type:显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:system>const>eq_ref>ref>fultext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
挑重要的来说:system>const>eq_ref>ref>range>index>ALL
,一般来说,得保证查询至少达到range级别,最好能达到ref。
从最好到最差依次是:system > const>eq_ref > ref > range > index > ALL
1.system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计
2.const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量.
3.eq_ref:唯一性索引,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。
4.ref:非唯一索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
5.range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了 between、<、>、in
等的查询这种范围扫描索引扫描比全表扫描要好,因为他只需要开始索引的某一点,而结束于另一点,不用扫描全部索引。
6.index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘数据库文件中读的)
7.all:FullTable Scan,将遍历全表以找到匹配的行(全表扫描)
注意:一般来说,得保证查询只是达到range级别,最好达到ref
5.possible_keys (可能用到的索引)
6.key(实际所用到的索引)
1.实际使用的索引,如果为null,则没有使用索引
2.若查询中使用了覆盖索引,则该索引仅出现在key列表中
7.key_len
1.表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
2.key_len显示的值为索引最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
key_len的计算方法:
varchr(10)变长字段且允许NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
varchr(10)变长字段且不允许NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)+2(变长字段)
char(10)固定字段且允许NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)
char(10)固定字段且不允许NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)
总结::变长字段需要额外的2个字节,固定长度字段不需要额外的字节。而null都需要1个字节的额外空间。
8.ref
1.显示索引哪一列被使用了,如果可能的话,最好是一个常数。哪些列或常量被用于查找索引列上的值
2.由key_len可知t1表的索引idx_col1_col2被充分使用,t1表的col1匹配t2表的col1,t1表的col2匹配了一个常量,即’ac’
9.rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
10.Extra:包含不适合在其他列中显示但十分重要的额外信息
Using filesort(文件排序):
MySQL中无法利用索引完成排序操作成为“文件排序”
Using temporary(创建临时表):
Using index(覆盖索引):
表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
覆盖索引(Covering Index),也说为索引覆盖
注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select * ,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
Using where:表明使用了where过滤
Using join buffer:表明使用了连接缓存
impossible where:where子句的值总是false,不能用来获取任何元组
select tables optimized away:在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
distinct:优化distinct,在找到第一匹配的元组后即停止找同样值的工作
Explain 热身 Case
第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name …】
第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id, name from t1 where other_column= ’ '】
第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】
第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name, id from t2】
第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的
索引优化
单表索引优化分析
建表 SQL:
CREATE TABLE IF NOT EXISTS article(
id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
author_id INT(10) UNSIGNED NOT NULL,
category_id INT(10) UNSIGNED NOT NULL,
views INT(10) UNSIGNED NOT NULL,
comments INT(10) UNSIGNED NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL
);
INSERT INTO article(author_id,category_id,views,comments,title,content)
VALUES
(1,1,1,1,'1','1'),
(2,2,2,2,'2','2'),
(1,1,3,3,'3','3');
查询案例:
查询category_id为1且comments 大于1的情况下,views最多的article_id。
SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
1.此时 article 表中只有一个主键索引
explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
很显然,type是ALL,即最坏的情况。
Extra 里还出现了Using filesort,也是最坏的情况,优化的空间是很大的。
开始优化方案1:
新建索引:create index idx_article_ccv on article(category_id, comments, views);
再次执行查询:type变成了range,这是可以忍受的。但是extra里使用Using filesort仍是无法接受的,还可以继续优化。
优化结果分析:
但是我们已经建立了索引,为啥没用呢?
1.这是因为按照B+Tree索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序comments,如果遇到相同的 comments 则再排序 views。
2.当comments字段在联合索引里处于中间位置时,因为comments>1条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的views部分进行检索,即 range 类型查询字段后面的索引无效。
3.将查询条件中的 comments > 1 改为 comments = 1 ,发现 Use filesort 神奇地消失了,从这点可以验证:范围后的索引会导致索引失效
删除刚刚所键的索引:drop index idx_article_ccv on article;
再次创建索引: create index idx_article_cv on article(category_id,views);
建立好索引,重新查询后的结果是非常满意的,比之前的好多了,建立合适的索引,能极大提高我们sql的性能。
两表索引优化:
两表索引优化分析:主外键
建表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 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)));
8
查询案例:先连接两张表,查看表中的数据
select b.bookid,b.card,c.card,c.id from book b inner join class c on b.card=c.card ;
使用 explain 分析 SQL 语句的性能(class是主表,book是从表)
sql:explain select * from class left join book on class.card = book.card;
结果分析:
1.type 有 All ,rows 为表中数据总行数,说明 class 和 book 进行了全表检索
2. 即每次 class 表对 book 表进行左外连接时,都需要在 book 表中进行一次全表检索
1.先个book表的card字段建立一个索引
create index idx_book_c on book(card);
执行sql: explain select * from class left join book on class.card = book.card;
2.删除掉index_book_c索引,给class表建立 idx_class_c 索引
键索引命令: create index idx_class_c on class(card);
执行sql: explain select * from class right join book on class.card = book.card;
1.也就是不管是左连接还是有链接,主表是会全表扫描的,但是从表被连接的字段一定要建立索引。
2.两张表,不管是索引建立在哪一张表上,我们都可以使用left或right进行一个怕匹配,遵从一个原则就是,有索引的表为从表,没有索引的表为主表就可以了。
如果我们主表建立索引,而从表没有的话,那这个索引建立的就没有任何意义(在大表上建立索引)。
比如:现在class是建立有索引,而book表没有建立索引,我们让class成为主表,book表成为从表,我们执行以下sql,查看以下结果。
执行结果分析:虽然使用到了索引,但是扫描的函数明显增多了很多。
我们现在改为有链接,让book成为主表,class为从表,执行sql,并且加以分析:
当然我这里还测试了一下,两张表都建立索引的情况,但是执行的结果是差不多的,更何况建立不适合的索引,也会消耗数据库的性能,维护起来也复杂,所以我们还是遵循以上的规则进行建立索引。
三张表优化案例:
三表索引优化分析:
建表sql:
CREATE TABLE IF NOT EXISTS phone(
phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(phoneid)
)ENGINE=INNODB;
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
先三张表连接查询,查看表中的数据;
sql :
select c.id,b.bookid,p.phoneid,c.card,b.card,p.card from class c left join book b on c.card=b.card left join phone p on b.card=p.card;
加上 explain 进行分析:(没有建立任何索引)
结果分析:
分别给book表和phone表建立索引后再次执行查询分析:
book表建立索引:
create index inx_book_c on book(card);
phone表建立索引:
create index inx_phone_c on phone(card);
执行sql:
explain select c.id,b.bookid,p.phoneid,c.card,b.card,p.card from class c left join book b on c.card=b.card left join phone p on b.card=p.card;
通过建立索引之后,再次执行sql进行分析得到结果,建立索引和不建立索引的区别很大,查询速度有了飞越性的提升。
join 连接查询优化总结: 用小表驱动大表(也就是说,如果有两个表,一个图书表,一个图书类别表,让图书类别表为主表,图书表为从表(在图书表上建立索引),让数据量小的一个表成为主表去带大表)
索引失效
创建表sql:
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('z3',22,'manager',NOW());
INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES('July',23,'dev',NOW());
INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES('2000',23,'dev',NOW());
create index idx_staffs_NameAgePos on staffs(name,age,pos);
1.全值匹配我最爱
3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
4.存储引擎不能使用索引中范围条件右边的列
5.尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
6.mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
7.is null,is not null 也无法使用索引(早期版本不能走索引,后续版本应该优化过,可以走索引)
8.like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描操作
9.字符串不加单引号索引失效
10.少用or,用它连接时会索引失效
最佳左匹配法则:带头大哥不能死,中间兄弟不能断
只有带头大哥 name 时
explain select id, name,age,add_time from staffs where name="July";
带头大哥 name 带上小弟 age
explain select id, name,age,add_time from staffs where name="July" and age=23 ;
explain select id, name,age,add_time from staffs where name="July" and age=23;
explain select id, name,age,add_time from staffs where age=23 and pos="dev";
在索引列上进行计算,会导致索引失效,进而转向全表扫描
对带头大哥 name 进行操作:使用 LEFT 函数截取子串
key = NULL 表明索引生效
type = ALL 表明进行了全表扫描
范围匹配之后全失效
explain select * from staffs where name="July" and age>23 and pos="dev";
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少 select *
SELECT * 的写法
explain select * from staffs where name=“July” and age>23 and pos=“dev”;
覆盖索引的写法:Extra = Using where; Using index ,Using index
表示使用索引列进行查询,将大大提高查询的效率
explain select name,age,pos from staffs where name="July" and age=23 and pos="dev";
覆盖索引中包含 range 条件:type = ref 并且 Extra = Using where; Using index ,虽然在查询条件中使用了 范围搜索,但是由于我们只需要查找索引列,所以无需进行全表扫描.
explain select name,age,pos from staffs where name="July" and age>23 and pos="dev";
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
在使用 != 会 <> 时会导致索引失效:
explain select * from staffs where name!="July" ;
explain select name,age,pos from staffs where name!="July" ;
is null,is not null 会导致索引失效:key = null 表示索引失效
explain select id,name,age,pos,add_time from staffs where name is not null ;
ike % 写在左边的情况
explain select * from staffs where name like "%J";
ike % 写在右边的情况
explain select * from staffs where name like "_u%";
建表sql:
CREATE TABLE `tbl_user`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age`INT(11) DEFAULT NULL,
`email` VARCHAR(20) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('1aa1',21,'[email protected]');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('2bb2',23,'[email protected]');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('3cc3',24,'[email protected]');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('4dd4',26,'[email protected]');
添加复合索引
create index idx_user_NameAge on tbl_user(name, age);
执行:explain select id, name ,age from tbl_user where name like "%aa%";
可以看到,我们利用覆盖索引的方式可以解决 左右 % like的情况,
注意点: 覆盖索引,当查询的字段,超出我们的符合索引字段的时候,覆盖索引依旧会失效
我们在查询的字段中增加email字段之后,然后在看执行结果
explain select name ,age ,email from tbl_user where name like "%aa%";
可以看到type 为 all key=null 索引直接就失效了,如果想不失效,建立复合索引的时候,email字段也得加上,所谓的覆盖就是必须在复合索引字段的范围之内,超出那个肯定覆盖不了,索引会直接失效
select * 也不行 会直接的导致索引失效
如果字符串忘记写 ‘’ ,那么 mysql 会为我们进行隐式的类型转换,但凡进行了类型转换,索引都会失效
explain select * from staffs where name = 2000;
少用or,用它连接时会索引失效
explain select * from staffs where name = '2000' or name="July";
sql:优化口诀
全值匹配我最爱, 最左前缀要遵守;
带头大哥不能死, 中间兄弟不能断;
索引列上少计算, 范围之后全失效;
LIKE 百分写最右, 覆盖索引不写 *;
不等空值还有 OR, 索引影响要注意;
VAR 引号不可丢, SQL 优化有诀窍。
索引优化面试题:
建表sql:
create table Tone(
id int primary key not null auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10)
);
insert into Tone(c1,c2,c3,c4,c5) values ('a1','a2','a3','a4','a5');
insert into Tone(c1,c2,c3,c4,c5) values ('b1','b2','b3','b4','b5');
insert into Tone(c1,c2,c3,c4,c5) values ('c1','c2','c3','c4','c5');
insert into Tone(c1,c2,c3,c4,c5) values ('d1','d2','d3','d4','d5');
insert into Tone(c1,c2,c3,c4,c5) values ('e1','e2','e3','e4','e5');
create index idx_Tone_c1234 on Tone(c1,c2,c3,c4);
1.全值匹配:
1.
explain select * from Tone where c1="a1" and c2="a2" and c3="a3" and c4="a4";
2.mysql内部优化器,进行sql优化,索引全部生效(但是最好是怎么建的怎么用,避免mysql底层第二次优化翻译,需要全是常量)
也就是说只要复合索引里面的字段都写全,无论顺序怎么样,索引都不会失效,因为mysql优化器会对我们的sql进行优化解析用到我们的索引,但是如果中间索引断裂,会导致后面的索引失效。
explain select * from Tone where c4="a1" and c3="a3" and c2="a2" and c1="a1";
3.
explain select * FROM Tone where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
c3 列使用了索引进行排序,并没有进行查找,导致 c4 无法用索引进行查找,导致c4索引失效(范围之后全失效)
explain select * from Tone where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
这里使用到了4个索引,因为mysql优化器对于常量值可以,进行位置调换,C4用于排序
select * from Tone where c1='a1' and c2='a2' and c4='a4' order by c3;
这里用到了两个常量值索引,然后实际上 c3也用到了,只不过用于排序去了
explain select * from Tone where c1='a1' and c2='a2' order by c3;
explain select * from Tone where c1='a1' and c2='a2' order by c4;
因为索引断裂,而又需要排序,索引直接导致,mysql底层调用了文件排序
8
.explain select * from Tone where c1='a1' and c2='a2' and c3="a4" order by c1;
如果排序的字段是前面的常量值就没有问题,因为在查找之前已经拍好了序
explain select * from Tone where c1="a1" and c5="a5" order by c2 ,c3;
用到了c1字段用于查找,c2,c3用于排序了,就没有filesort了,但是如果索引断裂的话,order by 后面跟的是 c3 c2的话 就会出现 filesor t(我猜想的估计是前面c1,已经拍好序了,所以我们使用c2,c3的时候是接着索引走的,所以不会出现filesort ,因为索引就是排序+查找,这样理解有有点说的通)。
explain select * from Tone where c1='a1' and c2='a2'order by c2, c3;
排序和查找都按照索引的顺序来,c1和c2用于查找,c3 用于排序
explain select * from Tone where c1='a1' and c2='a2' and c5='a5' order by c2, c3;
c1和c2用于查找,c2和c3用于排序,和c5没有任何关系
多了 a4 索引会断裂,索引a4就走不到索引,但是后面order by 会继续走的索引排序的, 因为前面的c2常量值已经进行了排序。
explain select * from Tone where c1='a1' and c2='a2' and c5='a5' order by c3, c2;
因为前面常量值 c2 已经排序,所以后面 c3 ,c2 不会出现filesort
13.
explain select * from Tone where c1='a1' and c4='a4' group by c2, c3;
因为c1 是一个常量值,在查找之前会进行一个排序,然后group by 其实也就是排序之后的操作,也就是说 c1 已经拍好序,后面的c2 c3 进行分组之前进行的排序,跟c1的索引是没有断裂,不会产生filesort
explain select * from Tone where c1='a1' and c4='a4' group by c3, c2;
这个因为c1虽然进行了排序,但是后面的排序字段是从c3开始,跟c1就接不上了,所以会直接产生文件内排序。
总结:
group by 分组之前比排序,如果顺序错乱,就会导致临时表的产生,定值为常量、范围之后失效,最终看排序的顺序
一般性建议:
1.在对于单键索引,进来针对当前query过滤性更好的索引
2.在选择组合索引的时候,当前query中过滤性最好的字段在索引字段中,位置越靠左越好。
3.在选择组合索引的时候,进量选择能够包含当前query中的where字段中更过的索引字段
4.尽可能通过分析统计信息和跳转query的写法来达到选择合适的索引。
MySQL 优化原则
exists(小表驱动大表)
EXISTS 语法:
1.SELECT … FROM table WHERE EXISTS(subquery)
该语法可以理解为:将查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。
2.EXISTS(subquery) 只返回TRUE或FALSE,因此子查询中的SELECT *也可以是SELECT 1或其他,官方说法是实际执行时会忽略SELECT清单,因此没有区别
3.EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
4.EXISTS子查询往往也可以用条件表达式、其他子查询或者JOIN来替代,何种最优需要具体问题具体分析
in的用法
建表sql:
create table tblA(
#id int primary key not null auto_increment,
age int,
birth timestamp not null
);
insert into tblA(age, birth) values(22, now());
insert into tblA(age, birth) values(23, now());
insert into tblA(age, birth) values(24, now());
create index idx_A_ageBirth on tblA(age, birth);
能使用索引排序的情况:
带头大哥 age在前面
explain select * from t_order order by age asc, birth asc;
explain select * from t_order order by age desc, birth desc
explain select * from t_order order by age desc, birth asc
;
索引失效,当age带头大哥不存在的时候:
explain select * from t_order where age>20 order by birth,age;
explain select * from t_order order by birth;
order by 结论:
MySQL支持二种方式的排序,FileSort和Index,Index效率高,它指MySQL扫描索引本身完成排序,FileSort方式效率较低。
ORDER BY满足两情况(最佳左前缀原则),会使用Index方式排序:
尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
如果未在索引列上完成排序,mysql 会启动 filesort 的两种算法:双路排序和单路排序
双路排序
单路排序
结论及引申出的问题:
更深层次的优化策略:
Order By 排序索引优化的总结
group by关键字优化
慢查询日志
慢查询日志是什么?
慢查询日志开启
说明:
查看是否开启及如何开启?
查看慢查询日志是否开启:
show variables like "%slow_query_log%";
查看 mysql 的慢查询日志是否开启如果要永久生效,就必须修改配置文件my.cnf(其它系统变量也是如此)
修改my.cnf文件,[mysqld]下增加或修改参数:slow_query_log和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件
[mysqld]
slow_query_log =1
slow_query_log_file=/var/lib/mysql/Heygo-slow.log
关于慢查询的参数slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)
那么开启慢查询日志后,什么样的SQL参会记录到慢查询里面?
这个是由参数 long_query_time 控制,默认情况下long_query_time的值为10秒,命令:show variables like "long_query_time";
查看慢 SQL 的阈值
可以使用命令修改,也可以在my.cnf参数里面修改。
假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,在mysql源码里是判断大于long_query_time,而非大于等于。
慢查询日志示例
查看慢 SQL 的阈值时间,默认阈值时间为 10s
show variables like 'long_query_time';
设置慢 SQL 的阈值时间,我们将其设置为 3s
set global long_query_time=3;
查看全局的 long_query_time 值:show global variables like 'long_query_time';
查看慢查询日志的输出方式: SELECT @@global.log_output
查看慢查询的状态: show global status like '%slow%';
配置版的慢查询日志
在 /etc/my.cnf 文件的 [mysqld] 节点下配置
配置版本:
slow_query_log=1;
slow_query_log_file=/www/server/data/mysql-slow.log
long_query_time=3;
log_output=FILE
注意:如果修改了mysql的配置文件,记得重启mysql服务,不然不会生效。
日志分析命令 mysqldumpslow
mysqldumpslow是什么?
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。
mysqldumpslow --help 命令无法使用的解决办法:
1.找到mysql的安装目录下bin
2.创建软连接:ln -s /www/server/mysql/bin/mysqldumpslow /usr/bin
查看 mysqldumpslow的帮助信息: mysqldumpslow --help
常用参数手册
得到返回记录集最多的10个SQL
- mysqldumpslow -s r -t 10 /www/server/data/mysql-slow.log
得到访问次数最多的10个SQL
- mysqldumpslow -s c- t 10 /www/server/data/mysql-slow.log
得到按照时间排序的前10条里面含有左连接的查询语句
- mysqldumpslow -s t -t 10 -g “left join” /www/server/data/mysql-slow.log
另外建议在使用这些命令时结合 | 和more使用,否则有可能出现爆屏情况
- mysqldumpslow -s r -t 10 /www/server/data/mysql-slow.log | more
批量数据脚本
部门表:dept
CREATE TABLE dept
(
deptno int unsigned primary key auto_increment,
dname varchar(20) not null default "",
loc varchar(8) not null default ""
)ENGINE=INNODB DEFAULT CHARSET=utf8;
员工表:emp
CREATE TABLE emp
(
id int unsigned primary key auto_increment,
empno mediumint unsigned not null default 0,
ename varchar(20) not null default "",
job varchar(9) not null default "",
mgr mediumint unsigned not null default 0,
hiredate date not null,
sal decimal(7,2) not null,
comm decimal(7,2) not null,
deptno mediumint unsigned not null default 0
)ENGINE=INNODB DEFAULT CHARSET=utf8;
设置参数
创建函数,假如报错:This function has none of DETERMINISTIC………
由于开启过慢查询日志,因为我们开启了bin-log,我们就必须为我们的function指定一个参数。
log_bin_trust_function_creators = OFF
,默认必须为 function 传递一个参数
查看命令:show variables like 'log_bin_trust_function_creators';
通过:set global log_bin_trust_function_creators=1;
我们可以不用为 function 传参
这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法在配置文件中修改‘
windows下:my.ini --> [mysqld] 节点下加上 :
log_bin_trust_function_creators=1
linux下:/etc/my.cnf --> [mysqld] 节点下加上:log_bin_trust_function_creators=1
创建函数,保证每条数据都不同
随机产生字符串的函数
delimiter $$ # 两个 $$ 表示结束
create function rand_string(n int) returns varchar(255)
begin
declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyz';
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 $$
随机产生部门编号的函数:
delimiter $$
create function rand_num() returns int(5)
begin
declare i int default 0;
set i=floor(100+rand()*10);
return i;
end $$
创建存储过程
创建往emp表中插入数据的存储过程
delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i+1;
insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values((start+i),rand_string(6),'salesman',0001,curdate(),2000,400,rand_num());
until i=max_num
end repeat;
commit;
end $$
创建往dept表中插入数据的存储过程
delimiter $$
create procedure insert_dept(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i+1;
insert into dept(deptno,dname,loc) values((start+i),rand_string(10),rand_string(8));
until i=max_num
end repeat;
commit;
end $$
调用存储过程
向 dept 表中插入 10 条记录
DELIMITER ;
CALL insert_dept(100, 10);
emp 表中插入 50w 条记录
DELIMITER ;
CALL insert_emp(100001, 500000);
Show Profile
分析步骤:
查看是当前的SQL版本是否支持: show profile;
查看 Show Profile 是否开启 : show variables like "profiling%";
开启功能 Show Profile ,默认是关闭,使用前需要开启
开启 Show Profile :
set profiling=on;
运行SQL:
正常 SQL
select * from t_emp;
select * from t_emp e inner join t_dept d on e.deptId = d.id;
select * from t_emp e left join t_dept d on e.deptId = d.id;
慢 SQL
select * from emp group by id%10 limit 150000;
select * from emp group by id%10 limit 150000;
select * from emp group by id%20 order by 5;
通过 show profiles;
指令查看执行了哪些sql
查看 SQL 语句执行的具体流程以及每个步骤花费的时间
show profile cpu, block io for query SQL编号;
日常开发需要注意的结论
全局查询日志
永远不要在生产环境开启这个功能。
配置启用全局查询日志
在mysql的my.cnf中,设置如下:
开启
general_log=1
记录日志文件的路径
general_log_file=/path/logfile
输出格式
log_output=FILE
编码启用全局查询日志
set global general_log=1;
set global log_output='TABLE';
此后,你所执行的sql语句,将会记录到mysql库里的general_log表,可以用下面的命令查看
select * from mysql.general_log;
锁的定义
锁的分类
从数据操作的类型(读、写)分
从对数据操作的颗粒度
表锁:
表锁的特点:偏向MyISAM存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发最低。
表锁案例分析:
建表 SQL:引擎选择 myisam( 偏读操作 )
create table mylock (
id int not null primary key auto_increment,
name varchar(20) default ''
) engine myisam;
insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
select * from mylock;
手动加锁和释放锁
查看当前数据库中表的上锁情况:
show open tables;
In_use= 0 表示未上锁,1表锁已上锁
添加锁: lock table 表名1 read(write), 表名2 read(write);
释放表锁: unlock tables;
读锁示例:
session 1 会话中,给 mylock 表加个读锁
为了更好的演示,这里我开启两个终端分别是session1和session2
lock table mylock read;
select * from mylock;
(ok 的)select * from mylock;
(ok 的)update mylock set name="a2" where id=1;
(不 ok)select * from book;
(不 ok)select * from book;
(ok 的)读锁结论:
写锁示例
lock table mylock write;
案例总结:
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
结论:结合上表,所以对MyISAM表进行操作,会有以下情况:
表锁分析:
查看哪些表被锁了,0 表示未锁,1 表示被锁
show open tables;
【如何分析表锁定】
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定,通过 show status like 'table%';
命令查看
行锁的特点(InnoDB:编写)
事务(Transation)及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
并发事务处理带来的问题
事物的隔离级别
show variables like 'tx_isolation';
mysql 默认是可重复读行锁案例:
建表sql:
CREATE TABLE innodb_lock (a INT(11),b VARCHAR(16))ENGINE=INNODB;
INSERT INTO innodb_lock VALUES(1,'b2');
INSERT INTO innodb_lock VALUES(3,'3');
INSERT INTOinnodb_lock VALUES(4, '4000');
INSERT INTO innodb_lock VALUES(5,'5000');
INSERT INTO test_innodb_lock VALUES(6, '6000');
INSERT INTO innodb_lock VALUES(7,'7000');
INSERT INTO innodb_lock VALUES(8, '8000');
INSERT INTO innodb_lock VALUES(9,'9000');
INSERT INTO innodb_lock VALUES(1,'b1');
CREATE INDEX test_innodb_a_ind ON innodb_lock(a);
CREATE INDEX test_innodb_lock_b_ind ON innodb_lock(b);
操作同一行数数据:
同时打开两个命令窗口 session1和session2
1.session1执行和session2:set autocommit=0;
2.session1执行: update innodb_lock set b="4000" where a=4;
3.session1执行:查询innodb_lock中的数据,可以看到已经被修改(读己之所写)
4.session2执行: 查询innnodb_lock表中的数据,是查询不到的,因为session1还没有提交
5.此时session2执行: update innodb_lock set b="4005" where a=4;
会直接阻塞,只有的session1提交事务以后,session2的更新操作才能被执行。
操作同一行数据总结:也可就是另外一个事务在没有提交之前,另外一个事务是读取不到修改后的数据的,如果两个事务同时操作同一行数据,必须等另外一方提交事务后方可执行,否则会发生阻塞状态。当前事务修改数据后,没有提交,只有自己能看到被修改的数据,需要提交之后,才能被别的事务所查询到。
操作不同行数据
事务1和事务2操作同表不同行的数据互不干扰。但是得等到提交事务之后别的事务才能查询到修改后的正确事务。
例: session1去修改第三行数据,session2修改第二行数据,两者都可以进行修改成功,互不干扰
> 无索引导致行锁升级为表锁
update innodb_lock set a=4 where b=4000;
间隙锁
什么是间隙锁?
间隙锁的危害
因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害
间隙锁示例:
session1 开启事务,执行修改 a > 1 and a < 6 的数据,这会导致 mysql 将 2~5 的数据行锁住(虽然表中并没有这行数据)
session2 开启事务,修改 test_innodb_lock 中不同行的数据,也会导致阻塞,直至 session1 提交事务
insert into innodb_lock values(2,“2000”);
如何手动锁定一行?
select xxx … for update 锁定某一行后,其它的操作会被阻塞,直到锁定行的会话提交
行锁结论分析
如何分析行锁定
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%';
参数说明如下:(标记为红色的为重点注意信息)
Innodb_row_lock_time:
从系统启动到现在锁定总时间长度;Innodb_row_lock_time_avg:
每次等待所花平均时间;Innodb_row_lock_waits:
系统启动后到现在总共等待的次数;行锁优化建议
页锁
复制的基本原理
slave会从master读取binlog来进行数据同步,主从复制的三步骤
master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件(binary log events)
slave将master的binary log events拷贝到它的中继日志(relay log)
每个slave只有一个master
每个slave只能有一个唯一的服务器ID
每个master可以有多个salve
复制最大问题: 因为发生多次 IO, 存在延时问题
如何查找windows下的mysql.ini文件存储在哪儿?
查看mysql的安装目录
select @@basedir ;
查看mysql的配置文件位置(mysql.ini就在Data目录下):selec @@tdatadir;
查看mysql临时目录文件位置:show variables like "tmpdir";
实现主从复制:windows5.7.32(主机) linx5.7.32(从机)
两台服务器的mysql数据库版本最好是一直,因为之前用的是8.0和5.7做主从复制的时候是成功了,但是数据不能同步,现在改为5.7之后就成功了。
实现之前配置文件准备工作
windows主机配置文件(my.ini)
【mysqld】
# 开启二进制
log-bin=master-bin
# mysql 字符集
character-set-server=utf8
collation-server=utf8_general_ci
init_connect='SET NAMES utf8'
# 数据库默认存储引擎
default-storage-engine=INNODB
# 错误日志文件位置
log-error="C:/ProgramData/MySQL/MySQL Server 5.7/Data/mysqlerror"
# mysql二进制文件存放位置
log-bin = C:/ProgramData/MySQL/MySQL Server 5.7/Data/mysqlbin
# 临时表存放位置
tmpdir="C:/ProgramData/MySQL/MySQL Server 5.7/Data/tmp_table/"
# 是否为只读
read-only=0
# 不备份的数据库
binlog_ignore_db=mysql
binlog_ignore_db=sys
# 设置需要备份的数据库
binlog-do-db=student
# 主机的serverid 从机的serverid不能和主机的相同
server-id=1
linux 配置文件(my.cnf)
# 为了方便记忆,也可以去ip地址的最后一个网段
server-id=3
修改完两个配置文件,都需要重启mysql服务
查看二进制日志文件是否开(必须开启): show variables like '%log_bin%';
linux : service mysqld restart
windows: net restart mysql57
1.主机创建用户并且授权(我这里直接给的是全部权限,建议生产环境不建议这样做)
create user 'admin'@'%' identified by 'admin';
grant all privileges on *.* to 'admin'@'%';
2.查看用户是否创建成功
select user,host ,plugin from mysql.user;
3.在linux终端登录mysql看看是否能够成功登录 (如果不能成功登录多半会失败)
mysql -h windows主机ip -p3306 -u admin -p
MYSQL连接报错:Authentication plugin ‘caching_sha2_password‘ cannot be loaded
解决办法:
先执行: grant system_user on *.* to 'root';
然后执行:因为mysql的版本不一致,导致验证密码的方式不一样 所以我们需要重新指定
ALTER USER 'admin'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
ALTER USER 'admin'@'%' IDENTIFIED BY 'admin';
FLUSH PRIVILEGES;
set global binlog_checksum='NONE';
show variables like '%checksum%';
执行完后刷新权限: flush privileges;
4.查看主机的状态 show master status;
在 Linux 从机上配置需要复制的主机
1.从机进行认证
CHANGE MASTER TO
MASTER_HOST='windows主机ip',
MASTER_USER='admin',
MASTER_PASSWORD='admin',
MASTER_LOG_FILE='mysqlbin.000008',
MASTER_LOG_POS= 1785;
2.启动从服务器复制功能 start slave;
3.查看从机复制功能是否启动成功:Slave_SQL_Running:Yes 和 Slave_IO_Running:Yes
说明从机连接主机成功
命令:show slave status\G
注意,不能加上 ;号
4.如何停止从服务复制功能 stop slave;
以上就是我对mysql高级部分的学习,希望有帮助到各位朋友,如果中途出现什么问题,建议百度,我也是出了很多问题,但是我出现的问题大多数网友已经给出了解决方案。