本篇文章主要涉及mysql的高级篇,主要是mysql的架构介绍、索引优化分析、查询截取分析、mysql锁机制以及主从复制等
在这之前的学习可参考我之前的文章进行学习
数据库知识 | 链接 |
---|---|
数据库查询常用语句语法 | 博客链接 |
数据库中增删改常用语法语句(全) | 博客链接 |
数据库中事务(Transaction)的详解 | 博客链接 |
数据库之DQL、DML、DDL、DCL详细分析 | 博客链接 |
SQLZOO(中文版)习题答案(全) | 博客链接 |
还有其他细节以及数据库的算法题 可看我的专栏
数据库细节以及数据库算法专栏
本篇文章的笔记主要通过该视频进行学习:
尚硅谷MySQL数据库高级,mysql优化,数据库优化
可通过安装包或者我之前的文章进行学习
linux版本可通过rpm命令或者apt-get命令或者tar进行解压等
连接服务引擎存储
架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离
。
连接层
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcplip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层
第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。
存储层
数据存储层,文件系统之上,完成与存储引擎的交互。
查看mysql以提供什么存储引擎 show engines;
查看mysql当前默认的存储引擎 show variables like '%storage_engine%';
关于mysql的存储引擎具体可看我之前的文章
对于这部分比较详细
Mysql的两种存储引擎详细分析及区别(全)
对比项 | MyISAM | InnoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其他行有影响,适合高并发的操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
表空间 | 小 | 大 |
关注点 | 性能 | 事务 |
默认安装 | 是 | 是 |
myIsam只关注性能,是因为他只能查询,查询后不用提交事务
而innodb关注事务,比如增删改,都要手动提交事务
不缓存真实数据,所以要查询的时候比较慢,而缓存了真实数据,查询就比较快
性能下降sql、执行时间长、等待时间长
补充:索引分为单值和复合
可以通过频繁使用给他建立索引,所以查询的比较快
关联查询太多join(设计缺陷或不得已的需求)
服务器调优及各个参数设置(缓冲、线程数等)
inner join
全连接full outer join
外连接上图中第6个的实现 可以通过如下:
由于有些mysql不能使用full join,不过可以换种方法表示
A 的独有 + AB 共有 + B的独有
union本身就可以去重
所以可以这样使用
select * from tbl_emp a left join tbl_dept b on a.deptId = b.id
union
select * from tbl_emp a right join tbl_dept b on a.deptId = b.id;
上图中第7个的实现可以通过如下:
也就是A的独有+ B的独有
之后通过union进行合并
select * from tbl_emp a left join tbl_dept b on a.deptId = b.id where b.id is null
union
select * from tbl_emp a right join tbl_dept b on a.deptId = b.id where a.deptId is null;
视频中的sql如何实践,具体这里就不给出,只需要知道其逻辑即可
具体其sql代码如下:
CREATE TABLE `tbl_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`)
)ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
CREATE TABLE tbl_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 CHARACTER SET = utf8;
提高效率,类比资源
排好序的、快速查找(影响order by)数据结构
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引
官方解释:
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
如下方二叉树的数据结构所示
索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。
为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
优势:
劣势
更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
MysQL有大数据量的表,需要花时间研究建立最优秀的索引,或优化查询
基本语法:
CREATE [UNIQUE] INDEX indexName ON mytable(columnName(length));
//或者
ALTER mytable ADD [UNIQUE] INDEX [indexName] ON (columnName(length));
DROP INDEX [indexName] ON mytable;
SHOW INDEX FROM tableName;
添加具体有四种方式:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list);
:该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。ALTER TABLE tbl name ADD UNIQUE index_name (column_list);
:这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。ALTER TABLE tbl_name ADD INDEX index_name (column_list);
:添加普通索引,索引值可出现多次。ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list);
:该语句指定了索引为FULLTEXT,用于全文索引。BTree索引:
初始化介绍
浅蓝色的称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示)
如磁盘块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,显然成本非常非常高。
分为有索引和无索引
索引(查找,排序)
需要建立索引的情况有:
不需要简历索引的情况有:
一个索引的选择性越接近于1,这个索引的效率就越高
MySQL Query Optimizer:Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划,但不见得最优
MySQL常见瓶颈(通过cpu 、io 、 服务器的硬件进行分析)
通过explain的关键字进行分析
explain的使用:explain + sql语句
关于explain的功能可以
id
:表的读取顺序。select_type
:数据读取操作的操作类型。possible_keys
:哪些索引可以使用。key
:哪些索引被实际使用。ref
:表之间的引用。rows
:每张表有多少行被优化器查询。select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:
id相同,执行顺序由上至下
(id都是1,执行顺序从上到下)
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
(在内层的子查询序列,其等级越高)
(以上两种情况同时存在的时候)
注意框框是一个临时表
按照上面的规则进行排序
id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行,衍生=DERIVED
select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
具体的类型有:
SIMPLE
- 简单的select查询,查询中不包含子查询或者UNION。PRIMARY
- 查询中若包含任何复杂的子部分,最外层查询则被标记为。(最外层)SUBQUERY
- 在SELECT或WHERE列表中包含了子查询。(内层)越内层等级越高,越先执行DERIUED
- 在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。UNION
- 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中外层SELECT将被标记为:DERIVED。UNION RESULT
- 从UNION表获取结果的SELECT。(合并之后的查询就是这个选项)访问类型排列
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL
常见的指标主要有:
system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
system
:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。
const
:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量。(单表中的主键id,一张表一个条件
)
eq_ref
:唯一性索引扫描
,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫(联表唯一,和上面的区别在于索引数量不同
)
ref
:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。(上面的条件是一对一,这个条件是一对多
)
range
:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
index
:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的
)。
all
:Full Table Scan,将遍历全表以找到匹配的行。
(也就是通过 select * 全部数据读取)
possible_keys(理论上要多少索引)
显示可能应用在这张表中的索引,一个或多个。
查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用。
key(实际用到的索引)
实际使用的索引。如果为NULL,则没有使用索引
查询中若使用了覆盖索引,则该索引仅出现在key列表中
所谓的覆盖索引:查询时未发生回表
。查询的字段只能建立在索引的字段中
在不损失精确性的情况下,长度越短越好
最大可能长度,并非实际使用长度
,即key_len是根据表定义计算而得,不是通过表内检索出的(显示使用到的条件查询,如果是常量就为const)
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即 ‘ac’。
查询中与其它表关联的字段,外键关系建立索引。
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。(越小越好)
每张表被优化器查询
把不合适的索引删除,慢慢优化
第一行(执行顺序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,namefrom 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列的
关于索引的优化也可看我之前的文章进行学习
Mysql中索引的最左前缀原则图文剖析(全)
建立一张单表
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’);
经过如下查询:
explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
发现其上面的单表查询,不是索引的话,他是进行了全表查询,而且在extra还出现了Using filesort等问题
所以思路可以有建立其复合索引
具体建立复合索引有两种方式:
create index idx_article_ccv on article(category_id,comments,views);
ALTER TABLE 'article' ADD INDEX idx_article_ccv ( 'category_id , 'comments', 'views' );
但这只是去除了它的范围,如果要去除Using filesort问题的话,还要将其中间的条件范围改为等于号才可满足
发现其思路不行,所以删除其索引 DROP INDEX idx_article_ccv ON article;
主要的原因是:
这是因为按照BTree索引的工作原理,先排序category_id,如果遇到相同的category_id则再排序comments,如果遇到相同的comments 则再排序views。
当comments字段在联合索引里处于中间位置时,因comments > 1条件是一个范围值(所谓range),MySQL无法利用索引再对后面的views部分进行检索
,即range类型查询字段后面的索引无效。
所以建立复合索引是对的
但是其思路要避开中间那个范围的索引进去
只加入另外两个索引即可create index idx_article_cv on article(category_id, views);
建立两张表
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)));
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
book
ADD INDEX Y(card
);EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
之所以只加入一个表的索引,索引就好了
那如果加入另外一个表的索引,结果还是没变化
这是因为
可以看到第二行的type变为了ref,rows也变少了,优化比较明显。这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要在右表建立索引
如果建立左索引,其实没变化
如果使用右连接RIGHT JOIN查询
换成左边是我们的关键点,要想优化需要在左表建立索引。
所以:索引两表优化,左连接右表建索引,右连接左表建索引
建立三张表:
(在前面中有两张表,现在多一个表即可)
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)));
因为有用到前面的索引,所以把不必要的索引删除干净
添加必要的索引
通过后两张表
分别是alter table
phoneadd index z(
card);
,以及alter table
bookadd index y(
card);
因此索引最好设置在需要经常查询的字段中
Join语句的优化
尽可能减少Join语句中的NestedLoop的循环总次数:“永远用小结果集驱动大的结果集”。
优先优化NestedLoop的内层循环
保证Join语句中被驱动表上Join条件字段已经被索引。
当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置。
为了更好的演示效果,采用实战方式
建立一张表
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());
建立索引 ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(
name,
age,
pos);
全值匹配,且从左到右,只不过长度变长了
索引失效(应该避免)
从左开始且不跳列才不会失效
即使跳过了中间的索引,但是其长度没变化,索引已经失效了
索引列上不做额外操作才不会失效
少用>,<,between…and等结构
范围条件右边的列
。(就是>,<,between…and)减少select *
不用!=或者<>
不用 is null, is not null
注意看上面模糊查询的细节,只有xx%前缀查询才不会失效
那如何改变%xx% 让其不失效呢
引入一张表
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 FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT id,NAME FROM tbl_user WHERE NAME LIKE ‘%aa%’;
EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE ‘%aa%’;
EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE ‘%aa%’;
id之所以没加索引,但是可以加入使用不会失效,是因为他是主键
但是如果加入了没有主键又不是索引的东西,%xx%就会失效
类型要正确
即使类型不正确也可以查询,但是底层会帮你转换类型,在判断,但会浪费时间,索引直接失效,变成了全表查询
字符串不加单引号索引失效。
不用or关键字就不会失效
少用or,用它来连接时会索引失效。
为了更加贴切的展示,先建立一张表
create table test03(
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 test03(c1,c2,c3,c4,c5) values (‘a1’,‘a2’,‘a3’,‘a4’,‘a5’);
insert into test03(c1,c2,c3,c4,c5) values (‘b1’,‘b2’,‘b3’,‘b4’,‘b5’);
insert into test03(c1,c2,c3,c4,c5) values (‘c1’,‘c2’,‘c3’,‘c4’,‘c5’);
insert into test03(c1,c2,c3,c4,c5) values (‘d1’,‘d2’,‘d3’,‘d4’,‘d5’);
insert into test03(c1,c2,c3,c4,c5) values (‘e1’,‘e2’,‘e3’,‘e4’,‘e5’);
建立一索引 create index idx_test03_c1234 on test03(c1,c2,c3,c4);
以下都符合最左前缀原则,都会用到索引,只是估计用到的长度在变化而已
正常顺序
explain select * from test03 where c1='a1';
explain select * from test03 where c1='a1' and c2='a2';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
乱序
即使更改一下顺序,mysql的最左前缀原则还是符合的,因为mysql有优化器会帮你查询是否匹配
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3';
限定范围
如果开始限定范围
第一条会用到3个索引,前两个用来查找,第三个用来排序
第二条用到了4个索引,前3个用来查找,第四个用来排序
explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
通过看其长度也可知道
使用order by
下面那个例子都是一样的
前两个都是在查找,第三个只是在排序,到了这里就已经断了,所以第四个可有可无
explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;
explain select * from test03 where c1='a1' and c2='a2' order by c3;
如果把3换成4,会出现Using filesort。,因为优化器会给你文件排序(因为中间跳了一个)
如果使用这个不会出现Using filesort,因为是正常的索引顺序
而这个就会出现Using filesort,因为顺序颠倒了
explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
下面这两个一样的功能
前两个是查找,第二三是排序
explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3;
这个就不一样,如果确定了一个查找的顺序之后,即使排序乱序也不会出现filesort
但是如果不用的话就会出现
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;
explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
group by
如果乱序,还会多出现一个临时表
定值、范围还是排序,一般order by是给个范围
group by基本上都需要进行排序,会有临时表产生
explain select * from test03 where c1='a1' and c4='a4' group by c2,c3;
explain select * from test03 where c1='a1' and c4='a4' group by c3,c2;
explain select * from test03 where c1=‘a1’ and c4=‘a4’ order by c2,c3;
模糊查询
explain select * from test03 where c1='a1' and c2 like 'kk%' and c3='a3';
explain select * from test03 where c1='a1' and c2 like '%kk' and c3='a3';
explain select * from test03 where c1='a1' and c2 like '%kk%' and c3='a3';
explain select * from test03 where c1='a1' and c2 like 'k%kk%' and c3='a3';
具体其执行结果如下
假设index(a, b, c)
where语句 | 索引是否被使用 |
---|---|
where a = 3 | 可以,使用到a |
where a = 3 and b = 5 | 可以,使用到a,b |
where a = 3 and b = 5 and c = 4 | 可以,使用到a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | 不可 |
where a = 3 and c = 5 | 使用到a,但是c不可以,b中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到a和b,c不能用在范围之后,b断了 |
where a = 3 and b like ‘kk%’ and c = 4 | 可以,使用到a,b,c。 |
where a = 3 and b like ‘%kk’ and c = 4 | Y,使用到a |
where a = 3 and b like ‘%kk%’ and c = 4 | Y,使用到a |
where a= 3 and b like ‘k%kk%’ and c = 4 | Y,使用到a,b,c |
优化的口诀如下:
全值匹配我最爱, 最左前缀要遵守;
带头大哥不能死, 中间兄弟不能断;
索引列上少计算, 范围之后全失效;
LIKE 百分写最右, 覆盖索引不写 *
;
不等空值还有 OR, 索引影响要注意;
VAR 引号不可丢, SQL 优化有诀窍。
SQL调优过程:
总结:
小表驱动大表
RBO原理:
select * from A where id in (select id from B)
等价于:
for select id from B
for select * from A where A.id = B.id
select * from A where exists (select 1 from B where B.id = A.id)
等价于:
for select * from A
for select * from B where B.id = A.id
关于exists的关键字
该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。
提示
因此子查询中的SELECT * 也可以是 SELECT 1 或select ‘X’
,官方说法是实际执行时会忽略SELECT清单,因此没有区别。何种最优需要具体问题具体分析
ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
建立一张表以及一个索引
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);
如果索引的时候也是按照顺序,两种情况都是一样的
但是索引的时候不按顺序
就会出现这样的情况
MySQL支持二种方式的排序:FileSort和lIndex
ORDER BY满足两情况,会使用Index方式排序:
如果不在索引列上,mysql的filesort有两种算法:双路排序、单路排序
两次扫描磁盘
,最终得到数据,读取行指针和OrderBy列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读对应的数据输出。
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
按照order by列在buffer对它们进行排序,然后扫描排序压的列表进行输出,它的效率更快一些
,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。但是用单路有问题
在sort_buffer中,单路排序比双路排序要多占用很多空间,因为单路排序是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。
本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
优化策略
为什么设置sort_buffer_size、max_length_for_sort_data参数能优化排序?
主要是因为可以提高Order By的速度
具体原因如下:
Order by时select * 是一个Query需要的字段,这点非常重要。在这里的影响是;
都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次IO,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer__size。
尝试提高max_length_for_sort_data,提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率
。小结
为排序使用索引
MySql两种排序方式∶文件排序 或 扫描有序索引排序
MySql能为 排序 与 查询 使用相同的索引
创建复合索引 a_b_c (a, b, c)
order by能使用索引最左前缀
如果WHERE使用素引的最左前缀定义为常量,则order by能使用索引
不能使用索引进行排序
GroupBy优化(和order by差不多)
它用来记录在MySQL中响应时间超过阀值的语句
,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10
,意思是运行10秒以上的语句。默认情况下,MySQL数据库没有开启慢查询日速,需要我们手动来设置这个参数。
一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
通过如下命令进行操作
SHOW VARIABLES LIKE '%slow_query_log%';
set global slow_query_log=1
,只对当前数据库生效,默认关闭的结果如下
永久生效
修改配置文件my.cnf
[mysqld]下增加或修改参数slow_query_log和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件
slow_query_log =1
slow_query_log_file=/var/lib/manongyanjiuseng-slow.log
关于慢查询的参数slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)
什么样的数据会放到慢查询日志
需要设置一个多长的时间段就会放到日志中
这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒
命令:SHOW VARIABLES LIKE 'long_query_time%';
运行时间正好等于long_query_time的情况,并不会被记录下来,需大于
设置阈值时间后还需要重启才可以生效 set global long_query_time=3;
查询当前系统中有多少条慢查询记录 show global status like '%Slow_queries%';
如果在配置文件中设置阈值
具体配置如下
#[mysqld]下配置:
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;
log_output=FILE;
==结合工具进行分析mysqldumpslow ==
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。
查看mysqldumpslow的帮助信息,mysqldumpslow --help
。
工作常用参考
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
mysqldumpslow -s r-t 10 /ar/lib/mysql/atguigu-slow.log | more
为了更好的展示
先建立一张表
create database bigData;
use bigData;
CREATE TABLE dept(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
dname VARCHAR(20)NOT NULL DEFAULT “”,
loc VARCHAR(13) NOT NULL DEFAULT “”
)ENGINE=INNODB DEFAULT CHARSET=utf8;
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;
开启了可以创建存储函数的权限,主要是这个功能
设置参数log_bin_trust_function_creators
show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1;
这是在终端上输入的
但是重启后会失效
如果要配置永久的,需要在配置文件上配置
windows下my.ini[mysqld]加上log_bin_trust_function_creators=1
linux下/etc/my.cnf 下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 $$
执行完语句后,在终端上输入select rand_string(2)$$;
,要以$$结尾
随机产生部门编号
delimiter $$
create function rand_num() returns int(5)
begin
declare i int default 0;
set i=floor(100+rand()*10);
return i;
end $$
执行完语句后,在终端上输入select rand_num()$$
,要以$$结尾
创建存储过程,创建往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表中插入数据
DELIMITER ;
CALL insert_dept(100, 10);
往emp表中插入50万数据
DELIMITER ;
CALL insert_emp(100001, 500000);
Show Profile是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
默认情况下,参数处于关闭状态,并保存最近15次的运行结果。
show variables like 'profiling';
set profiling=on;
通过执行show profiles;
- 诊断SQL,show profile cpu,block io for query 上一步前面的问题SQL数字号码;
例如show profile cpu,block io for query 3;
具体的参数介绍
在查询该文件的时候如果出现了这些一定要特别注意
General log默认不开启的原因有两个:
日志将会非常大,对磁盘是一个很大的压力。因为所有的操作都会被记录下来。
对MySQL数据的性能有一定的影响。
不要在生产环境开启这个功能。
#开启
general_log=1
#记录日志文件的路径
general_log_file=/path/logfile
#输出格式
log_output=FILE
show variables like 'general_log';
set global general_log=1;
set global log_output='TABLE';
log_output=‘FILE‘表示将日志存入文件,默认值是‘FILE‘
log_output=‘TABLE‘表示将日志存入数据库,这样日志信息就会被写入到mysql.slow_log表中.
所编写的sql语句,将会记录到mysql库里的geneial_log表,可以用下面的命令查看:select * from mysql.general_log;
查出来会有输出语句
具体详情可看我这篇文章
Mysql中各类锁的机制详细解析(全)
关于mysql的锁也可类似对比一下java的锁
详情可看我这篇文章
java中各类锁的机制详细解析(全)
二进制日志(binary log)
。这些记录过程叫做二进制日志事件,binary log events;中继日志(relay log) ;
。MySQL复制是异步的且串行化的
复制的原则主要有:
主从都配置在[mysqld]结点下,都是小写
具体的配置文件在window上是这么修改,主机修改my.ini
server-id=1
[必须]主服务器唯一ID
log-bin=自己本地的路径/mysqlbin
[必须]启用二进制日志
log-err=自己本地的路径/mysqlerr
[可选]启用错误日志
basedir=“自己本地路径”
[可选]根目录
tmpdir=“自己本地路径”
[可选]临时目录
datadir=“自己本地路径/Data/”
[可选]数据目录
binlog-ignore-db=mysql
[可选]设置不要复制的数据库
binlog-do-db=需要复制的主数据库名字
[可选]设置需要复制的数据库
linux上作为从机,从机修改my.cnf配置文件
关闭防火墙以及重启设备
具体可看我之前的文章进行学习补充
ubuntu:防火墙配置详细讲解(全)
linux之防火墙命令firewall、iptable以及端口号等详解诠释(全)
在windows上授权 linux,只需要给予服务器ip等
GRANT REPLICATION SLAVE ON *.* TO ‘zhangsan’@‘从机器数据库IP’ IDENTIFIED BY ‘123456’;
show master status;
在linux中授权window的IP地址
CHANGE MASTER TO MASTER_HOST=’主机
IP’, MASTER_USER=‘zhangsan’, MASTER_PASSWORD=’123456’, MASTER_LOG_FILE='File名字’, MASTER_LOG_POS=Position数字;
start slave;
启动服务器的slaveshow slave status\G
stop slave;
停止关于主从复制更加详细的内容可看我之前的文章
关于主从复制的超详细解析(全)
对于数据库想增强了解可看我的栏目
数据库栏目