- 前言
- 1. 简介
- 1.1 安装
- 1.2 MySQL逻辑架构
- 1.2.1 存储引擎
- 2. 索引优化分析
- 2.1 原因
- 2.2 常见通用的join查询
- 2.3 索引
- 2.3.1 索引分类
- 2.3.2 索引结构
- 2.3.3 索引情况
- 2.4 性能分析
- 2.4.1 id
- 2.4.2 select_type
- 2.4.3 type
- 2.4.4 possible_keys 、key 和 key_len
- 2.4.5 ref
- 2.4.6 rows
- 2.4.7 explain例子分析
- 2.5 优化分析
- 2.5.1 索引单表优化
- 2.5.2 索引两表优化
- 2.5.3 索引三表优化
- 2.6 索引失效
- 2.6.1 总结
- 3. 查询截取分析
- 3.1 in和exists
- 3.2 OrderBy优化
- 3.3 GroupBy优化
- 3.4 慢查询日志
- 4. 锁机制
- 5. 主从复制
本篇文章主要涉及mysql的高级篇,主要是mysql的架构介绍、索引优化分析、查询截取分析、mysql锁机制以及主从复制等
在这之前的学习可参考我之前的文章进行学习
| 数据库知识 | 链接 |
|---|---|
| 数据库查询常用语句语法 | 博客链接 |
| 数据库中增删改常用语法语句(全) | 博客链接 |
| 数据库中事务(Transaction)的详解 | 博客链接 |
| 数据库之DQL、DML、DDL、DCL详细分析 | 博客链接 |
| SQLZOO(中文版)习题答案(全) | 博客链接 |
还有其他细节以及数据库的算法题 可看我的专栏
数据库细节以及数据库算法专栏
本篇文章的笔记主要通过该视频进行学习:
尚硅谷MySQL数据库高级,mysql优化,数据库优化
可通过安装包或者我之前的文章进行学习
linux版本可通过rpm命令或者apt-get命令或者tar进行解压等
1.2 MySQL逻辑架构连接服务引擎存储
架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。
-
连接层
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcplip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 -
服务层
第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。 -
引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。 -
存储层
数据存储层,文件系统之上,完成与存储引擎的交互。
-
查看mysql以提供什么存储引擎 show engines;
-
查看mysql当前默认的存储引擎 show variables like '%storage_engine%';
| 对比项 | 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;2.3 索引
提高效率,类比资源
排好序的、快速查找(影响order by)数据结构
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引
官方解释:
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
如下方二叉树的数据结构所示
索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。
为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
- 平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引
- 唯一索引默认都是使用B+树索引,统称索引。
当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
优势:
- 提高数据检索的效率,降低数据库的IO成本(不用一直通过磁盘查找)
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
劣势
- 索引列也是要占用空间的(占空间)
- 更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
MysQL有大数据量的表,需要花时间研究建立最优秀的索引,或优化查询
2.3.1 索引分类- 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
- 唯一索引:索引列的值必须唯一,但允许有空值。
- 复合索引:即一个索引包含多个列
基本语法:
- 创建
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索引
- Hash索引
- full-text全文索引
- R-Tree索引
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,显然成本非常非常高。
2.3.3 索引情况分为有索引和无索引
索引(查找,排序)
需要建立索引的情况有:
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段(分组都是需要排序的)
不需要简历索引的情况有:
- 表记录太少
- 经常增删改的表(提高了查询速度,但是会同时江低更新表的速度,对表进行更新的时候,mysql还要保存数据,还要保存一下索引文件)
- 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引
- Where条件里用不到的字段不创建索引
一个索引的选择性越接近于1,这个索引的效率就越高
2.4 性能分析MySQL Query Optimizer:Mysql中有专门负责优化SELECt语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划,但不见得最优
MySQL常见瓶颈(通过cpu 、io 、 服务器的硬件进行分析)
- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
通过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 (估计用到的长度)
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即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;
- 再加入索引 ALTER TABLE book ADD INDEX Y(card);
- 在分析EXPLAIN SELECt * FROM class LEFT JOIN book ON class.card = book.card;
之所以只加入一个表的索引,索引就好了
那如果加入另外一个表的索引,结果还是没变化
这是因为
可以看到第二行的type变为了ref,rows也变少了,优化比较明显。这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要在右表建立索引
如果建立左索引,其实没变化
如果使用右连接RIGHT JOIN查询
换成左边是我们的关键点,要想优化需要在左表建立索引。
所以:索引两表优化,左连接右表建索引,右连接左表建索引
2.5.3 索引三表优化建立三张表:
(在前面中有两张表,现在多一个表即可)
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 tablephoneadd index z(card);,以及alter tablebookadd 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);
全值匹配,且从左到右,只不过长度变长了
索引失效(应该避免)
从左开始且不跳列才不会失效
- 最佳左前缀法则 - 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过复合索引中间列。
即使跳过了中间的索引,但是其长度没变化,索引已经失效了
索引列上不做额外操作才不会失效
- 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
少用>,<,between…and等结构
- 存储引擎不能使用索引中范围条件右边的列。(就是>,<,between…and)
减少select *
- 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致))
区别在于extra,索引的不同,速度不一样
不用!=或者<>
- mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
不用 is null, is not null
- is null, is not null 也无法使用索引。
模糊查询 - like以通配符开头(’%abc…’),mysql索引失效会变成全表扫描的操作。
注意看上面模糊查询的细节,只有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,'a@163.com');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('2bb2',23,'b@163.com');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('3cc3',24,'c@163.com');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('4dd4',26,'d@163.com');
在没有索引的时候,怎么查询都是全表查询
增加一个索引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,用它来连接时会索引失效。
总结:
假设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。视频说的a可b可c不可,但是测试了发现都可以 |
SQL调优过程:
- 观察,至少跑1天,看看生产的慢SQL情况。
- 开启慢查询日志,设置阙值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
- explain + 慢SQL分析。
- show profile。
- 运维经理 or DBA,进行SQL数据库服务器的参数调优。
总结:
- 慢查询的开启并捕获
- explain + 慢SQL分析
- show profile查询SQL在Mysql服务器里面的执行细节和生命周期情况
- SQL数据库服务器的参数调优。
小表驱动大表
RBO原理:
- 当B表的数据集必须小于A表的数据集时,用in优于exists。
select * from A where id in (select id from B) 等价于: for select id from B for select * from A where A.id = B.id
- 当A表的数据集系小于B表的数据集时,用exists优于in
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)来决定主查询的数据结果是否得以保留。
提示
- EXSTS(subquey)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以是 SELECT 1 或select ‘X’,官方说法是实际执行时会忽略SELECT清单,因此没有区别。
- EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
- EXISTS子查询往往也可以用条件表达式,其他子查询或者JOIN来替代,何种最优需要具体问题具体分析
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
- Index效率高,它指MySQL扫描索引本身完成排序
- FileSort方式效率较低。
ORDER BY满足两情况,会使用Index方式排序:
- ORDER BY语句使用索引最左前列。
- 使用where子句与Order BY子句条件列组合满足索引最左前列。
如果不在索引列上,mysql的filesort有两种算法:双路排序、单路排序
- 双路排序:MySQL4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和OrderBy列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读对应的数据输出。
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。取一批数据,要对磁盘进行了两次扫描,众所周知,IO是很耗时的,所以在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参数的设置
为什么设置sort_buffer_size、max_length_for_sort_data参数能优化排序?
主要是因为可以提高Order By的速度
具体原因如下:
Order by时select * 是一个Query需要的字段,这点非常重要。在这里的影响是;
- 当Query的字段大小总和小于max_length_for_sort_data而且排序字段不是TEXT|BLOB类型时,会用改进后的算法——单路排序,否则用老算法——多路排序。
- 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次IO,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer__size。
- 尝试提高sort_buffer_size,不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。
- 尝试提高max_length_for_sort_data,提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。
小结
为排序使用索引
MySql两种排序方式∶文件排序 或 扫描有序索引排序
MySql能为 排序 与 查询 使用相同的索引
创建复合索引 a_b_c (a, b, c)
order by能使用索引最左前缀
- ORDER BY a
- ORDER BY a, b
- ORDER BY a, b, c
- ORDER BY a DESC, b DESC, c DESC
如果WHERe使用素引的最左前缀定义为常量,则order by能使用索引
- WHERe a = const ORDER BY b,c
- WHERe a = const AND b = const ORDER BY c
- WHERe a = const ORDER BY b, c
- WHERe a = const AND b > const ORDER BY b, c
不能使用索引进行排序
- ORDER BY a ASC, b DESC, c DESC //排序不—致
- WHERe g = const ORDER BY b, c //产丢失a索引
- WHERe a = const ORDER BY c //产丢失b索引
- WHERe a = const ORDER BY a, d //d不是素引的一部分
- WHERe a in (…) ORDER BY b, c //对于排序来说,多个相等条件也是范围查询
GroupBy优化(和order by差不多)
- group by实质是先排序后进行分组,遵照索引建的最佳左前缀。
- 当无法使用索引列,增大max_length_for_sort_data参数的设置 + 增大sort_buffer_size参数的设置。
- where高于having,能写在where限定的条件就不要去having限定了。
具体详情可看我这篇文章
Mysql中各类锁的机制详细解析(全)
关于mysql的锁也可类似对比一下java的锁
详情可看我这篇文章
java中各类锁的机制详细解析(全)
- master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
- slave将master的binary log events拷贝到它的中继日志(relay log) ;
- slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL复制是异步的且串行化的
复制的原则主要有:
- 每个slave只有一个master
- 每个slave只能有一个唯一的服务器ID
- 每个master可以有多个salve
主从都配置在[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配置文件
- [必须]从服务器唯一ID
- [可选]启用二进制日志
关闭防火墙以及重启设备
具体可看我之前的文章进行学习补充
ubuntu:防火墙配置详细讲解(全)
linux之防火墙命令firewall、iptable以及端口号等详解诠释(全)
在windows上授权 linux,只需要给予服务器ip等
GRANT REPLICATION SLAVE ON *.* TO ‘zhangsan’@‘从机器数据库IP’ IDENTIFIED BY ‘123456’;
- show master status;
- 记录下File和Position的值
在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; 启动服务器的slave
- show slave statusG
- stop slave;停止



