mysql索引优化笔记

本文详细介绍了MySQL索引的工作原理、创建与优化策略,以及查询优化的关键点,包括EXPLAIN的使用、何时创建索引、不适合建索引的情况、查询截取分析、使用覆盖索引等。通过实例分析了如何通过调整索引和查询语句来提高查询性能,减少全表扫描和文件排序,以提升数据库系统的整体效率。
摘要由CSDN通过智能技术生成

目录

1. 索引

2. 需要创建索引的情况

3. 不适合建索引

3.1 小技巧

4. mysql查询优化器小知识

5. MySQL常见瓶颈

6. EXPLAIN关键字

6.1 select_type类型

6.2 type

7. 如何让索引不失效

7.1 查询截取分析

8. EXISTS

9. 一个小练习

10. sql优化

10.1 优化策略

11. 为排序使用索引

12. Mysql慢查询日志

13. mysqldumpslow

14. Show Profile

15. 全局查询日志


1. 索引

        MySql官方对索引的定义为:索引(Index)是帮助MySql高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。可以理解为“排好序的快速查找数据结构”。

        在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:

        为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取相应数据,从而快速的检索出符合条件的记录。

        数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构是索引。

        一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,符合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。

优势:

        类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本,通过索引列对数据进行排序,降低数据排序成本,降低了CPU的消耗。

劣势:

        实际上索引也是一张表,该表保存了主键于索引字段,并指向实体表记录,所以索引列也是要占用空间的,虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert,update和delete.因为更新表时,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 table_name\G;

-- 使用alter命令有四种方式来添加数据表的索引;
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,用于全文索引。

 初始化介绍

        一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1,P2,P3。

        P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实数据存在于叶子节点。即3,5,...,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。

2. 需要创建索引的情况

  1. 主键自动建立唯一索引。
  2. 频繁作为查询条件的字段应该创建索引。
  3. 查询中与其他表关联的字段,外键关系建立索引。
  4. 频繁更新的字段不适合创建索引:因为每次更新不单单是更新了记录还会更新索引。
  5. where条件里用不到的字段不创建索引。
  6. 单键/组合索引的选择问题,(在高并发下倾向创建组合索引)
  7. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。
  8. 查询中统计或者分组字段。

3. 不适合建索引

  1. 表记录太少。
  2. 经常增删改的表:提高了查询速度,同时却会降低更新表的速度,如对表进行insert,update,delete,因为更新表时,mysql不仅要保存数据,还要保存一下索引文件。
  3. 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。

3.1 小技巧

        假如一个表中有10万行记录,有一个字段A只有T和F两种值,且每个值得分布概率大约为50%,那么对于这种表A字段建立索引一般不会提高数据库的查询速度。

        索引的选择性是指索引列中不同值得数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。

4. mysql查询优化器小知识

MySQL Query Optimizer

  1. MySQL中有专门负责优化select语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的query提供他认为最优的执行计划(他认为 最优的数据检索方式,但不见得是DBA认为最优的,这部分最耗费时间)
  2. 当客户端向MySQL请求一条query,命令解析器模块完成请求分类,区别出是select并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条query进行优化,处理掉一些常量表达式的 预算,直接换算成常量值。并对query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析query中的hint信息(如果有),看显示hint信息是否可以完全确定该query的执行计划。如果没有hint或hint信息还不足以完全执行计划,则会读取所涉及对象的统计信息,根据query进行写相应的计算分析,然后再得出最后的执行计划。

5. MySQL常见瓶颈

  • CPU: CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候。
  • IO: 在磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
  • 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态。

6. EXPLAIN关键字

        使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句的。分析查询语句或是表结构的性能瓶颈。

explain + sql语句

执行计划包含的信息

 explain可以查看到的信息:

  •         表的读取顺序。
  •         数据读取操作的操作类型。
  •         哪些索引可以使用。
  •         哪些索引被实际使用。
  •         表之间的引用。
  •         每张表有多少行被优化器查询。

表的读取顺序有三种情况:

  • id相同:执行顺序由上至下。
  • id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。
  • id相同不同同时存在。
select t2.* from t2 where id = ( select id from t1 where id = ( select t3.id from t3 where t3.id = 3))

