MySQ索引优化
## MySQL整体架构
mysql是一个优秀的数据库,其最大的优点就是基于热插拔方式的存储引擎,可以根据需要切换,同时也可以对其进行修改,从总体结构来看,也可以把其分为连接层,服务层,引擎层层和存储层,我们平时编写sql就是再服务层。
各个层的功能如下
mysql引擎很多,最主要的是innodb和myisam,其主要区别如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5iTBXGem-1640514810838)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201025221033854.png)]
MySQL索引
为什么需要索引
mysql的数据是存在磁盘上的的,其io性能很差,另外对于关系型数据库来说,在大数量的情况下,最好的情况是依次就能查到目标值,最坏需要全表扫描,平均来说查找时间还是很长,因此为了能够快速查找数据,因此需要建立索引,索引是一个以二叉树为基础的多叉有序树(B和B+树),由于采用二分查找算法,依次每次都能排除一般的数据,查询高效。
MySQL索引分类
mysql常见索引如下以及相关特性:
唯一索引:该索引字段值唯一。
单值索引:该索引这设置在一个字段上。
组合索引(复合索引):由多个字段组成的索引。
覆盖索引:查出的字段刚好和索引字段对应。
聚集索引:叶子节点存储数据(B+树)。
非聚集索引:叶子节点不存储数据(B树)
索引建立原则
- 主键和外键需要建立索引。
- 有一定数据量的情况下才需要建立索引(例如300达到300万)
- 业务中经常用来查询的字段需要建立索引。
- 经常更新的字段上不宜建立索引,因为需要重新构建索引树。
- 数据重复太多的字段没必要建立索引,例如字典项。
SQL的解析顺序
正常来说我们编写的sql如下
、mysql解析器是按照如下顺序解析的
Explain详解
在工作中,我们常常编写的是sql性能低下,比如运行时间过长等等,这时候当我们利用相关工具检测出具体时哪些sql时,这时候需要找到具体存在的问题进行优化,而explain就可以做这个事情,它可以帮助我们发现是否使用了索引查询等指标,从而帮助我们过更好的优化。例如对于存在的dept表:
使用expla分析语句SELECT * from dept WHERE dname = ‘开发部’ and dage >8
expain出来的信息有10列,分别是id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、Extra。
各字段概要信息如下
- id:选择标识符
- select_type:表示查询的类型。
- table:输出结果集的表
- partitions:匹配的分区
- type:表示表的连接类型
- possible_keys:表示查询时,可能使用的索引
- key:表示实际使用的索引
- key_len:索引字段的长度
- ref:列与索引的比较
- rows:扫描出的行数(估算的行数)
- filtered:按表条件过滤的行百分比
- Extra:执行情况的描述和说明
各字段详细信息如下
1.id
SELECT识别符。这是SELECT的查询序列号,其执行顺序如下:
- id相同时,执行顺序由上至下
- 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
- id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
2.select_type
表示查询中每个select子句的类型
- SIMPLE(简单SELECT,不使用UNION或子查询等)
- PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
- UNION(UNION中的第二个或后面的SELECT语句)
- DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
- UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)
- SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)
- DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)
- DERIVED(派生表的SELECT, FROM子句的子查询)
- UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
3.table
显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是简称
4.type
对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。
常用的类型有: **ALL、index、range、 ref、eq_ref、const、system、**NULL(从左到右,性能从差到好)
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行。
index: Full Index Scan,index与ALL区别为index类型只遍历索引树。
range:只检索给定范围的行,使用一个索引来选择行。
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件。
const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system。
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
5.possible_keys
指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。
6.key
显示MySQL实际决定使用的键(索引),必然包含在possible_keys中。
7.key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)。不损失精确性的情况下,长度越短越好 。
8.ref
列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
9.rows
估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数。
10.Extra
该列包含MySQL解决查询的详细信息,有以下几种情况:
**Using where:**不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
**Using temporary:**表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by
**Using filesort:**当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”g join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)。
Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
No tables used:Query语句中使用from dual 或不含任何from子句
黑色加粗是常用的。
SQL优化
sql查询慢的主要原因是没有合理的使用索引,而索引的主要功能是查找和排序,所以也是针对这两部分优化,例如对于where条件的优化和order by的优化,假如一个索引是由a、b、c三个字段顺序组成,对于where条件的优化,其优化的主要原则如下:
1.最左原则,即where后面的条件要从a开始,否则将会导致索引失效而进行全盘扫描。
2.范围查询导致后面索引会失效,比如说b是一个范围查询,那么就会导致c失效。
3.遵循小表驱动大表原则(即先查询小表),合理使用in和exist,因为in是先加载嵌套部分,再加载外面的,而exist正好相反,根据各自特点合理使用。
4.使用非‘b%’的模糊查询会导致索引失效。
5.进行函数计算时会导致索引实现,比如说一个字符串索引,在查询时未加单引号(数值),此时会导致索引失效,因为会调用底层转换函数。
6.使用不等于(!=或者<>)会导致索引失效。
7.使用is null或者is not null 会导致索引失效。
8.尽量使用覆盖索引和全值匹配。
order优化
异常SQL诊断
当遇到查询慢的情况,如何找出是那些sql导致的呢,常用的有两种方式,慢查询日志和show profile,慢查询日志能帮助我们找出哪些sql的运行时间超过设置的指定时间,而show profile则能分析出整个sql执行生命周期在各个阶段的耗时。
慢查询日志
- 查看是否开启慢查询功能:
mysql> show variables like 'slow_query%';
+---------------------+------------------------------------+
| Variable_name | Value |
+---------------------+------------------------------------+
| slow_query_log | OFF |
| slow_query_log_file | /var/lib/mysql/instance-1-slow.log |
+---------------------+------------------------------------+
2 rows in set (0.01 sec)
mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)
说明:
slow_query_log 慢查询开启状态
slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录)
long_query_time 查询超过多少秒才记录
配置
临时配置
默认没有开启慢查询日志记录,通过命令临时开启:
mysql> set global slow_query_log='ON';
Query OK, 0 rows affected (0.00 sec)
mysql> set global slow_query_log_file='/var/lib/mysql/instance-1-slow.log';
Query OK, 0 rows affected (0.00 sec)
mysql> set global long_query_time=2;
Query OK, 0 rows affected (0.00 sec)
永久配置
修改配置文件达到永久配置状态:
/etc/mysql/conf.d/mysql.cnf
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/lib/mysql/instance-1-slow.log
long_query_time = 2
配置好后,重新启动 MySQL 即可。
测试
通过运行下面的命令,达到问题 SQL 语句的执行:
mysql> select sleep(2);
+----------+
| sleep(2) |
+----------+
| 0 |
+----------+
1 row in set (2.00 sec)
然后查看慢查询日志内容:
$ cat /var/lib/mysql/instance-1-slow.log
/usr/sbin/mysqld, Version: 8.0.13 (MySQL Community Server - GPL). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
/usr/sbin/mysqld, Version: 8.0.13 (MySQL Community Server - GPL). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
# Time: 2018-12-18T05:55:15.941477Z
# User@Host: root[root] @ localhost [] Id: 53
# Query_time: 2.000479 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0
SET timestamp=1545112515;
select sleep(2);
通过慢查询日志找出哪些sql存在问题后,再结合explain进行优化。
show profile
步骤分析
MySQL锁
常见分类
**读锁(共享锁)**是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
**写锁(排他锁)**如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
表锁:对整个表加锁。
行锁:对行数据加锁。
表锁、行锁和读写锁只是从两个维度划分的,可以共存的,例如只是针对某个表或者某个记录加读写锁。
优化策略
参考:
https://www.cnblogs.com/tufujie/p/9413852.html
https://www.cnblogs.com/magic-chenyang/p/10557002.html