赞
踩
优化有两种:RBO(基于规则的优化)
CBO(基于成本的优化)
Mysql 8.0之前,server中有缓存,为了减少IO。但关系数据库内容修改频繁,所以8.0去除了这个功能。
使用show profile查看sql执行时间。
注意:该方式会在新版本中被弃用和替代。
使用方法:
设置属性
set profiling=1;
执行sql
查看profile
-- 显示sql及执行总时长
show profiles;
-- 显示sql每个步骤所用时长
show profile;
如下:
mysql> set profiling = 1; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> select * from store; +----------+------------------+------------+---------------------+ | store_id | manager_staff_id | address_id | last_update | +----------+------------------+------------+---------------------+ | 1 | 1 | 1 | 2006-02-15 04:57:12 | | 2 | 2 | 2 | 2006-02-15 04:57:12 | +----------+------------------+------------+---------------------+ 2 rows in set (0.00 sec) mysql> show profiles; +----------+------------+---------------------+ | Query_ID | Duration | Query | +----------+------------+---------------------+ | 1 | 0.00030825 | select * from store | +----------+------------+---------------------+ 1 row in set, 1 warning (0.00 sec) mysql> show profile; +--------------------------------+----------+ | Status | Duration | +--------------------------------+----------+ | starting | 0.000039 | | Executing hook on transaction | 0.000003 | | starting | 0.000005 | | checking permissions | 0.000004 | | Opening tables | 0.000121 | | init | 0.000004 | | System lock | 0.000006 | | optimizing | 0.000003 | | statistics | 0.000008 | | preparing | 0.000012 | | executing | 0.000035 | | end | 0.000003 | | query end | 0.000002 | | waiting for handler commit | 0.000005 | | closing tables | 0.000004 | | freeing items | 0.000044 | | cleaning up | 0.000015 | +--------------------------------+----------+ 17 rows in set, 1 warning (0.00 sec)
查的是query_Id为2的sql的:
mysql> show profiles; +----------+------------+---------------------+ | Query_ID | Duration | Query | +----------+------------+---------------------+ | 1 | 0.00030825 | select * from store | | 2 | 0.00203925 | select * from staff | +----------+------------+---------------------+ 2 rows in set, 1 warning (0.00 sec) mysql> show profile for query 2; +--------------------------------+----------+ | Status | Duration | +--------------------------------+----------+ | starting | 0.000064 | | Executing hook on transaction | 0.000004 | | starting | 0.000005 | | checking permissions | 0.000004 | | Opening tables | 0.000698 | | init | 0.000004 | | System lock | 0.000004 | | optimizing | 0.000002 | | statistics | 0.000008 | | preparing | 0.000011 | | executing | 0.001179 | | end | 0.000005 | | query end | 0.000002 | | waiting for handler commit | 0.000006 | | closing tables | 0.000007 | | freeing items | 0.000026 | | cleaning up | 0.000012 | +--------------------------------+----------+ 17 rows in set, 1 warning (0.00 sec)
查询其他信息,可以制定type,如官网所示:
SHOW PROFILE [type [, type] ... ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type: {
ALL
| BLOCK IO
| CONTEXT SWITCHES
| CPU
| IPC
| MEMORY
| PAGE FAULTS
| SOURCE
| SWAPS
}
all:显示所有性能信息
block io:显示块io操作次数
coentext switches:显示上下文切换次数,被动和主动(
cpu:显示用户cpu时间、系统cpu时间()
IPC:显示发送和接受的消息数量()
Menory:暂未实现
page faults:显示页错误数量()
source:显示源码中的函数名称与位置()
swaps:显示swap的次数()
使用:show profile [block io | all | coentext switches |…] for query n
默认情况,该模式是开启的,查看方式:
mysql> show variables like 'performance_schema';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| performance_schema | ON |
+--------------------+-------+
1 row in set, 1 warning (0.03 sec)
手动关闭,提示该变量为一个只读变量:
mysql> set performance_schema=off;
ERROR 1238 (HY000): Variable 'performance_schema' is a read only variable
这时候需要修改mysql的一个配置文件my.cnf,才可以修改这个属性。
详细介绍:
查看连接数量:
mysql> show processlist;
+----+-----------------+-----------------+--------+---------+--------+------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+-----------------+--------+---------+--------+------------------------+------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 170180 | Waiting on empty queue | NULL |
| 8 | root | localhost:60421 | NULL | Query | 0 | starting | show processlist |
| 9 | root | localhost:61885 | mytest | Sleep | 3 | | NULL |
+----+-----------------+-----------------+--------+---------+--------+------------------------+------------------+
3 rows in set (0.00 sec)
属性介绍:
id表示session id
user表示操作的用户
host表示操作的主机
db表示操作的数据库
command表示当前状态
sleep:线程正在等待客户端发送新的请求
query:线程正在执行查询或正在将结果发送给客户端
locked:在mysql的服务层,该线程正在等待表锁
analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划
Copying to tmp table:线程正在执行查询,并且将其结果集都复制到一个临时表中
sorting result:线程正在对结果集进行排序
sending data:线程可能在多个状态之间传送数据,或者在生成结果集或者向客户端返回数据
info表示详细的sql语句
time表示相应命令执行时间
state表示命令执行状态
目前性能最好的连接池,阿里的Druid:官方文档
.frm是表结构文件
.ibd表示表结构为InnoDB的数据和索引文件文件
.myd表示表结构为MyISAM的数据文件
.myi表示数据结构为MyISAM的索引文件
1)使用最小数据类型:占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期更少
案例:设计两张表,设计不同的数据类型,查看表的容量(读取都是以4k为单位)
2)简单就好:简单数据类型的操作通常需要更少的CPU周期
例如:
a、整型比字符操作代价更低,因为字符集和校对规则是字符比较比整型比较更复杂,
b、使用mysql自建类型而不是字符串来存储日期和时间
c、用整型存储IP地址
案例:创建两张相同的表,改变日期的数据类型,查看SQL语句执行的速度
3)尽量避免null:列中包含null,查询难优化,因为可为null的列使得索引、索引统计和值比较都更加复杂
定义的长度无关,底层定了他的长度;TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT分别使用8,16,24,32,64位存储空间。
varchar
varchar(n),n<=255时使用额外一个字节保存长度,n>255使用额外两个字节保存长度。
varchar(5)与varchar(255)保存同样的内容,硬盘存储空间相同,但内存空间占用不同,是指定的大小 。
varchar在mysql5.6之前变更长度,或者从255一下变更到255以上时时,都会导致锁表。
应用场景:
a、存储长度波动较大的数据,如:文章,有的会很短有的会很长
b、字符串很少更新的场景,每次更新后都会重算并使用额外存储空间保存长度
c、适合保存多字节字符,如:汉字,特殊字符等
char
最大长度:255
会自动删除末尾的空格
检索效率、写效率 会比varchar高,以空间换时间
应用场景:
a、存储长度波动不大的数据,如:md5摘要
b、存储短字符串、经常更新的字符串
MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。两者都是为了存储很大数据而设计的字符串类型,分别采用二进制和字符方式存储。基本不会使用该方式,而是使用ftp服务器代替。
datetime
占用8个字节
与时区无关,数据库底层时区配置,对datetime无效
可保存到毫秒
可保存时间范围大
不要使用字符串存储日期类型,占用空间大,损失日期类型函数的便捷性
timestamp
占用4个字节
时间范围:1970-01-01到2038-01-19
精确到秒
采用整形存储
依赖数据库设置的时区
自动更新timestamp列的值
date
查询和显示的时候是字符串,其实存储的是数字,节约空间,也可以按顺序排序查询
ip地址和整型之间转换,最大转化为255.255.255.255
mysql> select INET_ATON('192.168.88.123');
+-----------------------------+
| INET_ATON('192.168.88.123') |
+-----------------------------+
| 3232258171 |
+-----------------------------+
1 row in set (0.00 sec)
mysql> select INET_NTOA('3232258171');
+-------------------------+
| INET_NTOA('3232258171') |
+-------------------------+
| 192.168.88.123 |
+-------------------------+
1 row in set (0.00 sec)
三范式最终目的是为了解决数据冗余
第一范式:列不允许再分
第二范式:属性必须完全依赖主键
第三范式:属性不允许出现传递依赖
使用了limit后order by失效
范式和反范式各有优缺点,实际项目中都是一起使用的
主键使用和业务无关的数字序列,优点:
纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间。
mysql中utf8只能存两个字节的字符,utf8mb4才能存3个
在linux中使用 man utf8可以查看编码介绍
MyISAM | InnoDB | |
---|---|---|
索引类型 | 非聚簇索引 | 聚簇索引 |
支持事务 | 否 | 是 |
支持表锁 | 是 | 是 |
支持行锁 | 否 | 是 |
支持外键 | 否 | 是 |
支持全文索引 | 是 | 是(5.6后支持) |
适合操作类型 | 大量select | 大量insert、delete、update |
建表时不设置存储引擎的话,默认的是INNODB,默认值在my.ini文件中,可修改
default-storage-engine=INNODB
INNODB加锁是默认加在索引上的,where条件后的列如果加了索引,则加的是行锁,否则为表锁
存储引擎代表的是数据文件的组织形式
聚簇索引:数据文件和索引文件放在一起
非聚簇索引:数据文件和索引文件不放在一起
oracle中有物化视图,有两种方式,一种基表更改,视图更新;另一种,查询的时候才从基表更新。
当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。
hash表:
memory存储引擎就是使用hash表作为索引文件的。
二插查找树:可能造成树过于深,导致查找IO过于庞大。
AVL树:由于要保证平衡最短子树和最长子树不能超过1,每次超出标准是要进行左旋、右旋,而导致插入效率低,而且也可能树比较深。
红黑树:最长子树不超过最短子树的两倍即可。牺牲了插入效率,来提高查询效率。
B-Tree:B-Tree非叶子结点有数据,导致IO的时候需要读入太多数据到内存。
B Tree*:B*Tree是非也只节点也有指针,对索引来说没必要
两者都是使用B+树作为索引结构。
InnoDB:数据和索引在同一个文件,所以索引B+Tree叶子节点存放了数据。
MyISAM:数据和索引不在一个文件,所以索引B+Tree叶子节点存放的是数据文件的数据地址。
InnoDB是通过B+Tree结构对主键创建索引,然后叶子节点中存储记录,如果没有主键,那么会选择唯一键,如果没有唯一键,那么会生成一个6位的row_id来作为主键,该主键在mysql中不可见,在oracle中可见。
非主键索引的叶子节点存的是主键。那么查询条件为非主键索引的时候,查询费主键索引树后,得到主键,然后还需要通过主键查询主键索引树,从而来查找到内容,这个过程叫回表。
组合索引中有(name,age)
sql语句有where name=‘’ and age=‘ ’;
老版本先把name匹配出来,把age全部取出来,但是后来在匹配name的时候就把age过滤了(5.7在存储引擎中就已经做了匹配了,而没有优化前的是在service中做的匹配)。
全值匹配:指的是和索引中的所有列进行匹配
explain select * from staffs where name = 'July' and age = '23' and pos = 'dev';
匹配最左前缀:只匹配前面的几列
explain select * from staffs where name = 'July' and age = '23';
explain select * from staffs where name = 'July';
匹配列前缀:可以匹配某一列的值的开头部分
explain select * from staffs where name like 'J%';
explain select * from staffs where name like '%y';
匹配范围值:可以查找某一个范围的数据
explain select * from staffs where name > 'Mary';
精确匹配某一列并范围匹配另外一列:可以查询第一列的全部和第二列的部分
explain select * from staffs where name = 'July' and age > 25;
只访问索引的查询:查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
explain select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev';
当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要
案例,建立组合索引a,b,c,不同SQL语句使用索引情况:
语句 | 索引是否发挥作用 |
---|---|
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 or where c = 4 | 否 |
where a = 3 and c = 4 | 是,仅使用了a |
where a = 3 and b > 10 and c = 7 | 是,使用了a,b |
where a = 3 and b like ‘%xx%’ and c = 7 | 使用了a,b |
select actor_id from actor where actor_id=4;select actor_id from actor where actor_id+1=5;
尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
使用前缀索引
前缀索引实例说明
使用索引扫描来排序
union all,in,or都能够使用索引,但是推荐使用in
explain select * from actor where actor_id = 1 union all select * from actor where actor_id = 2;explain select * from actor where actor_id in (1,2);explain select * from actor where actor_id = 1 or actor_id =2;
范围列可以用到索引
范围条件是:<、<=、>、>=、between
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
强制类型转换会全表扫描
explain select * from user where phone=13800001234;#不会触发索引explain select * from user where phone='13800001234';#触发索引
更新十分频繁,数据区分度不高的字段上不宜建立索引
更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能
类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据,、
一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算
创建索引的列,不允许为null,可能会得到不符合预期的结果
当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
能使用limit的时候尽量使用limit
单表索引建议控制在5个以内
单索引字段数不允许超过5个(组合索引)
查看参数:
show status like 'Handler_read%';
参数解释:
Handler_read_first:读取索引第一个条目的次数
Handler_read_key:通过index获取数据的次数
Handler_read_last:读取索引最后一个条目的次数
Handler_read_next:通过索引读取下一条数据的次数
Handler_read_prev:通过索引读取上一条数据的次数
Handler_read_rnd:从固定位置读取数据的次数
Handler_read_rnd_next:从数据节点读取下一条数据的次数
(1)查询性能低下的主要原因是访问的数据太多,某些查询不可避免的需要筛选大量的数据,我们可以通过减少访问数据量的方式进行优化
(2)是否向数据库请求了不需要的数据
我们常常会误以为mysql会只返回需要的数据,实际上mysql却是先返回全部结果再进行计算,在日常的开发习惯中,经常是先用select语句查询大量的结果,然后获取前面的N行后关闭结果集。
优化方式是在查询后面添加limit
select * from actor inner join film_actor using(actor_id) inner join film using(film_id) where film.title='Academy Dinosaur';select actor.* from actor...;
在公司的企业需求中,禁止使用select *,虽然这种方式能够简化开发,但是会影响查询的性能,所以尽量不要使用
如果需要不断的重复执行相同的查询,且每次返回完全相同的数据,因此,基于这样的应用场景,我们可以将这部分数据缓存起来,这样的话能够提高查询效率
在解析一个查询语句之前,如果查询缓存是打开的,那么mysql会优先检查这个查询是否命中查询缓存中的数据,如果查询恰好命中了查询缓存,那么会在返回结果之前会检查用户权限,如果权限没有问题,那么mysql会跳过所有的阶段,就直接从缓存中拿到结果并返回给客户端
mysql通过关键字将SQL语句进行解析,并生成一颗解析树,mysql解析器将使用mysql语法规则验证和解析查询,例如验证使用使用了错误的关键字或者顺序是否正确等等,预处理器会进一步检查解析树是否合法,例如表名和列名是否存在,是否有歧义,还会验证权限等等
当语法树没有问题之后,相应的要由优化器将其转成执行计划,一条查询语句可以使用非常多的执行方式,最后都可以得到对应的结果,但是不同的执行方式带来的效率是不同的,优化器的最主要目的就是要选择最有效的执行计划
mysql使用的是基于成本的优化器,在优化的时候会尝试预测一个查询使用某种查询计划时候的成本,并选择其中成本最小的一个
select count(*) from film_actor;
show status like 'last_query_cost';
可以看到这条查询语句大概需要做1104个数据页才能找到对应的数据,这是经过一系列的统计信息计算来的
原因:
InnoDB因为其mvcc的架构,并不能维护一个数据表的行数的精确统计信息
有时候某个执行计划虽然需要读取更多的页面,但是他的成本却更小,因为如果这些页面都是顺序读或者这些页面都已经在内存中的话,那么它的访问成本将很小,mysql层面并不知道哪些页面在内存中,哪些在磁盘,所以查询之际执行过程中到底需要多少次IO是无法得知的
mysql的优化是基于成本模型的优化,但是有可能不是最快的优化
执行存储过程或者用户自定义函数的成本
数据表的关联并不总是按照在查询中指定的顺序进行,决定关联顺序时优化器很重要的功能
索引和列是否可以为空通常可以帮助mysql优化这类表达式:例如,要找到某一列的最小值,只需要查询索引的最左端的记录即可,不需要全文扫描比较
explain select film.film_id,film_actor.actor_id from film inner join film_actor using(film_id) where film.film_id = 1
mysql在某些情况下可以将子查询转换一种效率更高的形式,从而减少多个查询多次对数据进行访问,例如将经常查询的数据放入到缓存中
如果两个列的值通过等式关联,那么mysql能够把其中一个列的where条件传递到另一个上:
explain select film.film_id from film inner join film_actor using(film_id) where film.film_id > 500;
这里使用film_id字段进行等值关联,film_id这个列不仅适用于film表而且适用于film_actor表
explain
select film.film_id
from film
inner join film_actor
using(film_id)
where film.film_id > 500
and film_actor.film_id > 500;
3
1.join的实现方式原理:
A、Simple Nested-Loop Join:
B、Index Nested-Loop Join:
C、Block Nested-Loop Join:
show variables like '%optimizer_switch%'
2.案例演示:
查看不同的顺序执行方式对查询性能的影响:
explain select film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from film inner join film_actor using(film_id) inner join actor using(actor_id);
查看执行的成本:
show status like 'last_query_cost';
按照自己预想的规定顺序执行:
explain select straight_join film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from film inner join film_actor using(film_id) inner join actor using(actor_id);
查看执行的成本:
show status like 'last_query_cost';
无论如何排序都是一个成本很高的操作,所以从性能的角度出发,应该尽可能避免排序或者尽可能避免对大量数据进行排序。
推荐使用利用索引进行排序,但是当不能使用索引的时候,mysql就需要自己进行排序,如果数据量小则再内存中进行,如果数据量大就需要使用磁盘,mysql中称之为filesort。
如果需要排序的数据量小于排序缓冲区(show variables like ‘%sort_buffer_size%’
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/1006588
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。