MySQL [jointest]> explain select t2.* from t2 where id = ( select id from t1 where id = ( select t3.id from t3 where t3.id = 3));
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
|  1 | PRIMARY     | t2    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
|  2 | SUBQUERY    | t1    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
|  3 | SUBQUERY    | t3    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

子查询id序号递增,id序号越大优先级越高,越先被执行。

select t2.* from (select t3.id from t3 where t3.id = 3) s1, t2 where s1.id=t2.id;

MySQL [jointest]> explain select t2.* from (select t3.id from t3 ) s1, t2 where s1.id=t2.id;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t3    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
|  1 | SIMPLE      | t2    | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

自己测试与笔记中记载有出入,可能是数据结构不同或是mysql版本不同:

 笔记中记载:

 id如果相同,可以认为是一组,从上往下顺序执行;

在所有的组中,id值越大,优先级越高,越先执行;

(Derived:衍生)

6.1 select_type类型

        select_type类型,主要用于区别普通查询,联合查询,子查询等的复杂查询;

  1. simple: 简单的select查询,查询中不包含子查询或union。
  2. primary: 查询中包含任何复杂的子部分,最外层查询则被标记为。
  3. subquery: 在select或where列表中包含子查询。
  4. derived: 在from列表中包含的子查询被标记为derived,mysql会递归执行这些子查询,把结果放在临时表中。
  5. union: 若第二个 select出现在union之后,则被标记 为union;如果union包含在from子句的子查询中,外层select将被标记为derived.
  6. union result: 从union表获取结果的select。

6.2 type

访问类型排列

显示查询使用了何种类型,

从最好到最差依次是

system > const > eq_ref > ref > range > index > all 

        上面为常见类型的排序,一般来说,保证查询至少达到range级别,最好达到ref.(但是如果实在做不到,查询比较特殊,为了 保证功能的正确性,不要吹毛求疵,非要按照这个标准)。全部类型的排序是:

system>const>eq_ref>ref>fullltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>all

system : 表中只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个可以忽略不计。

const : 表示通过索引一次就找到了,const用于比较 primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,mysql就能将该查询转为一个常量。

eq_ref : 唯一性索引扫描,对于每个索引建,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。

ref : 非唯一性索引扫描 ,返回匹配某个单独值得所有行。本质上是一种索引访问,返回所有匹配某个单独值的行,因为可能会找到多个符合条件的行,所以应该 属于查找和扫描的 混合体。

range : 只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。一般就是在你的where语句中出现了between、<、>、in 等的查询。这种范围扫描索引比全表扫描摇号,因为只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。

index : Full Index Scan, index 与 ALL 区别于index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是度全表,但index是从索引中读取的,而all是从硬盘中读取的)。

possible_key  : 显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

key : 实际使用的索引。如果为NULL,则没有索引。插叙中如果使用了覆盖索引,则该索引仅出现在key列表中。

t1表中只建立了主键id列,所以select * from t1语句:

查询全部,所以possible_keys=null;

因为t1表中只有一列主键id,所以查询全部相当于select id from t1,所以key=primary;

查询中使用覆盖索引,则该索引出现在key列表中

create index_id_name on t3 (id,`name`);

 key_len : 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出来的。

 ref : 显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。(实例中因为建立的复合索引 "index_id_name" ,如果查询条件是按照索引顺序 where id 或 where id = ? and name=  ?会用到索引,如果不按照顺序,直接 where name = ? 则不会用到索引!!!)

rows : 根据表统计信息和索引选用情况,大致估算出找到所需记录需要读取的行数。

Extra : 包含不适合在其他列中显示但十分重要的额外信息

        1. Using filesort。说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。

        2. using temporary。使用了临时表保存中间结果,MySQL对查询结果排序时使用了临时表。常见于排序order by 和分组查询 group by 。

        3. using index 。表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!如果同时使用了using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。

覆盖索引(Covering index ):

理解方式1 :就是select的数据列只用从索引中就能取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。

理解方式2 :索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就能得到想要的数据,就不需要 读取行了。一个索引包含(或覆盖了)满足查询结果的 数据就叫覆盖索引。

注意:如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *;因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

        4. Using where 。表明使用了where过滤。

        5. uusing join buffer。使用了连接缓存。

        6. impossible where。where子句的值总是false,不能用来获取任何元组。

        7. select tables optimized away。(了解即可)在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等执行阶段再进行计算,查询执行计划生成的阶段即完成优化。       

        8. distinct。(了解即可)优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。

