命令 | 描述 | 备注 |
---|---|---|
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’进行删除,系统会有残留信息保存 |
host表示连接类型。
user表示用户名,同一用户通过不同方式连接的权限是不一样的。
这里显示的是明文密码通过哦MYSQLSHA1加密算法加密后得到的密文密码,是不可逆的,mysql 5.7 的密码保存到 authentication_string 字段中不再使用 password 字段。
表示该用户所拥有的权限。
我们可以通过命令来授予用户权限,该权限如果发现没有该用户,则会直接新建一个用户。
格式为:
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';
我们可以使用命令来查看当前用户的权限。
show grants;
我们可以使用命令来收回用户的权限,权限收回后,必须用户重新登录后,才能生效。
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 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
MySql大概分为四层:
最上层是一些客户端和连接服务,包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于 SSL 的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层提供各种用户使用的接口,同时提供SQL优化器,对用户传进来的SQL语句进行优化。他有几个组件。
组件名 | 描述 |
---|---|
Management Serveices & Utilities | 系统管理和控制工具 |
SQL Interface | SQL 接口。接受用户的 SQL 命令,并且返回用户需要查询的结果。比如 select from就是调用 SQL Interface |
Parser | 解析器。 SQL 命令传递到解析器的时候会被解析器验证和解析 |
Optimizer | 查询优化器。SQL 语句在查询之前会使用查询优化器对查询进行优化,比如有where 条件时,优化器来决定先投影还是先过滤。 |
Cache 和 Buffer | 查询缓存。如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key 缓存,权限缓存等 |
存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
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
ON
JOIN
WHERE
GROUP BY
HAVING
SELECT
DISTINCT
ORDER BY
LIMIT
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整张表,不适合高并发操作 | 行锁,操作时只锁住某一行,不对其他行有影响,适合高并发操作 |
缓存 | 只缓存索引不缓存真实数据 | 不仅仅缓存索引还缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。可以简单理解为排好序的快速查找数据结构。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
左边是数据表,一共是两列七条数据,最左边的是数据记录的物理地址。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
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 并不真实存在于数据表中。
如果要查找数据项 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,显然成本非常非常高。
我们可以看到BTree结构图中,每个节点不仅仅包含key值,同时还有data值,每一个页的存储空间是有限的,如果data数据较大时会导致每个节点(即每一个页)能存储的key数量很小,当存储的数据量很大的时候会导致BTree的深度很深,增大了查询时磁盘I/O的次数。
而在B+Tree中,所有数据记录节点都是按照键值大小顺序放在同一层的叶子节点上,而非叶子节点只存储key信息,这样可以大大增加每个节点存储的key值的数量,降低B+Tree的高度。
实际情况中每个节点可能不能填满,在数据库中,B+Tree的高度一般在24层之间,MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的(不动用磁盘I/O,直接上内存找),也就是说查找某一键值行记录时最多只需要13次磁盘I/O操作。
单值索引指一个索引只包含单个列,一个表可以有多个单列索引。
# 随着表的建立一起建立
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);
唯一索引指的是索引列必须唯一,但是允许有空值,且只能有一个。
# 随着表的建立一起建立
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);
设定为主键后数据库会自动建立索引,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)索引
他会符合两个原则:
复合索引是一个索引包含多个列。
# 随表一起建索引
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);
主键索引是在建表时自动创建的。
建表,主键自动创建主键索引
create table t_user(id varchar(20) primary key,name varchar(20));
查看索引
show index from t_user;
创表的时候一起创建索引
# 在建表的时候字段后面用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;
建表的时候创建索引
# 在建表的时候字段后面用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);
建表的时候创建索引
# 在建表的时候字段后面用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 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。使用语法是:Explatn+SQL语句,他执行后返回的信息有好几列。
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') ;
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值相同,从上往下顺序执行。
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查询 |
简单的 select 查询,查询中不包含子查询或者 UNION连接查询。
select * from teacher
查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary。
使用到了临时表就会被标记为DERIVED。他有两种1情况:
explain select cr.cname from ( select * from course where tid in (1,2) ) cr ;
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION,第一个SELECT会被标记为DERIVED
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
告知开发人员,那些表之间存在union查询
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
table表明这个数据是基于哪张表的。
# 给一条复杂一点的SQL
explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;
在id = 1的查询的table中,有一个的值,说明是这个查询用到了衍生表,衍生表的出处是id为2的衍生表。
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优化的前提是必须有索引。
只有一条数据的系统表或者是衍生表中只有一条数据的主查询,一般是无法达到的,忽略不计。
表示通过索引一次就找到了,仅仅能查询到一条数据的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 ;
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配(有且只有一个,不能多也不能为0)。常见于主键或唯一索引扫描。是可遇不可求的。
explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid
非唯一性索引扫描,对于每个索引键的查询,返回匹配的所有行。
# 先给teacher表的name字段加一个索引
alter table teacher add index index_name(tname);
# 在修改数据库表,两条语句用同一个name
explain SELECT * from teacher where tname = 'tw';
只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现了between
、<
、>
、in
(有时候会失效,会转为无索引状态)等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
# 给teacher的tid加一个普通索引
alter table teacher add index index_id(tid);
# 查询id小于3的老师
explain select * from teacher where tid < 3
查询全部加了索引的那一列数据。
# 我们刚刚给tid加了索引
explain select tid from teacher;
查询表中的所有数据,一般是没有索引的情况。tname字段是没有索引的。
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
实际使用的索引。如果为NULL,则没有使用索引。
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的利用上了索引。ken_len 越长,说明索引使用的越充分。
key_len的计算方式:
指明当前表所参照的字段,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
其他的额外重要的信息。
出现这个说明你的SQL性能消耗大,需要额外的依次排序(查询),比方说有年龄、名字字段,我先通过名字查找出来,然后再根据年龄排序。
对于单索引来说,如果排序和查找是同一个字段,就不会出现Using filesort,反之亦然。
对于复合索引来说,不能跨列,要满足最佳左前缀,where和order by按照复合索引顺序使用,不要跨列或者无序使用。
# 我们先删除掉course中cid的主键,再执行查询
EXPLAIN select * from course where tid=1 order by cid
使了用临时表保存中间结果,表示性能损耗比较大。MySQL 在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by,已经有了一张表,但是不使用,必须使用额外一张表来进行存储。
避免出现Using temporary的方法:查询哪些列就用哪些列来分组。
Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index),只要使用到的列全部都在索引中,就是索引覆盖。他**避免访问了表的数据行,性能得到了提升!**原因在于这条SQL查询不读取源文件,只从索引文件中获取数据,不在原表中查询(不回表查询)。
表明使用了 where 过滤(既需要从索引中去查,又需要回原表中查询)。
# 索引列id可以从索引中查询,但是除了id之外的其他列需要去原表中查询
explain SELECT * from course c where c.tid =1
表明使用了连接缓存。
explain SELECT * from course c,teacher t where t.tid = c.tid
建表语句:
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;
全值索引我最爱指的是,查询的字段按照顺序在索引中都可以匹配到!我们要根据联合索引字段的顺序,不能出现跨列的现象。
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';
explain select * from emp where deptid = 4 and name = "xiaolin"
我们可以发现,这个时候索引失效了。
explain select * from emp where age = 18 and deptid = 4
查询字段与索引字段顺序的不同会导致,索引无法充分使用,甚至索引失效!使用复合索引,需要遵循最佳左前缀法则,即如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。
explain select * from emp where age +1 = 18 and deptid = 4
我们可以发现,这个时候索引失效了,因为我们在索引列——age上进行了+1的操作,我们不能在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),因为会导致索引失效而转向全表扫描。
我们先看不适用覆盖索引的情况。
explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'xiaolin';
再看使用到了覆盖索引的情况。
出现了一个Using Index,说明性能得到了提升。查询列和索引列一直,**不要写 select ***
在日常的使用过程中,模糊匹配可以说是使用很多的关键字了,在使用的过程中,我们需要避免使用前缀的模糊匹配,因为会造成索引失效。也就是说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";
我们可以发现,只要是出现了前缀的模糊匹配的时候,都会出现索引失效的问题。如果一定需要使用%开头的模糊查询,我们可以使用索引覆盖来一定程度提高性能。
# 我们使用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;
# 先试一下正常情况
explain select * from emp where name="123";
# 再试试索引失效情况
explain select * from emp where name=123;
索引失效的原因是因为mysql底层会把int类型的123转化为varchar类型的123,索引失效。
全职匹配我最爱,最左前缀要遵守。 带头大哥不能死,中间兄弟不能断。 索引列上少计算,范围之后全失效。 LIKE 百分写最右,覆盖索引不写*。 不等空值还有 OR,索引影响要注意。 VAR 引号不可丢,SQL 优化有诀窍。
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;
当我们进行连表查询的时候,会想到一个问题,索引往哪张表加?
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';
exist语法是将主查询的结果,放到子查询的进行校验(判断子查询是否有数据,如果有数据则校验成功),如果符合校验就保留数据。
select tname from teacher where exists (select * from teacher);
# 等价于
select * from teacher;
in
。exist
。我们一般使用order by的时候都会出现using filesort。using filesort有两种算法:
# 单位是字节,如果max_length_for_sort_data值太低(需要排序的总大小超过了max_length_for_sort_data定义的字节数),MySQL会自动从单路切换到双路。
set max_length_for_sort_data = 2048;
MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,第一次读取行指针和 orderby 列,对他们进行排序,然后第二次扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据出。从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其他字段。
简单来说,取一批数据,要对磁盘进行了两次扫描,众所周知,I\O 是很耗时的,所以在 mysql4.1 之后,出现了第二种改进的算法,就是单路排序。
从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
单路排序会有一定的隐患,他有可能不是一次IO,可能是多次IO。因为如果数据量太大的话会进行数据拆分,拆分成多次在buffer中进行排序,分片读取,多次读取。我们可以通过sql语句来调大buffer的大小。
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的1M-8M 之间调整。
mysql 使用单路排序的前提是排序的字段大小要小于max_length_for_sort_data。
提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出 sort_buffer_size 的概率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。
当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建 tmp 文件进行合并排序,导致多次 I/O,但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer的大小。
所以千万不要使用select * ...;
。
SQL 只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。
我们要保证全部的排序字段排序的一致性,要么全部升序,要么全部降序,不要出现某些部分升序,某些部分降序。
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。
由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。
默认情况下,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的配置文件
vim /etc/my.cnf
# 在[mysqld]下面追加
long_query_time=3
# 查询超过慢查询阈值的sql数量
# 睡眠4s,模拟一条超过了4s的SQL
select sleep(4);
show global status like '%slow_queries%';
# 如果我们想知道具体是哪条SQL的话,我们需要去刚刚指定的慢查询日志文件中进行查询
cat /var/lib/mysql/localhost-slow.log
我们可以发现用原生的慢查询日志十分不友好,我们可以通过mysql自带的日志分析工具 mysqldumpslow来分析慢查询。
# 在Linux中查看mysqldumpslow的帮助信息
mysqldumpslow --help
参数 | 描述 |
---|---|
-s | 是表示按照何种方式排序 |
c | 访问次数 |
l | 锁定时间 |
r | 返回记录 |
t | 查询时间 |
al | 平均锁定时间 |
ar | 平均返回记录数 |
at | 平均查询时间 |
-t | 返回前面多少条的数据 |
-g | 后边搭配一个正则匹配模式,大小写不敏感的 |
# 得到返回记录集最多的 10 个 SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
# 得到访问次数最多的 10 个 SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
# 得到按照时间排序的前 10 条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
# 另外建议在使用这些命令时结合 | 和 more 使用 ,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
在MySQL中有很多种类型的锁,比如我们最熟悉的行锁,那这里有个问题,为什么MySQL中会有行锁呢?其实原因就是:MySQL要保证数据的一致性。当数据 update 时首先要进行当前读(读取最新的数据)得到数据,而且要保证查出来的数据到更改完成的这段时间内数据不会被其他事务更改。这样的话你的 update 语句执行得到的结果和语义上是“一致的”。
/* MYSQL/SQLSERVER 支持自增,Oracle 需要借助于序列来实现自增 */
create table tablelock
(
id int primary key auto_increment,
name varchar(20)
) engine myisam;
insert into tablelock(name) values('a1');
insert into tablelock(name) values('a2');
insert into tablelock(name) values('a3');
insert into tablelock(name) values('a4');
insert into tablelock(name) values('a5');
# 给表加读锁或者写锁,可以给多张表一起加,语法格式为
lock table 表1 read/writelock table 表2 read/write;
# 查看加锁的表,1代表加了锁
show open tables;
# 释放锁
unlock tables;
show status like 'table%';
他的结果有两行数据:
一般建议用Table_locks_immedicate/Tbale_locks_waited的值来衡量,如果大于5000,采用InnoDB引擎,否则使用MyISAM引擎。
行锁,一次锁一行数据,因此 如果操作的是不同数据,则不干扰。
create table linelock
(
id int(5) primary key auto_increment,
name varchar(20)
)engine=innodb;
insert into linelock(name) values('1');
insert into linelock(name) values('2');
insert into linelock(name) values('3');
insert into linelock(name) values('4');
insert into linelock(name) values('5');
为了研究行锁,我们需要暂时将自动提交关闭,方便我们手动提交。
set autocommit = 0;
如果没有索引,则行锁会转为表锁。
show index from linelock ;
alter table linelock add index idx_linelock_name(name);
# 索引未失效
# 会话0进行写操作
update linelock set name = 'ai' where name = '3' ;
# 会话1进行写操作,不同的数据
update linelock set name = 'aiX' where name = '4' ;
# 索引失效(发生了索引类型转换)
# 会话0进行写操作
update linelock set name = 'ai' where name = 3 ;
# 会话1对不同的数据进行写操作
update linelock set name = 'aiX' where name = 4 ;
可以发现,数据被阻塞了(加锁),因为索引类型发生了类型转换导致了索引失效,因此此次操作会从行锁转为表锁。
行锁存在一种极为特殊的情况
值在范围内,但是却不存在,这种称为间隙锁。比如我们在linelock表中没有id=7的数据,当我们写一条SQL的时候:update linelock set name ='x' where id >1 and id<9 ;
,在中国where的范围内,没有id=7在这个范围内,但是没有id=7的数据,则id=7的数据成为了间隙。MySQL会自动给间隙加锁,名为间隙锁,同时他也是行锁。即MySQL会自动给id=7的数据加间隙锁(行锁)。
我们也可以在查询的时候加行锁,只需使用第四种方式。
# 通过for update对query语句进行加锁。
select * from linelock where id =2 for update ;
我们可以使用SQL语句来分析行锁。
show status like '%innodb_row_lock%' ;
他有四个参数: