一、Mysql性能介绍
1、什么是Mysql?它有什么优点?
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。MySQL是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
- Mysql是开源的,所以你不需要支付额外的费用。
- Mysql支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
- MySQL使用标准的SQL数据语言形式。
- Mysql可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。
- Mysql对PHP有很好的支持,PHP是目前最流行的Web开发语言。
- MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。
- Mysql是可以定制的,采用了GPL协议,可以修改源码来开发自己的Mysql系统。
影响Mysql性能的原因总结一下 主要在下面这几点
- 服务器硬件
- 网卡流量
- 磁盘IO
- SQL查询速度
服务器硬件
CPU: 64位的CPU一定要工作在64位的系统下 对于并发比较高的 场景,CPU的数量比频率重要 对于密集型场景和复杂SQL,则CPU频率越高越好 内存: 选择主板所能使用的最高频率的内存 内存的大小对于性能很重要,所以尽可能的大 I/O子系统: PCIe -> SSD ->Raid10 ->磁盘 ->SAN
2、Mysql的查询过程
![《MySQL监控调优》](https://i-blog.csdnimg.cn/blog_migrate/b918a2e971fcf686a97f4fb7edae7f17.png)
![《MySQL监控调优》](http://www.limlhome.cn/blog/wp-content/uploads/2018/04/mysql%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B.png)
3、Mysql架构
DDL (Data Definition Language 数据定义语言)
create table 创建表
alter table 修改表
drop table 删除表
truncate table 删除表中所有行
create index 创建索引
drop index 删除索引
DML (Data Manipulation Language 数据操作语言)
insert 将记录插入到数据库
update 修改数据库的记录
delete 删除数据库的记录
4、Mysql连接数
MYSQL数据库默认最大连接数是100,然而对于流量稍微大一点的论坛或网站这个连接数是远远不够的,当并发数过大的时候会出现连接数不够用,使得很多线程在等待其他连接释放,会直接导致导致数据库连接超时或者响应时间过长,所以需要调整最大连接数。
# 重新设置数据库最大连接数
set global max_connections=1000
# 查询数据库当前设置的最大连接数
show variables like '%max_connections%';
MariaDB [(none)]> show variables like '%max_connections%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| extra_max_connections | 1 | # extra_port可以接受的最大连接数
| max_connections | 151 | # 最大连接数
+-----------------------+-------+
2 rows in set (0.00 sec)
# extra_port是之前5.6版本开始新增的,指定了可以使用的端口
# 服务器响应的最大连接数 可以用来预测数据库的最大连接数应该设置为多少
show global status like 'Max_used_connections';
MariaDB [(none)]> show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| Max_used_connections | 4 |
+----------------------+-------+
1 row in set (0.00 sec)
# 服务器线程方面的
show status like 'Threads%';
MariaDB [(none)]> show status like 'Threads%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 0 | # mysql管理的线程池中还有多少可以被复用的资源
| Threads_connected | 3 | # 打开的连接数,一定是大于running的
| Threads_created | 226 | # 表示创建过的线程数
| Threads_running | 1 | # 激活的连接数,这个数值一般远低于connected数值,效率较高的时候是和连接数是差不多的
# 准确的来说,Threads_running是代表当前并发数
+-------------------+-------+
4 rows in set (0.00 sec)
5、Mysql缓存
数据库属于 IO 密集型的应用程序,其主要职责就是数据的管理及存储工作。而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级。所以,要优化数据库,首先第一步需要优化的就是 IO,尽可能将磁盘IO转化为内存IO。
缓存分为两个维度,查询缓存Query cache和存储引擎InnoDB_Buffer_Pool
- 查询缓存Query cache
查询缓存会缓存完整的SELECT查询结果,当查询命中缓存时MySQL会将结果立刻返回,直接跳过了解析、优化和执行阶段。
当然,Query Cache 也有一个致命的缺陷,那就是当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache 中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache 可能会得不偿失。
因此,应用程序不需要关心MySQL是通过缓存查询出的结果还是实际执行过SQL语句返回的结果,因为这两种结果是完全相同的。
从前面的数据库执行过程图中可以看到,执行一条SQL查询语句会先查询该语句是否存在于缓存中,需要注意的是当语句中的字符大小写或注释只要有一点点的不同,查询缓存就会被认为是不同的查询,导致无法命中查询缓存。另外,对于不确定的函数,如:now()、current_date()等这种查询都不会被缓存。
既然查询缓存的有可以改善性能的优点,自然也有自己的缺点,主要体现在当开启了查询缓存时对于读写操作都增加了额外的开销。相对于读,再查询开始前需要先检查缓存,而对于写,则是当写入数据后需要更新缓存。
- Query cache的参数:
查询缓存参数,在mysql配置文件中添加,linux下为my.cnf,windows下为my.ini:
配置完之后,重启mysql
# 1.是否开启查询缓存,具体选项是off,on query_cache_type = on # 2.分配给查询缓存的总内存,一般建议不超过256M query_cache_size = 200M # 3.这个选项限制了MySQL存储的最大结果。如果查询的结果比这个大,那么就不会被缓存。 query_cache_limit = 1M # 查询qcache状态: MariaDB [(none)]> show variables like '%query_cache%'; +------------------------------+---------+ | Variable_name | Value | +------------------------------+---------+ | have_query_cache | YES | #该MySQL 是否支持Query Cache | query_cache_limit | 1048576 | #缓存块大小,超过该大小不会被缓存 | query_cache_min_res_unit | 4096 | #每个qcache最小的缓存空间大小 | query_cache_size | 1048576 | #分配给查询缓存的总内存 | query_cache_strip_comments | OFF | #用于控制QC中是否去掉SQL语句的注释部分。 | query_cache_type | OFF | #是否开启 | query_cache_wlock_invalidate | OFF | #控制当有锁加在表上的时候,是否先让该表相关的缓存失效 +------------------------------+---------+ 7 rows in set (0.00 sec)
修改完配置后,让我们再来监控Qcache的使用情况。
# 查询qcache当前使用情况: SHOW STATUS LIKE 'Qcache%'; MariaDB [(none)]> show status like 'Qcache%'; +-------------------------+---------+ | Variable_name | Value | +-------------------------+---------+ | Qcache_free_blocks | 1 | # 表示查询缓存中处以重现状态的内存块数(碎片数量)。如果 # Qcache_free_blocks 的值较大,则意味着查询缓存中碎片比较多 # 表明查询结果集较小,此时可以减小query_cache_min_res_unit # 使用flush query cache 会对缓存中的若干个碎片进行整理,从 # 而得到一个比较大的空闲块。 | Qcache_free_memory | 1031336 | # Query Cache 中目前剩余的内存大小 | Qcache_hits | 0 | # 缓存命中次数 | Qcache_inserts | 0 | # 多少次未命中然后插入 | Qcache_lowmem_prunes | 0 | # 因为查询缓存已满而溢出,导致MySQL删除的查询结果个数。 # 如果该值比较大,则表明查询缓存过小。 | Qcache_not_cached | 0 | # 没有进入查询缓存的select个数 | Qcache_queries_in_cache | 0 | # 查询缓存中缓存中有多少条select语句的结果集 | Qcache_total_blocks | 1 | # 查询缓存的总个数 +-------------------------+---------+ 8 rows in set (0.00 sec) Query Cache 命中率= Qcache_hits / ( Qcache_hits + Qcache_inserts );
- 存储引擎层-innodb buffer pool
buffer pool是innodb存储引擎带的一个缓存池,查询数据的时候,它首先会从内存中查询,如果内存中存在的话,直接返回,从而提高查询响应时间。
innodb buffer pool和qcache的区别是:qcacche缓存的是sql语句对应的结果集,buffer pool中缓存的是表中的数据。Buffer pool是设置的越大越好,一般设置为服务器物理内存的70%。
innodb_buffer_pool_size = 16M # Innodb_buffer_pool的大小 innodb_buffer_pool_dump_now: 默认为关闭OFF。如果开启该参数,停止MySQL服务时,InnoDB将InnoDB缓冲池中的热数据保存到本地硬盘。 innodb_buffer_pool_load_at_startup:默认为关闭OFF。如果开启该参数,启动MySQL服务时,MySQL将本地热数据加载到InnoDB缓冲池中。
- 查看Innodb_buffer_pool状态
查询Innodb_buffer_pool状态:
SHOW VARIABLES LIKE '%innodb_buffer_pool%';
MariaDB [(none)]> SHOW VARIABLES LIKE '%innodb_buffer_pool%';
+-------------------------------------+----------------+
| Variable_name | Value |
+-------------------------------------+----------------+
| innodb_buffer_pool_dump_at_shutdown | OFF | ##停止mysq服务时是否自动保存热数据
| innodb_buffer_pool_dump_now | OFF | ##启动mysql服务时是否自动读取热数据
| innodb_buffer_pool_dump_pct | 100 | #表示转储每个bp instance LRU上最热的page的百分比。
| innodb_buffer_pool_filename | ib_buffer_pool | #热数据文件名称
| innodb_buffer_pool_instances | 8 | # 表示InnoDB缓存池被划分到多少个区域
| innodb_buffer_pool_load_abort | OFF | # 立刻中断LOAD操作
| innodb_buffer_pool_load_at_startup | OFF | # 启动实例时读入转储文件中记录的Page
| innodb_buffer_pool_load_now | OFF | # 立即做一次转储文件读入
| innodb_buffer_pool_populate | OFF |
| innodb_buffer_pool_size | 16777216 | ##设置的bp大小
+-------------------------------------+----------------+
10 rows in set (0.01 sec)
- 监控Innodb_buffer_pool使用情况
查询Innodb_buffer_pool当前使用情况: SHOW STATUS LIKE '%Innodb_buffer_pool%'; MariaDB [(none)]> SHOW STATUS LIKE '%Innodb_buffer_pool%'; +-----------------------------------------+----------------------------------------+ | Variable_name | Value | +-----------------------------------------+----------------------------------------+ | Innodb_buffer_pool_bytes_data | 4194304 | | Innodb_buffer_pool_bytes_dirty | 0 | | Innodb_buffer_pool_dump_status | Dumping buffer pool(s) not yet started | | Innodb_buffer_pool_load_status | Loading buffer pool(s) not yet started | | Innodb_buffer_pool_pages_data | 256 | | Innodb_buffer_pool_pages_dirty | 0 | | Innodb_buffer_pool_pages_flushed | 6186 | | Innodb_buffer_pool_pages_free | 765 | | Innodb_buffer_pool_pages_lru_flushed | 0 | | Innodb_buffer_pool_pages_made_not_young | 4584 | | Innodb_buffer_pool_pages_made_young | 0 | | Innodb_buffer_pool_pages_misc | 2 | | Innodb_buffer_pool_pages_old | 0 | | Innodb_buffer_pool_pages_total | 1023 | | Innodb_buffer_pool_read_ahead | 831 | | Innodb_buffer_pool_read_ahead_evicted | 0 | | Innodb_buffer_pool_read_ahead_rnd | 0 | | Innodb_buffer_pool_read_requests | 1758225 | | Innodb_buffer_pool_reads | 8144 | | Innodb_buffer_pool_wait_free | 0 | | Innodb_buffer_pool_write_requests | 13403 | +-----------------------------------------+----------------------------------------+ 21 rows in set (0.00 sec) 主要关注的两个参数 innodb_buffer_pool_read_requests 总共查询bp的次数 innodb_buffer_pool_reads 从物理磁盘中获取到数据的次数
6、事务和锁
事务就是一组原子性的SQL查询,或者也可以说一个独立的工作单元。如果数据库引擎可以成功的对数据库应用该组查询的全部语句,那么就执行该组查询。如果其中任意一条语句因为某种原因无法执行,那么其他的语句都不会执行。也就是说,事务内的语句要么全部都执行,要么全部执行失败。
- 表级锁和行级锁
行级锁又分共享锁和排他锁。
共享锁:共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。
SELECT `Id` FROM Score WHERE Id in (1,2); LOCK IN SHARE MODE 结果集的数据都会加共享锁
排他锁:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。
SELECT `Id` FROM Score WHERE Id=1 FOR UPDATE
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点
开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
- 死锁
我们在测试中经常会遇到数据库死锁的情况,那么什么是死锁呢? 死锁是指两个或者多个事务在同一资源上互相占用,并且请求锁定对方占用的资源,从而导致恶性循环的现象。
事务1:
START TRANSACTION;
UPDATE score set grade = 95 where id = 5;
UPDATE score set grade = 87 where id = 6;
COMMIT;
事务2:
START TRANSACTION;
UPDATE score set grade = 78 where id = 6;
UPDATE score set grade = 85 where id = 5;
COMMIT;
当两个事务同时执行,每个事务都执行了第一条update语句,更新了一行数据的同时也会锁定该行数据,接下来每个事务会尝试去执行第二条update语句,在这时会发现要修改的这条数据已经被锁定了,然后两个事务都在互相等待对方释放锁,同时又都持有对方所需要的锁,在这样的情况下就陷入了死循环。也就是死锁了,对于出现死锁的情况下,只有通过外部因素来介入来解除死锁。
当然既然存在有死锁的情况,自然也就有一些解决问题的方法,数据库系统实现了各种死锁检测和死锁超时机制。比如InnoDB存储引擎,就能检测到死锁的循环依赖,并且立即会返回一个错误。
锁的行为和顺序是和存储引擎相关的,以同样的顺序去执行语句时,有些存储引擎会出现死锁,有些就不会。因此死锁的产生有双重原因,有些是因为真正的数据冲突,有些则是因为存储引擎的实现方式导致。
如何查看死锁呢?
查看是否死锁:
show engine innodb status;(一般日志里有dblock、lock等字样)
SELECT * FROM information_schema.INNODB_TRX; (定位哪个线程导致死锁)
show processlist;
KILL xxxx;
- 多线程并发才有可能死锁
- 避免交叉加锁
- 减少涉及的表,表联接会大大增加锁范围
- 避免全表更新,控制更新行数
二、Mysql慢查询
开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。顾名思义,慢查询日志中记录的是执行时间较长的query,也就是我们常说的slowquery,通过设–log-slow-queries[=file_name]来打开该功能并设置记录位置和文件名。
慢查询日志采用的是简单的文本格式,可以通过各种文本编辑器查看其中的内容。其中记录了语句执行的时刻,执行所消耗的时间,执行用户,连接主机等相关信息。MySQL 还提供了专门用来分析满查询日志的工具程序mysqlslowdump,用来帮助数据库管理人员解决可能存在的性能问题。
- 查看慢查询相关参数
MariaDB [(none)]> show variables like 'slow_query%';
+---------------------+---------------+
| Variable_name | Value |
+---------------------+---------------+
| slow_query_log | OFF |
| slow_query_log_file | liml-slow.log |
+---------------------+---------------+
2 rows in set (0.00 sec)
MariaDB [(none)]> 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 查询超过多少秒才记录,默认为10秒
- 配置Mysql慢查询(通过配置文件)
Linux:
在mysql配置文件my.cnf中增加:
[mysqld]
slow_query_log = ON
slow_query_log_file = /opt/logs/slow.log
long_query_time=2
Windows:
在my.ini的[mysqld]添加如下语句:
log-slow-queries = E:\web\mysql\log\mysqlslowquery.loglong_query_time = 2
(其他参数如上)
slow_query_log_file (指定日志文件存放位置,可以为空,系统会给一个缺省的文件host_name-slow.log)
long_query_time(记录超过的时间,默认为10s)
slow_query_log 记录慢查询日志开关
- 使用命令启动MySQL慢查询
将 slow_query_log 全局变量设置为“ON”状态
mysql> set global slow_query_log='ON';
设置慢查询日志存放的位置
mysql> set global slow_query_log_file='/opt/slow.log';
查询超过1秒就记录
mysql> set global long_query_time=1;
- Mysqldumpslow命令
mysqldumpslow -s c -t 10 /database/mysql/slow-log
#这会输出记录次数最多的10条SQL语句,其中:
-s, 是表示按照何种方式排序,c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序,ac、at、al、ar,表示相应的倒叙;
-t, 是top n的意思,即为返回前面多少条的数据;
-g, 后边可以写一个正则匹配模式,大小写不敏感的;
- explain
前面分析出了具体是哪一条SQL导致的查询速度过慢,那么我们该如何针对这些占用资源严重的SQL来进一步的分析呢??答案就是使用explain
Explain :该命令是查看查询优化器如何决定执行查询的主要方法,这个功能有局限性,只是一个近似结果,有时它是一个很好的近似,有时可能相差甚远。但它的输出是可以获取的最准确信息,值得仔细学习。使用方法如下:
MariaDB [besttest]> select * from students where id in (801,802,803);
+-----+-----------+------+------+--------+--------------------+
| Id | Name | Sex | age | class | Addr |
+-----+-----------+------+------+--------+--------------------+
| 801 | 安大叔 | 男 | 20 | besttest| 北京市海淀区 |
| 802 | 小胖子 | 男 | 38 | besttest| 北京市昌平区 |
| 803 | 小楠楠 | 男 | 18 | besttest| 湖南省永州市 |
+-----+-----------+------+------+--------+--------------------+
3 rows in set (0.00 sec)
MariaDB [besttest]> explain select * from students where id in (801,802,803);
+------+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | students | range | PRIMARY | PRIMARY | 4 | NULL | 3 | Using where |
+------+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)
explain的列:
我们先完成一个建表
CREATE TABLE `t1` (`id` int NOT NULL AUTO_INCREMENT ,`name` varchar(255) NULL ,`age` INT(3) NULL,PRIMARY KEY (`id`));
CREATE TABLE `t2` (`id` int NOT NULL AUTO_INCREMENT ,`name` varchar(255) NULL ,`age` INT(3) NULL,PRIMARY KEY (`id`));
CREATE TABLE `t3` (`id` int NOT NULL AUTO_INCREMENT ,`name` varchar(255) NULL ,`age` INT(3) NULL,PRIMARY KEY (`id`));
1. id:
包含一组数字,表示查询中执行select子句或操作表的顺序(id相同,执行顺序由上至下),如果是子查询,id值越大优先级越高,越先被执行
explain select t2.* from t2 where id =
(select id from t1 where id = (select t3.id from t3 where t3.name=''));
MariaDB [besttest]> explain select t2.* from t2 where id = (select id from t1 where id = (select t3.id from t3 where t3.name=''));
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
| 2 | SUBQUERY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | no matching row in const table |
| 3 | SUBQUERY | t3 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
3 rows in set (0.00 sec)
如果ID相同,则按照顺序从上向下
MariaDB [besttest]> explain select t2.* from (select t3.id from t3 where t3.name='')s1, t2 where s1.id=t2.id;
+------+-------------+-------+--------+---------------+---------+---------+----------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+--------+---------------+---------+---------+----------------+------+-------------+
| 1 | SIMPLE | t3 | ALL | PRIMARY | NULL | NULL | NULL | 1 | Using where |
| 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | besttest.t3.id | 1 | |
+------+-------------+-------+--------+---------------+---------+---------+----------------+------+-------------+
2 rows in set (0.00 sec)
2. select_type
示查询中每个select子句的类型(简单OR复杂)
- SIMPLE:查询中不包含子查询或者UNION
- 查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
- 在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY
- 在FROM列表中包含的子查询被标记为:DERIVED(衍生)用来表示包含在from子句中的子查询的selectmysql会递归执行并将结果放到一个临时表中。服务器内部称为”派生表”,因为该临时表是从子查询中派生出来的
- 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
- 从UNION表获取结果的SELECT被标记为:UNION RESULT
SUBQUERY和UNION还可以被标记为DEPENDENT和UNCACHEABLE。
DEPENDENT意味着select依赖于外层查询中发现的数据。
UNCACHEABLE意味着select中的某些 特性阻止结果被缓存于一个item_cache中。
MariaDB [besttest]> explain select d1.name, ( select id from t3) d2 from (select id,name from t1 where name='')d1 union (select name,id from t2);
+------+--------------+------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+------------+-------+---------------+---------+---------+------+------+-------------+
| 1 | PRIMARY | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
| 2 | SUBQUERY | t3 | index | NULL | PRIMARY | 4 | NULL | 1 | Using index |
| 4 | UNION | t2 | ALL | NULL | NULL | NULL | NULL | 1 | |
| NULL | UNION RESULT | <union1,4> | ALL | NULL | NULL | NULL | NULL | NULL | |
+------+--------------+------------+-------+---------------+---------+---------+------+------+-------------+
4 rows in set (0.00 sec)
3. type
表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型如下:
ALL, index, range, ref, eq_ref, const, system, NULL
从左到右,性能从最差到最好
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
MariaDB [besttest]> explain select * from t1 where name='';
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
index:Full Index Scan,index与ALL区别为index类型只遍历索引树
MariaDB [besttest]> explain select id from t1;
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | index | NULL | PRIMARY | 4 | NULL | 1 | Using index |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)
range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行。索引范围扫描是带有between或者where子句里带有<, >查询。当mysql使用索引去查找一系列值时,例如IN()和OR列表,也会显示range(范围扫描),当然性能上面是有差异的。
MariaDB [besttest]> explain select * from t1 where id in (2,6);
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)
ref:使用非唯一索引扫描或者唯一索引的前缀扫描,返回匹配某个单独值的记录行
MariaDB [besttest]> explain select * from t1 where name = 'zhang';
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
eq_ref类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
MariaDB [besttest]> explain select t1.name from t1, t2 where t1.id=t2.id;
+------+-------------+-------+--------+---------------+---------+---------+----------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+--------+---------------+---------+---------+----------------+------+-------------+
| 1 | SIMPLE | t1 | ALL | PRIMARY | NULL | NULL | NULL | 1 | |
| 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | besttest.t1.id | 1 | Using index |
+------+-------------+-------+--------+---------------+---------+---------+----------------+------+-------------+
2 rows in set (0.00 sec)
const,system,当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
MariaDB [besttest]> explain select * from ( select * from t1 where id=1) b;
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
1 row in set (0.00 sec)
NULL,MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
MariaDB [besttest]> explain select * from t1 where id = (select min(id) from t2);
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
| 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
| 2 | SUBQUERY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No matching min/max row |
+------+-------------+-------+------+---------------+------+---------+------+------+-----------------------------------------------------+
2 rows in set (0.00 sec)
4. possible_keys
指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用
5. key
显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL
6. key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)
7. ref
表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
8. rows
表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
9. Extra
包含不适合在其他列中显示但十分重要的额外信息
a. Using index
该值表示相应的select操作中使用了覆盖索引(Covering Index)
MariaDB [besttest]> explain select id from t1;
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | index | NULL | PRIMARY | 4 | NULL | 1 | Using index |
+------+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)
b. Using where
表示mysql服务器将在存储引擎检索行后再进行过滤。许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where字句的查询都会显示”Using where”。有时”Using where”的出现就是一个暗示:查询可受益与不同的索引。
MariaDB [besttest]> explain select * from t1 where name = 'zhang';
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
c. Using temporary
表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
这个值表示使用了内部临时(基于内存的)表。一个查询可能用到多个临时表。有很多原因都会导致MySQL在执行查询期间创建临时表。两个常见的原因是在来自不同表的上使用了DISTINCT,或者使用了不同的ORDER BY和GROUP BY列。可以强制指定一个临时表使用基于磁盘的MyISAM存储引擎。这样做的原因主要有两个:
1)内部临时表占用的空间超过min(tmp_table_size,max_heap_table_size)系统变量的限制
2)使用了TEXT/BLOB 列
MariaDB [besttest]> explain select id from t1 where id in (1,2) group by age,name;
+------+-------------+-------+-------+---------------+---------+---------+------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+------+----------------------------------------------+
| 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where; Using temporary; Using filesort |
+------+-------------+-------+-------+---------------+---------+---------+------+------+----------------------------------------------+
1 row in set (0.00 sec)
d. Using filesort
MySQL中无法利用索引完成的排序操作称为“文件排序”
MariaDB [besttest]> explain select id,age from t1 order by name;
+------+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using filesort |
+------+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)
MariaDB [besttest]> explain select id,age from t1 order by age;
+------+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using filesort |
+------+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.00 sec)
e. Using join buffer
改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
MariaDB [besttest]> explain select t1.name from t1 inner join t2 on t1.name=t2.name;
+------+-------------+-------+------+---------------+------+---------+------+------+-------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------------------------------------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | |
| 1 | SIMPLE | t2 | ALL | NULL | NULL | NULL | NULL | 1 | Using where; Using join buffer (flat, BNL join) |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------------------------------------------+
2 rows in set (0.00 sec)
f. Impossible where
这个值强调了where语句会导致没有符合条件的行。
MariaDB [besttest]> EXPLAIN SELECT * FROM t1 WHERE 1=2;
+------+-------------+-------+------+---------------+------+---------+------+------+------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |
+------+-------------+-------+------+---------------+------+---------+------+------+------------------+
1 row in set (0.00 sec)
h. Select tables optimized away
这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行.
MariaDB [besttest]> explain select max(id) from t1;
+------+-------------+-------+------+---------------+------+---------+------+------+-------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No matching min/max row |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------------------+
1 row in set (0.00 sec)
I. Index merges
当MySQL 决定要在一个给定的表上使用超过一个索引的时候,就会出现以下格式中的一个,详细说明使用的索引以及合并的类型。
Using sort_union(…)
Using union(…)
Using intersect(…)
四、查询性能优化
首先,让我们来看一下SQL语句执行的过程,下面这个是我们平时写的SQL语句。
SELECT DISTINCT
< select_list >
FROM
< left_table > < join_type >
JOIN < right_table > ON < join_condition >
WHERE
< where_condition >
GROUP BY
< group_by_list >
HAVING
< having_condition >
ORDER BY
< order_by_condition >
LIMIT < limit_number >
然而当数据库接收到查询请求以后会先对数据进行解析,解析后查询顺序就变成了下面这样
先从磁盘取数据,然后存入内存,然后cpu再做过滤操作。
FROM <left_table>
ON <join_condition>
<join_type> JOIN <right_table>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
SELECT
DISTINCT <select_list>
ORDER BY <order_by_condition>
LIMIT <limit_number>
了解了真实的MySQL查询顺序后,我们再来看一看SQL的优化目标。
- 减少IO次数
IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当然,也是收效最明显的优化手段。
- 降低 CPU 计算
除了 IO 瓶颈之外,SQL优化中需要考虑的就是 CPU 运算量的优化了。order by, group by,distinct … 都是消耗 CPU 的大户(这些操作基本上都是 CPU 处理内存中的数据比较运算)。当我们的 IO 优化做到一定阶段之后,降低 CPU 计算也就成为了我们 SQL 优化的重要目标。
基于这个目标我们来看下优化的基本原则
- MySQL优化基本原则
1、选取最适用的字段类型,尽量避免浪费
MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。
例如,在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。同样的,如果可以的话,我们应该使用MEDIUMINT而不是BIGIN来定义整型字段。尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
另外一个提高效率的方法是在可能的情况下,应该尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
2、使用连接(JOIN)来代替子查询(Sub-Queries)
虽然Join 性能并不佳,但是和 MySQL 的子查询比起来还是有非常大的性能优势。MySQL 的子查询执行计划一直存在较大的问题,虽然这个问题已经存在多年,但是到目前已经发布的所有稳定版本中都普遍存在,一直没有太大改善。虽然官方也在很早就承认这一问题,并且承诺尽快解决,但是至少到目前为止我们还没有看到哪一个版本较好的解决了这一问题。 在MySQL5.6或者更新的版本或者是MariaDB可以忽略关于子查询方面的建议
3、使用联合(UNION)来代替手动创建的临时表
union查询,它可以把需要使用临时表的两条或更多的select查询合并的一个查询中。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了,要注意的是所有select语句中的字段数目要想同。
4、减少排序
排序操作会消耗较多的 CPU 资源,所以减少排序可以在缓存命中率高等 IO 能力足够的场景下会较大影响 SQL 的响应时间。 对于MySQL来说,减少排序有多种办法,比如:
- 通过利用索引来排序的方式进行优化
- 减少参与排序的记录条数
- 非必要不对数据进行排序
5、禁用外键
6、避免大sql
- 一个SQL只能在一个cpu上运行
- 高并发环境中,大SQL容易影响性能问题
- 可能一个大SQL把数据库搞死 拆分SQL
7、保持事物的短小精悍
- 即开即用,用完即关
- 无关操作踢出事务,减少资源占用
- 保持一致性的前提下,拆分事务
8、避免大批量更新
- 避开高峰
- 白天限制速度
- 加sleep
9、避免取过量数据,灵活使用limit
当系统需要进行分页操作的时候,尽量使用limit加上偏移量的方法来实现,同时配合order by的子句
10、避免在SQL 语句中进行数学运算、函数计算、逻辑判断等操作
尤其需要注意,不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11、避免OR
同一字段,推荐in 不同字段,推荐union
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
当然是用in 或者 not in的时候也需要谨慎一些
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
12、优先优化高并发的 SQL,而不是执行频率低某些“大”SQL
13、尽可能对每一条运行在数据库中的SQL进行explain。
14、任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
五、mysql索引
MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。
索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
建立索引会占用磁盘空间的索引文件。
1、普通索引
最普通的索引,所有列都可以加
# 创建索引 CREATE INDEX indexName ON mytable(username(length)); # 修改表结构(添加索引) ALTER table tableName ADD INDEX indexName(columnName) # 创建表的时候直接指定 CREATE TABLE mytable( ID INT NOT NULL, username VARCHAR(16) NOT NULL, INDEX [indexName] (username(length)) );
2、主键索引
建表的时候加的主键
3、组合索引
create index index_name on table_name (col,col2);
4、唯一索引
它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
# 创建索引 CREATE UNIQUE INDEX indexName ON mytable(username(length)) # 修改表结构 ALTER table mytable ADD UNIQUE [indexName] (username(length)) # 创建表的时候直接指定 CREATE TABLE mytable( ID INT NOT NULL, username VARCHAR(16) NOT NULL, UNIQUE [indexName] (username(length)) );
那么我怎么样知道我当前的表是否添加了索引呢?
SHOW INDEX FROM table_name; \G
六、优化MySQL服务设置
- 系统配置
系统配置一个指的是操作系统的配置,有一些操作系统的配置会影响mysql的性能,现在咱们大多数服务器都是用的linux服务器,linux上面一切东西都是基于文件的,mysql数据里面的表、数据等等都是文件存在磁盘上的。
linux系统有一个系统配置是文件打开的数量,默认是1024,也就是最多只能打开1024个文件,那在数据库里面表比较多、并发大的情况下,这1024就不够用了,要想获取数据就得打开文件,但是打开文件的数量最多就1024个,就会导致有一些数据获取不到,就得等待别的文件关闭之后,才能打开。那就要修改系统的配置,在/etc/security/limits.conf文件里面可以修改最大打开文件的数量。
vi /etc/security/limits.conf * soft nofile 65536 * hard nofile 65536
还有一些mysql配置参数会影响mysql的性能。
sleep超时时间
mysql的连接数是提前配置好的,如果程序里面代码写的不好,有一些数据库操作没有及时关闭数据库,那这个链接就不会释放会一直占用链接,这样子并发大的情况下,就会导致数据库连接数不够用了,就连接不上数据库了。mysql默认8小时不操作数据库才会自动关闭链接,所以这个sleep的超时时间会影响mysql的性能。
set global wait_timeout=600; 设置sleep的超时间,单位是秒 show variables like '%wait_timeout%'; 查询超时时间 MariaDB [(none)]> show variables like '%wait_timeout%'; +--------------------------+----------+ | Variable_name | Value | +--------------------------+----------+ | innodb_lock_wait_timeout | 50 | | lock_wait_timeout | 31536000 | | wait_timeout | 600 | +--------------------------+----------+ 3 rows in set (0.00 sec)
mysql5.6.6之前默认是共享的表空间,mysql5.6.6之后默认是开启了独立表空间的。
那什么是共享表空间呢?
就是说这个空间是所有的表都共享的,所有的表的数据都存在一个地方的。
你想一下,所有的货架都存在一个仓库里面的话,快递员去拿货的时候,人一多,可能进出都要排队,拿货的时候就比较慢了。
所以说共享表空间如果在数据量和并发量比较大的情况下,对IO的消耗是比较大的,影响性能。
共享表空间还有一个缺点就是不能自动收缩,自动收缩是什么意思呢,刚建表的时候,表里面数据很少,就1条数据,可能占用空间就几kb,到后来数据多了,占用了10个G的空间,然后发现有一些数据都是垃圾数据,删了5个G,那这个时候表空间就不会自动减小了,它还是10个G,浪费空间。
而独立表空间就是每个表的表空间都是独享的,用仓库这个例子就是每个货架都单独在一个房间里头,这样的话快递员去拿哪个东西,直接去那个房间里就好了,不用都挤在一个仓库里了。
而使用了独立的表空间,每个表都有自己的表空间,删了数据也会自动收缩,就不会有上面的问题了。
set global innodb_file_per_table =ON; 设置独立表空间打开 show variables like '%per_table%'; #查询是否打开独立表空间
读/写进程数配置
在mysql5.5之后读、写的进程数是可以配置的。默认读和写的进程数都是4个。
当然我们都知道,人多好干活嘛。进程多就是干活的人多,具体配置根据cpu的核数和业务逻辑来配置这两个值。
假如cpu是32核的,那么就是同时可以有32个进程在运行,就可以把这两个值给调大。
假如说是系统是一个内容类的网站,大多数操作都是读操作,那么就可以把读的进程数设置大一点,写的进程数设置的小一点。
怎么修改呢,找到mysql的配置文件,在[mysqld]节点下加入下面参数的即可
innodb_read_io_threads =5 读进程数 innodb_write_io_threads =3 写进程数
七、监控mysql
Spotlight on mysql客户端工具,有漂亮的ui界面,可以监控到mysql的io、qcache、连接数、buffer pool等等,还有预警的功能
Lepus,一个开源的国产监控平台,可以监控到mysql的慢查询、 qcache、连接数、buffer pool等等,可以同时监控多台,配置多个实例即可
Zabbix,也是一个开源的监控平台,和lepus类似,配置比较复杂