select d1.name,(select id from t3) d2 from (select id,`name` from t1 where `name`='test01') d1 union (select name, id from t2)

 以上实例,id越大优先级越高 。

分析explain:

实例表tbl_a如下图,id为主键。

 

 实例表tbl_b如下图,id为主键,val 和 a_id都有对应的索引,val不能为空,a_id可以为空。

 

explain select * from tbl_a left join tbl_b on tbl_a.id=tbl_b.id;

 使用了索引,所以b表只搜索了一行

 以val列为例删除idx_val的索引  (drop index idx_val on tbl_b)

 就会查询tbl_b表中的所有行数。(到这里就自己应该有意识如果是连接,优先考虑要小表驱动大表,大表建立好索引)。

7. 如何让索引不失效

  1. 全值匹配
  2. 最佳左前缀法则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列
  3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
  4. 存储引擎不能使用索引范围中条件右边的列。
  5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select * .
  6. mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
  7. is null,is not null 也无法使用索引。
  8. like 以通配符开头(‘%abc...’)mysql索引失效会变成全表扫描的操作。
  9. 字符串不加单引号索引失效。
  10. 少用or,用它来连接时会索引失效。

7.1 查询截取分析

  1. 观察,至少跑1天,看看生产的慢sql情况。
  2. 开启慢查询日志,设置阈值。
  3. explain + sql 分析
  4. show profile  查询SQL在MySQL服务器里面的执行细节和生命周期情况
  5. SQL数据库服务器的参数调优。

优化原则:小表驱动大表,即小的数据集驱动大的数据集。

-- 举例说明 --
select * from A where id in (select id from B)
-- 等价于:
for select id from B
for select * from A where A.id = B.id

-- 当B表的数据集必须小于A表的数据集,用in优于exists。
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

-- 当A表的数据集小于B表的数据集时,用exists优于in。
-- 注意:A表与B表的ID字段应建立索引。

8. EXISTS

SELECT ... FROM table WHERE EXISTS(subquery)

该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留

提示:

  1. EXISTS(subquery)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以是SELECT 1 或select 'X',官方说法是实际执行时会忽略SELECT清单,因此没有区别
  2. EXISTS子查询的实际执行过程可能经过了优化而不是我们理解的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
  3. EXISTS子查询往往也可以用条件表达式,其他子查询或者JOIN来代替,何种最优需要具体问题具体分析。

