你真的了解MySQL了吗,那你给我说一下锁机制!
Posted XiaoLin__Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你真的了解MySQL了吗,那你给我说一下锁机制!相关的知识,希望对你有一定的参考价值。
一、mysql 的用户和权限管理
1.1、MySQL的用户管理
1.1.1、相关命令
命令 | 描述 | 备注 |
---|---|---|
create user xiaolin identified by ‘123456’; | 创建名称为xiaolin的用户,密码为123456 | |
select host,user,password,select_priv,insert_priv,drop_priv from mysql.user; | 查看用户和权限的相关信息 | |
set password = password(‘123456’) | 修改其他用户的密码 | 所有通过 user 表的修改,必须用 flush privileges;命令才能生效 |
update mysql.user set user = ‘zs’ where user = ‘xiaolin’ | 修改用户名 | 所有通过 user 表的修改,必须用 flush privileges;命令才能生效 |
drop user xiaolin | 删除用户 | 不要通过delete from user where user = 'xiaolin’进行删除,系统会有残留信息保存 |
1.1.2、详解
host
host表示连接类型。
- %:表示所有远程通过TCP方式连接。
- 127.0.0.1:通过指定IP进行的TCP方式的连接。
- 机器名:通过指定网络中的机器名进行的TCP方式的连接。
- ::1:IPV6的本地IP地址,等同于IPV4的127.0.0.1
- localhost 本地方式通过命令行方式的连接 ,比如 mysql -u xxx -p 123xxx 方式的连接。
user
user表示用户名,同一用户通过不同方式连接的权限是不一样的。
password
这里显示的是明文密码通过哦MYSQLSHA1加密算法加密后得到的密文密码,是不可逆的,mysql 5.7 的密码保存到 authentication_string 字段中不再使用 password 字段。
select_priv , insert_priv等
表示该用户所拥有的权限。
1.2、MySQL的权限管理
1.2.1、授予权限
我们可以通过命令来授予用户权限,该权限如果发现没有该用户,则会直接新建一个用户。
格式为:
grant 权限 1,权限 2,…权限 n on 数据库名称.表名称 to 用户名@用户地址 identified by‘连接口令
示范:
# 给 xiaolin 用户用本地命令行方式下,授予 user 这个库下的所有表的插删改查的权限。
grant select,insert,delete,drop on user.* to xiaolin@localhost;
# 授予通过网络方式登录的的 xiaolin 用户 ,对所有库所有表的全部权限,密码设为 123.
grant all privileges on *.* to xiaolin@'%' identified by '123';
1.2.2、查看权限
我们可以使用命令来查看当前用户的权限。
show grants;
1.2.3、收回权限
我们可以使用命令来收回用户的权限,权限收回后,必须用户重新登录后,才能生效。
revoke
[权限 1,权限 2,…权限 n] on 库名.表名 from 用户名@用户地址 ;
示范:
# 收回全库全表的所有权限
REVOKE ALL PRIVILEGES ON mysql.* FROM joe@localhost;
# 收回 mysql 库下的所有表的插删改查权限
REVOKE select,insert,update,delete ON mysql.* FROM xiaolin@localhost;
二、MySQL逻辑架构
2.1、整体架构图
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
2.2、MySQL分层
MySql大概分为四层:
2.2.1、连接层
最上层是一些客户端和连接服务,包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于 SSL 的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2.2.2、服务层
服务层提供各种用户使用的接口,同时提供SQL优化器,对用户传进来的SQL语句进行优化。他有几个组件。
组件名 | 描述 |
---|---|
Management Serveices & Utilities | 系统管理和控制工具 |
SQL Interface | SQL 接口。接受用户的 SQL 命令,并且返回用户需要查询的结果。比如 select from就是调用 SQL Interface |
Parser | 解析器。 SQL 命令传递到解析器的时候会被解析器验证和解析 |
Optimizer | 查询优化器。SQL 语句在查询之前会使用查询优化器对查询进行优化,比如有where 条件时,优化器来决定先投影还是先过滤。 |
Cache 和 Buffer | 查询缓存。如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key 缓存,权限缓存等 |
2.2.3、引擎层
存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
2.2.4、存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
2.3、MySQL的查询流程
mysql 的查询流程大致是:
- MySQL客户端通过协议与MySQL服务器建立连接,发送查询语句。
- 服务器收到了查询语句之后,会先去检查查询缓存,这个缓存存储了SELECT语句以及相应的查询结果集。如果命中(查询结果已经位于缓存中)服务器就不会再对查询进行解析、优化、执行,他做的仅仅是将缓存中的结果直接返回给用户,大大提升了性能。如果没有命中缓存的话,将会进行第三步。
- 接下来进入的是语法解析器和预处理,首先MySQL通过关键字将SQL进行解析,并生成一颗对应的解析树。MySQL解析器将使用MySQL语法规则验证解析查询,预处理器则根据一些MySQL规则进一步检查解析是否合法。
- 接着到了查询优化器,解析树由优化器转化为执行计划。一条查询可以有很多种执行方式,但是最后都返回相同的结果,优化器的作用就是找到其中最好的执行计划。
- 最后交有存储引擎进行执行返回给客户端。
2.4、MySQL的执行顺序
我们手写一条完整的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 >
然而它的执行顺序是这样的.。
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.5、MyISAM 和 InnoDB
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整张表,不适合高并发操作 | 行锁,操作时只锁住某一行,不对其他行有影响,适合高并发操作 |
缓存 | 只缓存索引不缓存真实数据 | 不仅仅缓存索引还缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
三、索引
3.1、索引的概述
3.1.1、索引是什么
MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。可以简单理解为排好序的快速查找数据结构。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
左边是数据表,一共是两列七条数据,最左边的是数据记录的物理地址。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
3.2、索引的优缺点
3.2.1、优点
- 提高数据检索的效率,降低数据库的IO成本。
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
3.2.2、缺点
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
3.2、Btree 索引
3.2.1、初始化介绍
MySQL 使用的是 Btree 索引。一颗B树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示)。
磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3,P1 表示小于 17 的磁盘块,P2 表示在 17 和 35 之间的磁盘块,P3 表示大于 35 的磁盘块。
真实的数据存在于叶子节点即 3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只是为了作为分隔线,存储指引搜索方向的数据项,如 17、35 并不真实存在于数据表中。
3.2.2、查找过程
如果要查找数据项 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,显然成本非常非常高。
3.3、B+tree 索引
我们可以看到BTree结构图中,每个节点不仅仅包含key值,同时还有data值,每一个页的存储空间是有限的,如果data数据较大时会导致每个节点(即每一个页)能存储的key数量很小,当存储的数据量很大的时候会导致BTree的深度很深,增大了查询时磁盘I/O的次数。
而在B+Tree中,所有数据记录节点都是按照键值大小顺序放在同一层的叶子节点上,而非叶子节点只存储key信息,这样可以大大增加每个节点存储的key值的数量,降低B+Tree的高度。
实际情况中每个节点可能不能填满,在数据库中,B+Tree的高度一般在24层之间,MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的(不动用磁盘I/O,直接上内存找),也就是说查找某一键值行记录时最多只需要13次磁盘I/O操作。
3.4、B+Tree 与 B-Tree 的区别
- B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
- 在 B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而 B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看好像B-树的性能1是要比B+树的性能要高,而在实际应用中确实B+树的性能更好一些。因为B+树的非叶子节点不存放实际的数据,这样每个11节点1可容纳的元素个数比B-树多,但是依次磁盘访问的时间相当于成千上百次内存比较的时间,因此在实际中B+树性能更好,而且B+树的叶子节点1是使用指针连接在一起,方便顺序遍历。
3.5、为什么B+树比 B树更适合实际应用
- B+树的磁盘读写代价更低:B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说 IO 读写次数也就降低了。
- B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
3.6、聚簇索引和非聚簇索引
- 聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据,聚簇索引默认是主键。
- 非聚簇索引:也称为辅助索引,将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置。非聚簇索引存储的不再是行的物理位置,而是主键值,辅助索引访问数据总是需要二次查找。不存储物理位置的原因是当数据发生增删改的时候,物理位置可能会发生改变,万一发生了改变,那么还需要维护非聚簇索引。
- 聚簇索引查找:将主键组织到一个B+Tree树中,而行数据就存储在叶子节点上,若使用’where id = 14’这样的条件查找主键时,则按照B+Tree的检索算法即可查找到对应的叶子节点,从而获得行数据。
- 若对name(辅助索引)列进行条件搜索则需要两个步骤:
- 第一步先在辅助索引B+Tree中检索name,到达其叶子节点对应的主键。
- 使用主键在主索引B+Tree中再执行一次B+Tree检索操作,最终到达叶子节点即可获取整行数据。
3.6.1、聚簇索引的好处
- 由于行数据和聚簇索引的叶子节点存储在一起,同一页中会有多条行数据,访问统一数据页不同记录时,已经把页加载到了buffer(缓存器),再次访问时,会在内存中完成访问,不必访问磁盘。这样主键和行数据时一起载入内存的,找到叶子节点就可以立刻将数据返回了,如果按照主键ID来组织数据的话,获得数据更快。
- 辅助索引的叶子节点是存储主键的,而不是数据的存放地址。好处是当行数据发生改变时,索引树节点也是需要分裂变化的,另一个好处是因为辅助索引存放的是主键值,减少了辅助索引占用的存储空间大小。
3.6.2、聚簇索引的限制
- 对于 mysql 数据库目前只有 innodb 数据引擎支持聚簇索引,而 Myisam 并不支持聚簇索引。
- 由于数据物理存储排序方式只能有一种,所以每个 Mysql 的表只能有一个聚簇索引。一般情况下就是该表的主键。
- 为了充分利用聚簇索引的聚簇的特性,**所以 innodb 表的主键列尽量选用有序的顺序 id,而不建议用无序的 id,比如 uuid 这种。**如果id比较大的1话,可以选择雪花算法1得出的id。
- 建议使用int自增的类型,方便排序且默认会在索引树末尾增加主键值,对索引树结构影响最小。聚簇索引的数据的物理存放顺序是一致的,因为索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的,如果主键不是自增id,那么它会不断地调整物理地址、分页,但如果是自增,那么他只需一页页地写索引结构相对紧凑,磁盘碎片较少,效率高。
3.7、索引的分类
3.7.1、单值索引
3.7.1.1、概述
单值索引指一个索引只包含单个列,一个表可以有多个单列索引。
3.7.1.2、语法
# 随着表的建立一起建立
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);
3.7.2、唯一索引
3.7.2.1、概述
唯一索引指的是索引列必须唯一,但是允许有空值,且只能有一个。
3.7.2.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);
3.7.3、主键索引
3.7.3.1、概述
设定为主键后数据库会自动建立索引,innodb为聚簇索引,主键索引列值不能为空。
3.7.3.2、语法
# 随表一起建索引
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)索引
3.7.4、复合索引
他会符合两个原则:
- 最左匹配原则。
- MySQL引擎在查询时为了更好的利用索引,在查询过程中会动态调整查询字段的顺序,以便于更好地利用索引。
3.7.4.1、概述
复合索引是一个索引包含多个列。
3.7.4.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_name), KEY (customer_no,customer_name) );
# 单独建索引
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
3.8、索引的创建时机
3.8.1、适合创建索引的时机
- 主键自动建立唯一索引。
- 频繁作为查询条件的字段应该创建索引。
- 查询中与其他表关联的字段,外键关系建立索引。
- 单键/组合索引面临选择的适合,一般选择组合索引性价比更高。
- 查询中排序的字段,排序字段若通过1索引去访问将大大提高排序的1速度。
- 查询中统计或者分组字段。
3.8.2、不适合创建索引的情况
- 表记录太少。
- 经常增删改的表或者字段。
- where条件里用不到的字段不创建索引。
3.9、索引的基本操作
3.9.1、主键索引
主键索引是在建表时自动创建的。
建表,主键自动创建主键索引
create table t_user(id varchar(20) primary key,name varchar(20));
查看索引
show index from t_user;
3.9.2、单列索引
创表的时候一起创建索引
# 在建表的时候字段后面用key(列名)来创建索引,但是我们没办法指定索引名,默认索引名和列名一致。
create table t_user(id varchar(20) primary key , name varchar(20 , key(name)))
创建完表以后加索引
# 语法格式
create index 索引名 on 表名(列名);
# 示范
create index index_name on t_user(name);
删除索引
# 语法格式
drop index 索引名 on 表名;
# 示范
drop index index_name on t_user;
3.9.3、唯一索引
建表的时候创建索引
# 在建表的时候字段后面用unique(列名)来创建索引,但是我们没办法指定索引名,默认索引名和列名一致。
create table t_user(id varchar(20) primary key , name varchar(20) , unique(name) );
创建表之后创建索引
# 格式
create unique index 索引名 on 表名(列名);
# 示范
create unique index index_name on t_user(name);
3.9.4、复合索引
建表的时候创建索引
# 在建表的时候字段后面用key(列名1,列名2...)来创建索引,但是我们没办法指定索引名,默认索引名和列名一致。
create table t_user (id varchar(20) primary key , name varchar(20) , age int , key(name , age) );
建表之后创建
# 语法格式
create index 索引名 on 表名(列名1,列名2...);
# 示范
create index index_name_age on t_user (name , age);
四、Explain性能分析
4.1、概述
使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。使用语法是:Explatn+SQL语句,他执行后返回的信息有好几列。
4.2、数据准备
create table course
(
cid int(3),
cname varchar(20),
tid int(3)
);
create table teacher
(
tid int(3),
tname varchar(20),
tcid int(3)
);
create table teacherCard
(
tcid int(3),
tcdesc varchar(200)
);
insert into course values(1,'java',1);
insert into course values(2,'html',1);
insert into course values(3,'sql',2);
insert into course values(4,'web',3);
insert into teacher values(1,'tz',1);
insert into teacher values(2,'tw',2);
insert into teacher values(3,'tl',3);
insert into teacherCard values(1,'tzdesc') ;
insert into teacherCard values(2,'twdesc') ;
insert into teacherCard values(3,'tldesc') ;
4.3、id
explain返回的结果集中的id列表示select查询的序列号,表示查询中执行 select 子句或操作表的顺序。
id 的每个号码,表示一趟独立的查询,一个 sql 的查询趟数越少越好。
# 查询课程编号为2或者教师编号为3的老师信息
EXPLAIN select t.* from teacher t
left join teacherCard tc
on tc.tcid = t.tcid
left join course c
on c.tid = t.tid
where c.cid = 2 or t.tid = 3
我们可以发现,id值相同,从上往下顺序执行。
那要是id值不同呢?
# 查询教授SQL课程的老师的描述(desc)
# 我们如果不使用子查询的话,会发现id值还是相同,所以下面将展示子查询的形式
EXPLAIN select tc.tcdesc from teacherCard tc where tc.tcid =
(select t.tcid from teacher t where t.tid =
(select c.tid from course c where c.cname = 'sql')
);
id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行,在嵌套子查询时,先查内层 再查外层。所以先查询c表,然后是t表,最后是tc表。
那继续深入,如果相同1又有不同的id呢?
# 查询教授SQL课程的老师的描述(desc)
# 我们采用子查询1加多表的形式进行查询
explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid
and t.tid = (select c.tid from course c where cname = 'sql') ;
id值越大越优先,若id值相同,从上往下顺序执行。
4.4、select_type
select_type 代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
属性 | 含义 |
---|---|
SIMPLE | 简单的 select 查询,查询中不包含子查询或者 UNION连接查询 |
PRIMARY | 查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary |
DERIVED | 使用到了临时表 |
SUBQUERY | 包含了子查询SQL中的子查询(非最外层) |
DEPEDENT SUBQUERY | 在SELECT或WHERE列表中包含了子查询,子查询基于外层 |
UNCACHEABLE SUBQUERY | 无法使用缓存的子查询 |
UNION | 如果有table1 union table2 ,则table1 就是derived,table2就是union |
UNION RESULT | 告知开发人员,那些表之间存在union查询 |
4.4.1、SIMPLE
简单的 select 查询,查询中不包含子查询或者 UNION连接查询。
select * from teacher
4.4.2、PRIMARY
查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary。
4.4.3、DERIVED
使用到了临时表就会被标记为DERIVED。他有两种1情况:
- 在from子查询中只有一张表。
explain select cr.cname from ( select * from course where tid in (1,2) ) cr ;
- 在from子查询中, 如果有table1 union table2 ,则table1 就是derived,table2就是union
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
4.4.4、UNION
若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION,第一个SELECT会被标记为DERIVED
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
4.4.5、UNION RESULT
告知开发人员,那些表之间存在union查询
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
4.5、table
table表明这个数据是基于哪张表的。
# 给一条复杂一点的SQL
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
在id = 1的查询的table中,有一个<derived2>的值,说明是这个查询用到了衍生表,衍生表的出处是id为2的衍生表。
4.6、type
type表示的是索引类型,是较为重要的一个指标,性能从高到低依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL
一般来说,得保证查询至少达到 range 级别,最好能达到 ref,其中system、const是理想情况,一般很难达到,一般达到的是ref或者range。
如果想对type优化的前提是必须有索引。
4.6.1、system
只有一条数据的系统表或者是衍生表中只有一条数据的主查询,一般是无法达到的,忽略不计。
4.6.2、const
表示通过索引一次就找到了,仅仅能查询到一条数据的SQL,用于Primary key 或 unique,只针对这两个索引有效。也是很难达到的。
# 创建表
create table user
(
tid int(3),
tname varchar(20)
);
insert into test01 values(1,'xiaolin') ;
commit;
# 添加索引
alter table test01 add constraint tid_pk primary key(tid) ;
# 测试
explain select * from (select * from test01 )t where tid =1 ;
4.6.3、eq_ref
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配(有且只有一个,不能多也不能为0)。常见于主键或唯一索引扫描。是可遇不可求的。
explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid
4.6.4、ref
非唯一性索引扫描,对于每个索引键的查询,返回匹配的所有行。
# 先给teacher表的name字段加一个索引
alter table teacher add index index_name(tname);
# 在修改数据库表,两条语句用同一个name
explain SELECT * from teacher where tname = 'tw';
4.6.5、range
只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现了between
、<
、>
、in
(有时候会失效,会转为无索引状态)等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
# 给teacher的tid加一个普通索引
alter table teacher add index index_id(tid);
# 查询id小于3的老师
explain select * from teacher where tid < 3
4.6.6、index
查询全部加了索引的那一列数据。
# 我们刚刚给tid加了索引
explain select tid from teacher;
4.6.7、all
查询表中的所有数据,一般是没有索引的情况。tname字段是没有索引的。
4.6.8、总结
- system\\const:结果只有一条数据。
- eq_ref:结果多条数据,但是每条数据是唯一的。
- ref:结果多条,但是每条数据是0条或者多条。
4.7、possible_keys
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
4.8、key
实际使用的索引。如果为NULL,则没有使用索引。
4.9、key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的利用上了索引。ken_len 越长,说明索引使用的越充分。
key_len的计算方式:
- 先看索引上字段的类型+长度比如 int=4 、varchar(20) =20 、 char(20) =20。
- 如果是 varchar 或者 char 这种字符串字段,视字符集要乘不同的值,比如 utf-8要乘 3,GBK 要乘 2。
- archar 这种动态字符串要加 2 个字节。
- 允许为空的字段要加 1 个字节。
- 如果是复合索引的话,key_len的长度是当前索引以及之前的索引之和。
4.10、ref
指明当前表所参照的字段,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
4.11、rows
rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
4.12、Extra
其他的额外重要的信息。
4.12.1、Using filesort
出现这个说明你的SQL性能消耗大,需要额外的依次排序(查询),比方说有年龄、名字字段,我先通过名字查找出来,然后再根据年龄排序。
对于单索引来说,如果排序和查找是同一个字段,就不会出现Using filesort,反之亦然。
对于复合索引来说,不能跨列,要满足最佳左前缀,where和order by按照复合索引顺序使用,不要跨列或者无序使用。
# 我们先删除掉course中cid的主键,再执行查询
EXPLAIN select * from course where tid=1 order by cid
4.12.2、Using temporary
使了用临时表保存中间结果,表示性能损耗比较大。MySQL 在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by,已经有了一张表,但是不使用,必须使用额外一张表来进行存储。
避免出现Using temporary的方法:查询哪些列就用哪些列来分组。
4.12.3、Using index
Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index),只要使用到的列全部都在索引中,就是索引覆盖。他**避免访问了表的数据行,性能得到了提升!**原因在于这条SQL查询不读取源文件,只从索引文件中获取数据,不在原表中查询(不回表查询)。
-
如果同时出现 using where,表明索引被用来执行索引键值的查找。
-
如果没有同时出现 using where,表明索引只是用来读取数据而非利用索引执行查找。
-
如果用到了覆盖索引的时候,会对possible_keys和key造成影响:
- 如果没有where,则索引只出现在key中。
- 如果有where,则索引会出现在possible_keys和key中
4.12.4、Using where
表明使用了 where 过滤(既需要从索引中去查,又需要回原表中查询)。
# 索引列id可以从索引中查询,但是除了id之外的其他列需要去原表中查询
explain SELECT * from course c where c.tid =1
4.12.5、Using join buffer
表明使用了连接缓存。
explain SELECT * from course c,teacher t where t.tid = c.tid
五、单表SQL优化
建表语句:
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;
5.1、全值索引我最爱
全值索引我最爱指的是,查询的字段按照顺序在索引中都可以匹配到!我们要根据联合索引字段的顺序,不能出现跨列的现象。
SQL 中查询字段的顺序,跟使用索引中字段的顺序,没有关系。优化器会在不影响 SQL 执行结果的前提下,给你自动地优化。
建立索引
create index index_age_depid_name on emp(age,deptid,name);
# 查看emp的索引,检测我们建立索引是否成功
show index from emp
书写SQL测试
EXPLAIN SELECT * FROM emp WHERE emp.age=30;
EXPLAIN SELECT * FROM emp WHERE emp.age=30 and deptid=4;
EXPLAIN SELECT * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd';
5.2、最佳左前缀法则
5.2.1、索引失效情况
explain select * from emp where deptid = 4 and name = "xiaolin"
我们可以发现,这个时候索引失效了。
5.2.2、索引有效的情况
explain select * from emp where age = 18 and deptid = 4
5.2.3、总结
查询字段与索引字段顺序的不同会导致,索引无法充分使用,甚至索引失效!使用复合索引,需要遵循最佳左前缀法则,即如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。
5.3、不要在索引列上做任何计算
explain select * from emp where age +1 = 18 and deptid = 4
我们可以发现,这个时候索引失效了,因为我们在索引列——age上进行了+1的操作,我们不能在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),因为会导致索引失效而转向全表扫描。
5.4、尽量使用覆盖索引
我们先看不适用覆盖索引的情况。
explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'xiaolin';
再看使用到了覆盖索引的情况。
出现了一个Using Index,说明性能得到了提升。查询列和索引列一直,**不要写 select ***
5.5、尽量不要出现前缀模糊匹配
在日常的使用过程中,模糊匹配可以说是使用很多的关键字了,在使用的过程中,我们需要避免使用前缀的模糊匹配,因为会造成索引失效。也就是说like进行以常量开头,不要以%开头
# 先给name字段加上一个索引
create index index_name on emp(name);
# 测试后缀模糊匹配
explain select * from emp where name like "a%";
# 测试前缀模糊匹配
explain select * from emp where name like "%a";
# 测试前后都模糊匹配
explain select * from emp where name like "%a";
我们可以发现,只要是出现了前缀的模糊匹配的时候,都会出现索引失效的问题。如果一定需要使用%开头的模糊查询,我们可以使用索引覆盖来一定程度提高性能。
5.6、减少使用or
# 我们使用or的时候也很容易造成索引失效的问题。
explain select * from emp where age = 18 or deptid = 4
如果我们在实际开发中,需要使用到or的话,我们可以使用 union all 或者 union 来替代。
# 使用union all替代
explain select * from emp where age = 18 union all select * from emp where deptid = 4;
# 使用union替代
explain select * from emp where age = 18 union select * from emp where deptid = 4;
5.8、尽量不要使用显示、隐式类型转换
# 先试一下正常情况
explain select * from emp where name="123";
# 再试试索引失效情况
explain select * from emp where name=123;
索引失效的原因是因为mysql底层会把int类型的123转化为varchar类型的123,索引失效。
5.7、总结
全职匹配我最爱,最左前缀要遵守。
带头大哥不能死,中间兄弟不能断。
索引列上少计算,范围之后全失效。
LIKE 百分写最右,覆盖索引不写*。
不等空值还有 OR,索引影响要注意。
VAR 引号不可丢,SQL 优化有诀窍。
六、多表SQL优化
6.1、建表语句
create table teacher2
(
tid int(4) primary key,
cid int(4) not null
);
insert into teacher2 values(1,2);
insert into teacher2 values(2,1);
insert into teacher2 values(3,3);
create table course2
(
cid int(4) ,
cname varchar(20)
);
insert into course2 values(1,'java');
insert into course2 values(2,'python');
insert into course2 values(3,'kotlin');
commit;
6.2、left join
当我们进行连表查询的时候,会想到一个问题,索引往哪张表加?
explain select *from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';
一般情况下,我们把数据量小的表放在左边,数据量大的表放在右边,在进行连表查询的时候,是左表驱动右表,也就是数据量小的表驱动数据量大的表,这是因为这条SQL查询的底层,实际上是两个循环,一个外层循环,一个内层循环,在开发中,一般是将数据小的循环放外层,数据大的循环放内存。
索引建立在经常使用的字段上,所以可得,如果是左外连接,索引建立在左表的字段,右外连接,索引建立在右表的字段。
按照规则,我们给teacher2这张表加上索引。
alter table teacher2 add index index_teacher2_cid(cid);
# 再次执行
explain select *from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';
我们可以发现,有一张表已经用上了索引了。一般来说,where后面的字段也要加索引,于是我们进一步优化。
# 给cname字段加上索引
alter table course2 add index index_course2_cname(cname);
# 再次执行
explain select *from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';
七、其他的优化方法
7.1、exist
exist语法是将主查询的结果,放到子查询的进行校验(判断子查询是否有数据,如果有数据则校验成功),如果符合校验就保留数据。
select tname from teacher where exists (select * from teacher);
# 等价于
select * from teacher;
- 如果主查询的数据集大,用
in
。 - 如果子查询的数据集很大,用
exist
。
7.2、order by
7.2.1、MySQL的排序算法
我们一般使用order by的时候都会出现using filesort。using filesort有两种算法:
- 双路排序:MySQL4.1之前默认使用双路排序,所谓的双路就是扫描2次磁盘。第一次从磁盘中读取排序字段,在buffer缓冲区对排序字段进行排序。第二次扫描其他字段。这种两次IO是很消耗性能的。
- 单路排序:MySQL4.1之后,为了减少IO访问次数,就改为了单路排序。他只读取一次全部字段,在buffer中挑出排序字段进行排序。但
# 单位是字节,如果max_length_for_sort_data值太低(需要排序的总大小超过了max_length_for_sort_data定义的字节数),MySQL会自动从单路切换到双路。
set max_length_for_sort_data = 2048;
7.2.1.1、双路排序
MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,第一次读取行指针和 orderby 列,对他们进行排序,然后第二次扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据出。从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其他字段。
简单来说,取一批数据,要对磁盘进行了两次扫描,众所周知,I\\O 是很耗时的,所以在 mysql4.1 之后,出现了第二种改进的算法,就是单路排序。
7.2.1.2、单路排序
从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间,
因为它把每一行都保存在内存中了。
7.2.1.3、单路排序存在的问题
单路排序会有一定的隐患,他有可能不是一次IO,可能是多次IO。因为如果数据量太大的话会进行数据拆分,拆分成多次在buffer中进行排序,分片读取,多次读取。我们可以通过sql语句来调大buffer的大小。
7.2.2、提高order by查询的策略
7.2.2.1、增大 sort_butter_size 参数的设置
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的1M-8M 之间调整。
7.2.2.2、增大 max_length_for_sort_data 参数的设置
mysql 使用单路排序的前提是排序的字段大小要小于max_length_for_sort_data。
提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出 sort_buffer_size 的概率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。
7.2.2.3、减少 select 后面的查询的字段
当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建 tmp 文件进行合并排序,导致多次 I/O,但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer的大小。
所以千万不要使用select * ...;
。
7.2.2.4、使用覆盖索引
SQL 只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。
7.2.2.5、保证排序的一致性
我们要保证全部的排序字段排序的一致性,要么全部升序,要么全部降序,不要出现某些部分升序,某些部分降序。
八、慢查询日志
8.1、慢查询日志
8.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进行全面分析。
8.1.2、怎么用
默认情况下,MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。一般在开发的时候打开,上线部署的时候关闭。
# 检查是否开启了慢查询日志,默认是off表示未开启。
show variables like '%slow_query_log';
# 临时开启,在内存中开启,MySQL服务关闭时就关闭了
set global slow_query_log = 1;
# 永久开启,需要在MySQL的配置文件中进行编辑
# 进入MySQL的配置文件
vim /etc/my.cnf
在[mysqld]中添加两行配置。
# 开启慢查询日志
slow_query_log=1
# 指定慢查询日志的存放路径
slow_query_log_file=/var/lib/mysql/localhost-slow.log
# 查询慢查询的阈值
show variables like '%long_query_time%';
# 设置慢查询阈值
# 临时设置,设置完毕后,需要重新登陆后才生效
set global long_query_time = 5;
# 永久开启,需要在MySQL的配置文以上是关于你真的了解MySQL了吗,那你给我说一下锁机制!的主要内容,如果未能解决你的问题,请参考以下文章