一、存储引擎
1.1、查看mysql支持的存储引擎 show engines;
1.2、查看当前默认的存储引擎 show variables like ‘storage_engine’;
1.3 myISam存储引擎和Innodb存储引擎之间的区别
对比项 | myIsam | Innodb |
主外键 | 不支持(外键) | 支持(主外键) |
事务 | 不支持事务 | 支持 |
锁 | 表锁(即使操作一条数据也会锁住整张表,不适合高并发) | 行锁:只锁住一行,不对其他行产生影响(适合搞并发) |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还缓存真实数据,对内存要求较高,而且内存大小影响性能 |
表空间 | 大 | 小 |
关注点 | 性能 | 事务 |
默认安装 | 安装 | 安装 |
二、SQL优化分析(SQL性能下降,等待时间长,执行时间长)
2.1、基础sql查询语句模板
SELECT DISTINCT FROM JOIN WHERE GROUP BY HAVING ORDER BY LIMIT
2.2、你真的会7种join查询吗?
2.3、mysql索引真的很难?
2.3.1、什么是索引
官方定义(索引是帮助Mysql高效获取排好序的数据结构),个人理解数据库在存储数据本身外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现改高级查找算法,这种数据结构就是索引。
为了加快Col2的查找,数据库维护右侧这样一颗二叉查找数,每个节点分别包括索引键值和一个执行对应数据记录物理地址的指针,可以理解为一个map,从而快速检索出符合条件的记录。
如:select * from test_table where Col2 = 89
如没有索引会进行全表扫描 即从表的第一行开始找直到找到Col2=89的记录返回,总共需查找6次才能找到,要是利用索引序列进行查找,索引2次就可以找到。
注意:mysql的索引底层并未使用二叉树来作为数据结构,而是采用 B族树来维护索引 ,原因分析:我们都知道二叉树的左子节点子节点值>大于父节点的值。如果数据库出现以下情形(单边情况)相当于进行了全表查询,并不会提高查询效率。
2.3.2、为什么不采用红黑树来作为索引的数据结构?
红黑树是一种自平衡二叉树,为防止单边情况的发生,红黑树采用左旋或者右旋的方式来平衡二叉树, 例如jdk1.8 hashmap底层引入红黑树来解决链表过长问题 ,关于红黑的原理在这里不做过多的介绍。有兴趣的朋友可以去学习。 那为什么索引底层不是用红黑树作为索引的数据结构呢?一个数据库的数据可能达到百万甚至千万级别,则形成的索引树的深度会非常的深,如果查找的某条数据恰好在最底层的叶子节点,也会查询几十次或几百次才能找到。肯定不适合做索引的数据结构。
2.3.3、更好的解决方案(B-Tree),三层树结构可存储上百万条数据
上文提到红黑树虽然解决了二叉树出现单边的情况,但随着数据量的增大,树的深度无法可控,因此B树利用横向开辟更大的空间来存储元素,使得树的高度可控,而B树上大部分的操作所需要的磁盘存取次数和B树的高度是成正比的,在B树中可以检查多个子结点,由于在一棵树中检查任意一个结点都需要一次磁盘访问,所以B树避免了大量的磁盘访问。
上述图为b+树图,叶子节点存储数据,其他节点只存储key,目的是节省更多的空间存储数据。
通俗地将,我们知道索引也是存在磁盘中的,当查询数据时不可能将索引树全部节点加载到内存中,而是将根节点先加入内存,判断key的范围,再将子节点加入内存,直到找到数据。比如28,首先将【17,35】加载内存中,判断【17<28<35】,再将【26,30】加载进内存,判断【26<28<30】,将【28,29】加载进内存,恰好找到28,取出磁盘地址,进行io数据返回。
2.3.4、为什么不使用hash作为索引的数据结构
mysql索引也可以采用哈希表来作为索引的数据结构,哈希表也为散列表,又直接寻址改进而来。在哈希的方式下,一个元素k处于h(k)中,即利用哈希函数h,根据关键字k计算出槽的位置。函数h将关键字域映射到哈希表T[0...m-1]的槽位上。
上图中哈希函数h有可能将两个不同的关键字映射到相同的位置,这叫做碰撞,在数据库中一般采用链接法来解决。在链接法中,将散列到同一槽位的元素放在一个链表中,如下图所示:
InnoDB中采用除法散列函数,冲突机制采用链接法。
一般将hash索引应该一次索引就能找到数据,那为什么mysql索引很少采用hash作为索引的数据结构。
- Hash索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。哈希索引只支持等值比较查询,包括=、 IN 、<=> (注意<>和<=>是不同的操作)。也不支持任何范围查询,例如WHERE price > 100。 由于Hash索引比较的是进行Hash运算之后的Hash值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的Hash算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样。
- Hash索引无法被用来避免数据的排序操作。 由于Hash索引中存放的是经过Hash计算之后的Hash值,而且Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
- Hash索引不能利用部分索引键查询。 对于组合索引,Hash索引在计算Hash值的时候是组合索引键合并后再一起计算Hash值,而不是单独计算Hash值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash索引也无法被利用。
- Hash索引在任何时候都不能避免表扫描。 前面已经知道,Hash索引是将索引键通过Hash运算之后,将 Hash运算结果的Hash值和所对应的行指针信息存放于一个Hash表中,由于不同索引键存在相同Hash值,所以即使取满足某个Hash键值的数据的记录条数,也无法从Hash索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
- Hash索引遇到大量Hash值相等的情况后性能并不一定就会比BTree索引高。 对于选择性比较低的索引键,如果创建Hash索引,那么将会存在大量记录指针信息存于同一个Hash值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
2.3.5、索引的目的
- 在于提高查找效率,可以类比字典
- 索引会影响where 查找 和 order by排序 group by分组
- 索引是一种数据结构 (排好序的快速查找数据库) 避免全表查找
2.3.6、优势
- 提高数据的检索速度(避免全表检索) 降低数据库的io成本
- 通过索引对数据排序,降低对数据排序的成本,降低了CPU 的消耗
2.3.7、劣势
- 索引其实也是一张表,保存了主键和索引字段,并指向实体表的记录,所以索引列也占用较大的磁盘空间
- 虽然索引提高了检索速度,但会大大降低update,delete,insert的速度,都会更新索引列
- 索引需要不断的更新,和花费大量的时间去维护索引
2.3.8、索引的存储分类
mysql目前只支持4种索引,不是所有的存储引擎都支持索引
- B-Tree索引:最常见的索引
- Hash索引:只有Memory引擎支持,使用场景简单
- R-Tree索引(空间索引):空间索引是myisam存储引擎的一种特殊索引类型,主要用于地理空间索引
- Full-txet(全文索引):全文索引是myisam存储引擎的一种特殊索引类型,主要用于全文索引
索引 | MyIsqm引擎 | InnoDB引擎 | Memory引擎 |
B-Tree索引 | 支持 | 支持 | 支持 |
HASH索引 | 不支持 | 不支持 | 支持 |
R-Tree索引 | 支持 | 不支持 | 不支持 |
Full-Text索引 | 支持 | 不支持 | 不支持 |
2.3.9、索引的分类和基本操作
- 单值索引:即一个索引只包含一个索引列,一个表可以有多个索引列。
- 唯一索引:索引列的值必须唯一,但允许有空值。
- 复合索引:即一个索引包含多列。
-
基本语法:
创建索引 create [union] index index_name on table_name(字段1,字段2,...)
Alter table_name add [UNION] index [index_name] on (字段 1,字段2,...)
删除索引 drop index [index_name] on table_name
查看索引 show index from table_name
-
多个单列索引和一个复合索引在使用上的不同:如
create index index_name_age on user(name,age)或者
create index index_name on user(name),
create index index_age on user(age),
两者的性能比较
创建一个多列索引:CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name) ); 创建多个索引:CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name), INDEX_2 name (first_name) );
当查询语句的条件中包含last_name 和 first_name时,例如:SELECT * FROM test WHERE last_name='Kun' AND first_name='Li'; sql会先过滤出last_name符合条件的记录,在其基础上再过滤first_name符合条件的记录。那如果我们分别在last_name和first_name上创建两个列索引,mysql的处理方式就不一样了,它会选择一个最严格的索引来进行检索,可以理解为检索能力最强的那个索引来检索,另外一个利用不上了,这样效果就不如多列索引了。但是多列索引的利用也是需要条件的,以下形式的查询语句能够利用上多列索引:
SELECT * FROM test WHERE last_name='Widenius';SELECT * FROM test WHERE last_name='Widenius' AND first_name='Michael';SELECT * FROM test WHERE last_name='Widenius' AND (first_name='Michael' OR first_name='Monty');SELECT * FROM test WHERE last_name='Widenius' AND first_name >='M' AND first_name < 'N';
以下形式的查询语句利用不上多列索引:
SELECT * FROM test WHERE first_name='Michael';SELECT * FROM test WHERE last_name='Widenius' OR first_name='Michael';
多列建索引比对每个列分别建索引更有优势,因为索引建立得越多就越占磁盘空间,在更新数据的时候速度会更慢。另外建立多列索引时,顺序也是需要注意的,应该将严格的索引放在前面,这样筛选的力度会更大,效率更高。
2.3.10、那些字段适合建索引
- 主键自动建索引(主键)
- 频繁作为查询条件的字段应该建索引(查询条件)
- 查询中与其他表关联的字段,外键关系建立索引(外键)
- 频繁更新的字段不适合建立索引 因为更新并不仅仅更新数据,还要更新索引树
- where条件中用不到的字段,不要建索引(where)
- 查询中排序字段,排序字段若通过索引去访问将大大提高排序速度(order by)
- 查询中统计或分组的字段(group by)
2.3.1、那些字段不适合建索引
- 表记录太少
- 经常更新的表提高了查询的速度,降低了更新表的速度 更新表时同时更新索引树
- 固定且重复率过高的数据,建索引意义不大,如性别 国家等。
假如有一个表有十万行记录,有一个字段只有true或false两种值,且每个值出现的概率大约各占50%,这种一般建索引不会提高查询效率。 索引的选择性 是指索引列不同值的数目与表中记录数的比,如果一个表中有2000条记录,表索引列有1980个不同的值,那么索引的选择性为1980/2000=0.99, 一个索引的选择性接近于1,这个索引的效率越高 。
三、性能分析
3.1、mysql Query optimizer(mysql查询优化器)
mysql中有专门负责优化select语句的优化器模块,主要功能:通过计算分析收集到的统计信息,为客户端提供它认为最优的执行计划(它认为最优的执行计划,但并不一定是DBA认为最优的执行计划) 3.2、mysql的常见瓶颈
- cpu在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候
- IO 发生在装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈
3.3、优化sql语句的一般步骤
当遇到sql的性能问题,我们该从何处入手进行系统分析,使得能够尽快定位sql问题并得以解决,请看一下分析
3.3.1、通过show status命令了解各种sql的执行效率
mysql连接客户端之后,通过 show[session|global] status (session:当前连接,gloabal:自数据库上次连接至今的统计结果)命令可以提供服务器状态信息。
Com_xxx表示每个xxx语句的执行次数(对 所有存储引擎 的表都会进行统 计),我们比较关心以下的几个参数
-
- Com_select:累计查询次数
- Com_insert: 累计插入次数(批量只统计一次)
- Com_update: 累计更新次数
- Com_delete: 累计删除次数
以下是针对于innodb存储引擎的,通过以下的参数可以判断该数据库是以查询为主,还是以更新为主。
- Innodb_rows_read:select查询返回的行数
- Innodb_rows_inserted: 累计插入的行数
- Innodb_rows_updated: 累计更新的行数
- Innodb_rows_deleted: 累计删除的行数
针对于事务的应用通过Com_commit和Com_rollback了解事务的提交和回滚情况。
3.3.2、定位执行效率较低的sql语句
- 概念:慢查询日志是mysql提供的一种日志记录,用来记录在mysql中响应时间超过阈值(long_query_time默认为10s)的sql语句
- 记录慢sql语句,结合要将的explain来分析sql语句
3.3.3、通过Explain分析低效的sql执行计划
- 概念介绍:使用explain关键字可以模拟优化器来执行sql语句,从而知道mysql是如何处理你的sql语句的,分析你的查询语句或表结构的性能瓶颈。
- 怎么玩:explain select * from table_name
- 能干嘛:
- explain sql 结果分析(干货来了)
(1)、Id:select查询序列号,包含一组数字,表示查询中执行select语句顺序或操作表的顺序
ID | 结果分析 |
全不同 | 加载表的顺序从上至下 |
全相同 | id值越大,优先加载 |
有相同也有不同 | 先加载ID大的,如果ID相同则从上至下加载 |
查看表的加载顺序 |
(2)、select_type 查询类型,分为以下几种
- simple 简单查询,不包含子查询或者union
- Primary:查询中包含复杂的子部分,最外层的被标记为primary
- SUBQUREY 在select 或者where中包含了子查询
- Devired 临时表,子查询的结果有时会临时存放在一张
- Union:若第二个select出现在union之后,则被标记为union,若union包含在from子句中,外层的select被标记为Devired
- Union result:union结果的合并
(3)、Type表示mysql在表中找到所需的行的方式,或访问类型
system>const>eq_ref>ref>range>index>all>null性能从最好到最差
- System:表只有一行记录(等于系统表)
- Const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据+,所以很快,如将主键置于where列表中,mysql能将其转化为一个常量。主键索引和唯一索引
- eq|_ref: 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,-常见主键和唯一性索引比如返回每个公司的ceo(刚好是索引查询,恰好只有一条记录)
- Ref: 非唯一性索引扫描,返回匹配单独值的所有行,本质上也是一种索引访问,他返回所有匹配值的单独行,然而可能会找到匹配的条件的多行
- Range:范围查询,不用全表扫描
- Index: index与all的区别是,index只遍历索引树,通常比all要快,因为索引文件通常小于数据文件 两者都是读全表,index从索引文件中取数据,all从磁盘中去数据
- All:全表扫描
(4)、Possible_keys:显示可能运用在这张表表中的索引 一个或多个 但不一定在查询中能使用到。
(5)、key:实际用到的索引 如果为null则没有被使用 查询中若使用了覆 盖索引覆盖索引:create index ind_col1_col2 on mytable(col1,col2)
Select col1,col2 from mytable
(6)、Key_len : 表示索引中使用的字节数
(7)、Ref:显示索引的那一列被使用,如果可能话,是一个常数(有没有用到索引,体现在哪几个列上)
(8)、Rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
(9)、Extra 要的信息
- using filesort:说明mysql会对数据使用一个额外的索引排序,而不是按照表内的索引顺序进行读取,MYSQL中无法利用索引完成的排序操作成为文件排序;
- using temporary 使用了临时表保存中间结果 mysql对结果排序时使用临时表,常见order by 和group by
- using index 使用了覆盖索引 如果同时出现using where 表明索引被用来执行索引的查找,如果同时没有出现using where 表明索引用来读取数据而非执行查找动作
- using where使用where过滤
- Using join buffer 使用了连接缓存
- Impossible where:表示where字句总是false select * from table where name=“zhangsan” and name=“李四”
3.3.4、两个简单实用的优化方法
- 定期分析和检查表
- 定期优化表
四、案例分析
4.1、单表优化案例
- 创建表
create table article( id int(10) 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, comment int(10) unsigned not null, title varchar(225) not null, content text not null );
- 插入数据
Insert into article(author_id,category_id,views, comment1,title,content )values(1,1,1,1,'1','1'),(2,2,2,2,'2','2'),(1,1,1,3,'3','3');
- 创建索引:create index index_ccv on article(category_id,comment1)
当前的索引并不合适解决了全表扫描问题。
出现文件排序问题using filesort产生的原因:按照b-tree的工作原理先排序category_id 如果遇到相同的category_id在按排序comments 如果遇到相同的comments,在排序views 当comments出现在中间位置且 > 1是一个范围值 mysql无法满足后面的索引,views导致出现using filesort
所以删除当前索引 寻找更优的解决方案 drop index indx_ccv on article
- 创建索引:create index indx_cv on article(category_id, views)
排序和查找都用到了索引
4.2、双表优化案例
- explain select * from class left join book on class.card = book.card;
- 在左表添加索引 create index indx_card on class(card)
- 在右边加索引 create index_card on book(card);
通过以上对比:由于左连接的特性决定左表得到字段全有,所以右边是关键点,一定要建索引。
4.3、三表优化案例
- explain select * from class left join book on class.card = book.card left join phone on book.card = phone.card;
- 分别给book,phone创建索引
性能有很大的提升Join的优化 尽可能减少join语句中的NestedLoop的循环次数 永远小结果驱动大结果,保证Join语句中被驱动的表上join字段已经被索引,在上述案列中book,和phone都是被驱动的表。
4.4、避免索引失效
Create table staffs( Id int primary key auto_increment,Name varchar(24) not null, Age int not null default 0,Pos varchar(24) not null,Add_time timestamp not null) charset utf8Insert into staffs(name,age,pos,add_time) values(‘z3’ 22,’manager’,NOW());Insert into staffs(name,age,pos,add_time) values(‘JULY’ 22,’DEV’,NOW());Insert into staffs(name,age,pos,add_time) values(‘2000’ 22,’TEST’,NOW());
- 创建索引create index index_name_age_pos on staffs(Name ,age,pos)
(1)全值匹配我最爱
(2)索引失效情况:最左匹配原则
(3)不要在索引列上做任何的操作(计算,函数,自动或手动数据类型转换) 导致索引失效而转向全表扫描
(4)存储索引不能使用范围右边的列(范围查找导致索引失效) > >= != like
(5)尽量使用覆盖索引,避免使用select * from 按需取数据,!= <>会导致索引失效 而全表扫描
(6)is null 和 is not null 也无法使用索引
(7)like 模糊查询
Select * from staffs where name = ‘%July’; 全表扫描。 Select * from staffs where name = ‘July%’ range索引不会失效。
解决 %字符串% 索引失效的问题 使用覆盖索引来保证索引不会失效。小总结 字符串不加单引号索引会失效, 自动转换(强制类型转换)。少用or,用它来连接时会索引失效。
(8)索引优化总结口诀
全值匹配我最爱,最左前缀要遵守。带头大哥不能死,中间兄弟不能短。索引列上少计算,范围之后全失效。LIKE百分写最右,覆盖索引不写*。不等空值还有or,索引失效要不能用。VAR引号不能丢(相当于进行了强制转换),
(9)常见的索引失效分析
五、总结
mysql索引知识多而复杂,这是之前学习的笔记,加以整理,可能还有很多东西没有说到,也有可能很多地方说的不对或者模糊请海涵,感谢郑同学之前的提醒,使我重新捡起这部分,并纠正了之前的一些误区。