9. 一个小练习

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` varbinary(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;

--结论:很显然,type是all,最坏的情况。Extra里还出现了Using filesort,也是最坏的情况。优化是必须的。

--开始优化:
--1.1 新建索引+删除索引
--alter table `article` add index idx_article_ccv(`category_id`,`comments`,`views`);
create index idx_article_ccv on article(category_id,comments,views);
--1.2 第二次explain
explain select id,author_id from `article` where category_id=1 and comments>1 order by views desc limit 1;
explain select id,author_id from `article` where category_id=1 and comments=3 order by views desc limit 1;

--结论:
--type 变成range,但是extra里使用Using filesort 。
--建立的索引为什么没用?
--因为按照BTree索引的工作原理,
--先排序category_id,
--如果遇到相同的category_id则再排序comments,如果遇到相同的comments则再排序views.
--当comments字段在联合索引里处于 中间位置时,
--因comments>1条件是一个范围值(所谓range),
--mysql无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。

--1.3删除第一次建立的索引
drop index idx_article_ccv on article;

--1.4第2次新建索引
--alter table `article` add index idx_article_cv(`category_id`,`views`);
create index idx_article_cv on article(`category_id`,views);

--1.5第3次explain
explain select id,author_id from article where category_id=1 and comments>1 order by views desc limit 1;
--结论:可以看到type变成了 ref,Extra中的Using filesort也消失了,结果非常理想。


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 idx_staffs_nameAgePos(`name`,age,pos);

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,'b@163.com');
insert into tbl_user(name,age,email)values('2aa2',222,'a@163.com');
insert into tbl_user(name,age,email)values('3aa3',265,'c@163.com');
insert into tbl_user(name,age,email)values('4aa4',21,'d@163.com');

10. sql优化

        ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序。MySQL支持两种方式排序,FileSort和Index,Index效率高。指MySQL扫描索引本身完成排序。FileSort方式效率较低。

        ORDER BY 满足两种情况,会使用Index方式排序:

  •         ORDER BY语句使用索引最左前列
  •         使用Where子句与Order By子句条件列组合满足索引最左前列 。

        尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀

        如果不在索引列上,filesort有两种算法:双路排序和单路排序。mysql就要启动双路排序和单路排序.

         order by使用了索引的字段但是没有按照索引顺序,最终会使用filesort方式排序。

        order by使用了索引中的所有字段age、birth(mysql会优化,其实相当于按顺序使用了复合索引),Extra显示使用了where和index。没有使用filesort.

        虽然按顺序使用了索引字段,但是排序时age正序,birth逆序,并没有按照索引顺序规则,还是会使用到filesort(如果age和birth都是用正序或都使用逆序,索引依然健康有效).

        mysql4.1之前使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。

        从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。要对磁盘进行两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

        从磁盘读取查询需要的所有列,按照order by列在buffer对他们进行排序,然后扫描排序后的列表进行输出,他的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是他会使用更多的空间,因为他把每一行都保存在内存中了。

结论及引申出的问题:由于单路是后出的,总体而言好过双路,但是单路有问题:

        在sort_buffer中,方法B比方法A要占用很多空间,因为方法B把所有字段都取出来,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只读取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_buffer容量大小,再排……从而多次I/O.

本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

10.1 优化策略

        增大sort_buffer_size参数的设置

        增大max_length_for_sort_data参数的设置

提高Order by的速度

1. Order by时select * 是一个大忌,只query需要的字段,这点非常重要,在这里的影响是:

  • 当query的 字段大小总和小于max_length_for_sort_data而且排序字段不是text|blob类型时,会用改进后的算法——单路排序,否则使用老算法——多路排序。
  • 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。

2.尝试提高sort_buffer_size。

  • 不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力提高,因为这个参数是针对每个进程的。

3.尝试提高max_length_for_sort_data

  • 提高这个参数,会增加用改进算法的概率,但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。

11. 为排序使用索引

        MySql两种排序方式:文件排序或扫描有序索引排序

        MySql能为排序与查询使用相同的索引

-- KEY 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 -- 对于排序来说,多个相等条件也是范围查询

        group by 实质上是先排序后进行分组,遵照索引建的最佳左前缀。当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的 设置

        where高于having,能写在where限定的条件就不要去having限定了。

12. Mysql慢查询日志

        MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中的响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。

        具体指运行时间超过long_query_time的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。

        由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前的explain进行全面分析。

        默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动设置该参数。

        当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件

-- 查看是否开启以及如何开启:

SHOW VARIABLES LIKE '%slow_query_log%';

set global slow_query_log=1;

        使用set global slow_query_log=1开启了慢查询日志只对当前数据库生效,如果MySQL重启后则会失效。

         如果要永久生效,就必须修改配置文件my.cnf (其它系统变量也是如此)。修改my.cnf文件,【mysqld】下增加或修改参数 slow_query_log 和 slow_query_log_file 后,然后重启MySQL服务器。如下:

slow_query_log=1
slow_query_log_file=/var/lib/mysql/slow_sql.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%';

         可以使用命令修改,也可以在my.cnf参数里面修改。

        假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,在mysql源码里面是判断大于long_query_time,而非大于等于。

使用命令

set global long_query_time=3

修改阈值到3秒就是慢sql

 

 【mysqld】下的配置:

slow_query_log=1;
slow_query_log_file=/var/lib/mysql/sql_slow.log;
long_query_time=3;
log_output=FILE

13. mysqldumpslow

常用参考:

-- 得到返回记录集最多的10个SQL

mysqldumpslow -s r -t 10 /var/lib/mysql/sql_slow.log

-- 得到访问次数最多的10个SQL

mysqldumpslow -s c -t 10 /var/lib/mysql/sql_slow.log

-- 得到按照时间排序的前10条里面含有左连接的查询语句

mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/sql_slow.log

-- 另外建议使用这些命令时结合 | 和 more 使用,否则有可能出现爆屏情况

mysqldumpslow -s r -t 10 /var/lib/mysql/sql_slow.log | more

查看mysqldumpslow的帮助信息:

  • s: 是表示按照何种方式排序;
  • c: 访问次数;
  • l: 锁定时间;
  • r: 返回记录;
  • t: 查询时间;
  • al: 平均锁定时间;
  • ar: 平均返回记录数;
  • at: 平均查询时间;
  • t: 即为返回前面多少条数据;
  • g: 后边搭配一个正则匹配模式,大小写不敏感;

测试用例

-- 建表dept

create database bigData;

use bigData;

-- 1 建表dept

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=gbk;


-- 2 建表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=gbk;

创建函数,假如报错:This function has none of DETERMINISTIC......

由于开启过慢查询日志,因为我们开启了bin-log,我们就必须为我们的function指定一个参数。

show variables like 'log_bin_trust_function_creators';

set global log_bin_trust_function_creators=1;

这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:

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 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

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 $$

--

delimiter $$

create procedure insert_emp(IN START INT(10), in max_num int(10))

begin

declare i int default 0;

-- set autocommit=0 把autocommit 设置成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 $$

--

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 $$

-- 删除存储过程

drop function rand_num();

14. Show Profile

-- 准备工作
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 DEFAULT CHARSET=utf8;

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 default charset=utf8;

insert into tbl_dept(deptName,locAdd)values('RD',11);
insert into tbl_dept(deptName,locAdd)values('HR',12);
insert into tbl_dept(deptName,locAdd)values('MK',13);
insert into tbl_dept(deptName,locAdd)values('MIS',14);
insert into tbl_dept(deptName,locAdd)values('FD',15);

insert into tbl_emp(name,deptId)values('z3',1);
insert into tbl_emp(name,deptId)values('z4',1);
insert into tbl_emp(name,deptId)values('z5',1);
insert into tbl_emp(name,deptId)values('w5',2);
insert into tbl_emp(name,deptId)values('w6',2);
insert into tbl_emp(name,deptId)values('s7',3);
insert into tbl_emp(name,deptId)values('s8',4);
insert into tbl_emp(name,deptId)values('s9',51);

        show profile 是MySQL提供的可以用来分析当前会话中语句执行的资源消耗情况,可以用于SQL的调优的测量。

官网:MySQL :: MySQL 8.0 参考手册 :: 13.7.7.30 显示配置文件语句https://dev.mysql.com/doc/refman/8.0/en/show-profile.html

 默认情况下,参数处于关闭状态,并保存最近15次的运行结果

分析步骤:

1. 是否支持,看看当前的mysql版本是否支持。

2. 开启功能,默认是关闭,使用前需要开启。

3. 运行SQL

select * from emp group by id%10 limit 150000;

select * from emp group by id%20 order by 5;

 

4. 查看结果, show profiles;

select * from tbl_emp e inner join tbl_dept d on e.deptId = d.id;

 以此为例:

 

5. 诊断SQL, show profile cpu,bolck io for query 上一步前面 问题SQL数字号码;

-- 诊断SQL

show profile cpu,block io for query 5;

-- 参数备注

ALL    -- 显示所有开销信息

BLOCK IO    -- 显示块IO相关开销

CONTEXT SWITCHES    -- 上下文切换相关开销

CPU    -- 显示CPU相关开销信息

IPC    -- 显示发送和接收相关开销信息

MEMORY    -- 显示内存相关开销信息

PAGE FAULTS    -- 显示页面错误相关开销信息

SOURCE    -- 显示和source_function, source_file, source_line相关的开销信息

SWAPS    -- 显示交换次数相关开销信息

 可以看出2的查询过程创建了临时表

 

6. 日常开发需要注意的结论

        converting heap to myisam 查询结果太大,内存都不够用了往磁盘上搬了。

        creating tmp table 创建临时表

                拷贝数据到临时表

                用完再删除

        copying to tmp table on disk 把内存中临时表复制到磁盘,危险!!!

        locked

15. 全局查询日志

永远不要再生产环境使用

配置启用

编码启用

set global general_log=1;

set global log_output='TABLE';

 此后,所编写的sql语句,将会记录到mysql库里的general_log表,可以用下面的命令查看

select * from mysql.general_log;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_evenif

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值