MySQL性能优化
一、为什么需要优化mysql数据库
1、避免出现页面访问错误
- 由于数据库连接超时产生的5xx错误
- 由于慢查询造成页面无法加载
- 由于阻塞造成数据无法提交
2、增加数据库的稳定性
- 很多数据库问题是由于低效的查询引起的
二、优化的思路
1.在数据库优化上有两个主要方向:即安全与性能。
安全 ---> 数据安全性
性能 ---> 数据的高性能访问
2.数据库优化分为四个纬度:
硬件,系统配置,数据库表结构,SQL及索引
硬件: CPU、内存、存储、网络设备等
系统配置: 服务器系统、数据库服务参数等
数据库表结构: 高可用、分库分表、读写分离、存储引擎、表设计等
Sql及索引: sql语句、索引使用等
3.MySQL查询流程
我们该如何进行sql优化呢, 首先我们需要知道,sql优化其实主要是解决查询的优化问题,所以我们先从数据库的查询开始入手,下面这幅图显示了查询的执行路径:
① 客户端将查询发送到服务器;
② 服务器检查查询缓存,如果找到了,就从缓存中返回结果,否则进行下一步。
③ 服务器解析,预处理。
④ 查询优化器优化查询
⑤ 生成执行计划,执行引擎调用存储引擎API执行查询
⑥服务器将结果发送回客户端。
查询缓存
在解析一个查询语句之前,如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据,如果命中缓存直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行。
语法解析和预处理器
MySQL通过关键字将SQL语句进行解析,并生成一棵对应的“解析树”。MySQL解析器将使用MySQL语法规则验证和解析查询。
查询优化器
语法书被校验合法后由优化器转成查询计划,一条语句可以有很多种执行方式,最后返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
查询执行引擎
在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎则根据这个执行计划来完成整个查询。最常使用的也是比较最多的引擎是MyISAM引擎和InnoDB引擎。mysql5.5开始的默认存储引擎已经变更为innodb了。
三、具体优化
1.查询优化
sql是我们和数据库交流最重要的部分,所以我们在调优的时候,需要花费的大量时间就在sql调优上面。常见的分析手段有慢查询日志,EXPLAIN 分析查询,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能 。
常见的分析手段有慢查询日志,EXPLAIN 分析查询,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。
1.1、慢查询日志
(1)慢查询日志开启
在配置文件my.cnf或my.ini中在[mysqld]一行下面加入两个配置参数
log-slow-queries=/data/mysqldata/slow-query.log
long_query_time=5
- log-slow-queries参数为慢查询日志存放的位置,一般这个目录要有mysql的运行帐号的可写权限,一般都将这个目录设置为mysql的数据存放目录;
- long_query_time=5中的5表示查询超过五秒才记录;
- 还可以在my.cnf或者my.ini中添加log-queries-not-using-indexes参数,表示记录下没有使用索引的查询
(2)慢查询分析
我们可以通过打开log文件查看得知哪些SQL执行效率低下 ,从日志中,可以发现查询时间超过5秒的SQL,而小于5秒的没有出现在此日志中。
如果慢查询日志中记录内容很多,可以使用mysqldumpslow工具(MySQL客户端安装自带)来对慢查询日志进行分类汇总。mysqldumpslow对日志文件进行了分类汇总,显示汇总后摘要结果。
进入log的存放目录,运行:
[root@mysql_data]# mysqldumpslow slow-query.log Reading mysql slow query log fromslow-query.log Count: 2 Time=11.00s (22s) Lock=0.00s (0s)Rows=1.0 (2), root[root]@mysql select count(N) from t_user;
mysqldumpslow命令
/path/mysqldumpslow -s c -t 10/database/mysql/slow-query.log
这会输出记录次数最多的10条SQL语句,其中:
-s, 是表示按照何种方式排序,c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序,ac、at、al、ar,表示相应的倒叙 -g, 后边可以写一个正则匹配模式,大小写不敏感的;
例如:
/path/mysqldumpslow -s r -t 10/database/mysql/slow-log
得到返回记录集最多的10个查询。
/path/mysqldumpslow -s t -t 10 -g “leftjoin” /database/mysql/slow-log得到按照时间排序的前10条里面含有左连接的查询语句。
使用mysqldumpslow命令可以非常明确的得到各种我们需要的查询语句,对MySQL查询语句的监控、分析、优化是MySQL优化非常重要的一步。开启慢查询日志后,由于日志记录操作,在一定程度上会占用CPU资源影响mysql的性能,但是可以阶段性开启来定位性能瓶颈。
1.2、EXPLAIN
可以帮助开发人员分析SQL问题,EXPLAIN显示了MySQL如何使用SQL执行计划,可以帮助开发人员写出更优化的查询语句。使用方法,在select语句前加上Explain就可以了:
EXPLAIN SELECT * FROM products
示例2:
结果的列的说明如下 :
1) id
SELECT识别符。这是SELECT查询序列号。这个不重要
2) select_type
表示SELECT语句的类型。
1. simple:简单select(不使用union或子查询)。 2. primary:最外面的select。 3. union:union中的第二个或后面的select语句。 4. dependent union:union中的第二个或后面的select语句,取决于外面的查询。 5. union result:union的结果。 6. subquery:子查询中的第一个select。 7. dependent subquery:子查询中的第一个select,取决于外面的查询。 8. derived:导出表的select(from子句的子查询)。
3) table
显示这查询的数据是关于哪张表的。
4) type
区间索引,这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为:
system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery >index_subquery > range > index > ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
1. system:表仅有一行,这是const类型的特列,平时不会出现,这个也可以忽略不计。 2. const:数据表最多只有一个匹配行,因为只匹配一行数据,所以很快 3. eq_ref:mysql手册是这样说的:"对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY"。eq_ref可以用于使用=比较带索引的列。 4. ref:查询条件索引既不是UNIQUE也不是PRIMARY KEY的情况。ref可用于=或<或>操作符的带索引的列。 5. ref_or_null:该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。在解决子查询中经常使用该联接类型的优化。 6. index_merge:该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。 7. unique_subquery:该类型替换了下面形式的IN子查询的ref: value IN (SELECT primary_key FROM single_table WHERE some_expr)unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。 8. index_subquery:该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引: value IN (SELECT key_columnFROM single_table WHERE some_expr) 9. range:只检索给定范围的行,使用一个索引来选择行。 10. index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。 11. ALL:对于每个来自于先前的表的行组合,进行完整的表扫描。(性能最差)
5) possible_keys
指出MySQL能使用哪个索引在该表中找到行。如果是空的,没有相关的索引。这时要提高性能,可通过检验WHERE子句,看是否引用某些字段,或者检查字段不是适合索引。
6) key
实际使用到的索引。如果为NULL,则没有使用索引。如果为primary的话,表示使用了主键。
7) key_len
最长的索引宽度。如果键是NULL,长度就是NULL。在不损失精确性的情况下,长度越短越好。
8) ref
显示使用哪个列或常数与key一起从表中选择行。
9) rows
显示MySQL认为它执行查询时必须检查的行数。
10)filtered -
显示了通过条件过滤出的行数的百分比估计值。
11) Extra
执行状态说明,该列包含MySQL解决查询的详细信息
Distinct:MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。 Not exists:MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。 range checked for each record (index map: #):MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用。 Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。 Using index:从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息。 Using temporary:为了解决查询,MySQL需要创建一个临时表来容纳结果。 Using where:WHERE 子句用于限制哪一个行匹配下一个表或发送到客户。 Using sort_union(...), Using union(...), Using intersect(...):这些函数说明如何为index_merge联接类型合并索引扫描。 Using index for group-by:类似于访问表的Using index方式,Using index forgroup-by表示MySQL发现了一个索引,可以用来查 询GROUP BY或DISTINCT查询的所有列,而不要额外搜索硬盘访问实际的表。
2.索引查询
索引的目的在于提高查询效率,大家可以回忆之前学习的全文检索技术。类似使用字典,如果没有目录(索引),那么我们要从字典的第一个字开始查询到最后一
个字才能有结果,可能要把字典中所有的字看一遍才能找到要结果,而目录(索引)则能够让我们快速的定位到这个字的位置,从而找到我们要的结果。
索引是快速搜索的关键。MySQL索引的建立对于MySQL的高效运行是很重要的。对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降。索引的目的在于提高查询效率,
2.1.索引的类型
(1)主键索引 PRIMARY KEY
它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。
PRIMARY KEY (`id`)
(2)唯一索引 UNIQUE
唯一索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。可以在创建表的时候指定,也可以修改表结构。
UNIQUE KEY `num` (`number`) USING BTREE
(3)普通索引 INDEX
这是最基本的索引,它没有任何限制。可以在创建表的时候指定,也可以修改表结构。
KEY `num` (`number`) USING BTREE
(4)组合索引 INDEX
索引分单列索引和组合索引(联合索引)。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。
KEY `num` (`number`,`name`) USING BTREE
注意,组合索引前面索引必须要先使用,后面的索引才能使用。
(5)全文索引 FULLTEXT
全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法智能分析出文本文字中关键字词的频率及重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。
示例:
1.创建以下表:
# 创建表 CREATE TABLE `tb_table` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(20) DEFAULT NULL COMMENT '姓名', `number` int(11) DEFAULT NULL COMMENT '编号', PRIMARY KEY (`id`), # 主键索引 KEY `number` (`number`) # 索引 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
执行以下sql,批量添加10条数据:
# 插入数据 drop procedure if exists tb_insert; CREATE PROCEDURE tb_insert() BEGIN DECLARE i INT; SET i = 0; START TRANSACTION; WHILE i < 10 DO -- 10即插入10条数据 INSERT INTO tb_table (`name`,`number`) VALUES (concat("张三",i),i); SET i = i+1; END WHILE; COMMIT; END; call tb_insert();
2.分别在没有和有(number)的情况下查看执行时间
有number索引:
执行sql语句:
select * from tb_table where id = 9;
结果为0.01秒:
删除number索引:
执行sql语句,不使用number=9,是为了防止缓存的影响。
select * from tb_table where number = 8;
可见当数据量小的时候,查询的时间差不多,索引的效果不明显。
3.再添加数据库的数据,插入100万条,再次测试有索引和没有索引的查询语句。
drop procedure if exists tb_insert; CREATE PROCEDURE tb_insert() BEGIN DECLARE i INT; SET i = 0; START TRANSACTION; WHILE i < 1000000 DO -- 10即插入1百万条数据 INSERT INTO tb_table (`name`,`number`) VALUES (concat("张三",i),i); SET i = i+1; END WHILE; COMMIT; END; call tb_insert();
使用索引number,执行sql语句
select * from tb_table where number = 1000;
结果:
不 使用索引number,执行sql语句
select * from tb_table where number = 999;
结果:
对比可知,使用索引时间由0.002s——》0.001s,节约了一半的时间。
3.存储优化
3.1.存储引擎介绍
- InnoDB存储引擎特点:
1. InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。相比较MyISAM存储引擎,InnoDB写的处理效率差一点并且会占用更多的磁盘空间保留数据和索引。
2. 提供了对数据库事务ACID(原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability)的支持,实现了SQL标准的四种隔离级别。
3. 设计目标就是处理大容量的数据库系统,MySQL运行时InnoDB会在内存中建立缓冲池,用于缓冲数据和索引。
4. 执行“select count(*) from table”语句时需要扫描全表,因为使用innodb引擎的表不会保存表的具体行数,所以需要扫描整个表才能计算多少行。
5. InnoDB引擎是行锁,粒度更小,所以写操作不会锁定全表,在并发较高时,使用InnoDB会提升效率。即存在大量UPDATE/INSERT操作时,效率较高。
6. InnoDB清空数据量大的表时,是非常缓慢,这是因为InnoDB必须处理表中的每一行,根据InnoDB的事务设计原则,首先需要把“删除动作”写入“事务日志”,然后写入实际的表。所以,清空大表的时候,最好直接drop table然后重建。即
InnoDB一行一行删除,不会重建表。
使用场景:
1. 经常UPDETE/INSERT的表,使用处理多并发的写请求
2. 支持事务,必选InnoDB。
3. 可以从灾难中恢复(日志+事务回滚)
4. 外键约束、列属性AUTO_INCREMENT支持
- MyISAM存储引擎
特点:
1. MyISAM不支持事务,不支持外键,SELECT/INSERT为主的应用可以使用该引擎。
2. 每个MyISAM在存储成3个文件,扩展名分别是:
1) frm:存储表定义(表结构等信息)
2) MYD(MYData),存储数据
3) MYI(MYIndex),存储索引
3. 不同MyISAM表的索引文件和数据文件可以放置到不同的路径下。
4. MyISAM类型的表提供修复的工具,可以用CHECK TABLE语句来检查MyISAM表健康,并用REPAIR TABLE语句修复一个损坏的MyISAM表。
5. 在MySQL5.6以前,只有MyISAM支持Full-text全文索引
使用场景:
1. 经常SELECT/INSERT的表,插入不频繁,查询非常频繁
2. 不支持事务
3. 做很多count 的计算。
- MyISAM和Innodb区别
InnoDB和MyISAM是许多人在使用MySQL时最常用的两个存储引擎,这两个存储引擎各有优劣,视具体应用而定。基本的差别为:MyISAM类型不支持事务处理,而InnoDB类型支持。MyISAM类型强调的是性能,其执行速度比InnoDB类型更快,而InnoDB提供事务支持已经外部键等高级数据库功能。具体实现的差别:MyISAM是非事务安全型的,而InnoDB是事务安全型的。MyISAM锁的粒度是表级,而InnoDB支持行级锁定。MyISAM不支持外键,而InnoDB支持外键MyISAM相对简单,所以在效率上要优于InnoDB,小型应用可以考虑使用MyISAM。InnoDB表比MyISAM表更安全。
3.2.存储优化
(1)禁用索引
InnoDB中,对于使用索引的表,插入记录时,MySQL会对插入的记录建立索引。如果插入大量数据,建立索引会降低插入数据速度。为了解决这个问题,可以在批量插入数据之前禁用索引,数据插入完成后再开启索引。
禁用索引的语句:
ALTER TABLE table_name DISABLE KEYS
开启索引语句:
ALTER TABLE table_name ENABLE KEYS
MyISAM对于空表批量插入数据,则不需要进行操作,因为MyISAM引擎的表是在导入数据后才建立索引。
(2)禁用唯一性检查
唯一性校验会降低插入记录的速度,可以在插入记录之前禁用唯一性检查,插入数据完成后再开启。
禁用唯一性检查的语句:SET UNIQUE_CHECKS = 0; 开启唯一性检查的语句:SET UNIQUE_CHECKS = 1;
(3) 禁用外键检查
插入数据之前执行禁止对外键的检查,数据插入完成后再恢复,可以提供插入速度。
禁用:SET foreign_key_checks = 0; 开启:SET foreign_key_checks = 1;
(4)批量插入数据
插入数据时,可以使用一条INSERT语句插入一条数据,也可以插入多条数据。
一个sql语句插入一条数据:
一次插入多个数据:
(5)禁止自动提交
插入数据之前执行禁止事务的自动提交,数据插入完成后再恢复,可以提高插入速
禁用:SET autocommit = 0; 开启:SET autocommit = 1;
5、数据库结构优化
5.1 优化表结构
(1)尽量将表字段定义为NOT NULL约束,这时由于在MySQL中含有空值的列很难进行查询优化,NULL值会使索引以及索引的统计信息变得很复杂。
(2)对于只包含特定类型的字段,可以使用enum、set 等数据类型。
(3)数值型字段的比较比字符串的比较效率高得多,字段类型尽量使用最小、最简单的数据类型。例如IP地址可以使用int类型。
(4)尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED。但对整数类型指定宽度,比如INT(11),没有任何用,因为指定的类型标识范围已经确定。
(5)VARCHAR的长度只分配真正需要的空间
(6)尽量使用TIMESTAMP而非DATETIME,但TIMESTAMP只能表示1970 - 2038年,比DATETIME表示的范围小得多,而且TIMESTAMP的值因时区不同而不同。
(7)单表不要有太多字段,建议在20以内
(8)合理的加入冗余字段可以提高查询速度。 避免联合查询,降低查询速度。
5.2.表拆分
(1) 垂直拆分
垂直拆分按照字段进行拆分,其实就是把组成一行的多个列分开放到不同的表中,这些表具有不同的结构,拆分后的表具有更少的列。例如用户表中的一些字段可能经常访问,可以把这些字段放进一张表里。另外一些不经常使用的信息就可以放进另外一张表里。插入的时候使用事务,也可以保证两表的数据一致。缺点也很明显,由于拆分出来的两张表存在一对一的关系,需要使用冗余字段,而且需要join操作。但是我们可以在使用的时候可以分别取两次,这样的来说既可以避免join操作,又可以提高效率。
(2) 水平拆分
水平拆分按照行进行拆分,常见的就是分库分表。以用户表为例,可以取用户ID,然后对ID取10的余数,将用户均匀的分配进这 0-9这10个表中。查找的时候也按照这种规则,又快又方便。
有些表业务关联比较强,那么可以使用按时间划分的。例如每天的数据量很大,需要每天新建一张表。这种业务类型就是需要高速插入,但是对于查询的效率不太关心。表越大,插入数据所需要索引维护的时间也就越长。
5.3 表分区
分区适用于例如日志记录,查询少。一般用于后台的数据报表分析。对于这些数据汇总需求,需要很多日志表去做数据聚合,我们能够容忍1s到2s的延迟,只要数据准确能够满足需求就可以。MySQL主要支持4种模式的分区:range分区、list预定义列表分区,hash 分区,key键值分区。
录入使用key键值分区:
CREATE TABLE `test2` ( `id` int(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `name` varchar(100) DEFAULT NULL COMMENT '名称', `state` int(1) DEFAULT NULL COMMENT '状态', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 PARTITION BY KEY (id) PARTITIONS 10;
5.4.读写分离
大型网站会有大量的并发访问,如果还是传统的数据存储方案,只是靠一台服务器处理,如此多的数据库连接、读写操作,数据库必然会崩溃,数据丢失的话,后果更是不堪设想。这时候,
我们需要考虑如何降低单台服务器的使用压力,提升整个数据库服务的承载能力。我们发现一般情况对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,这样分析可
以采用数据库集群的方案。其中一个是进行数据同步,那么可以使用多个从库作为读库,已完成读写分离的效果。主库,负责写入数据,我们称为写库;其它都是从库,负责读取数据,我
们称为读库。这样可以缓解一台服务器的访问压力。
MySql自带主从复制功能,我们可以使用主从复制的主库作为写库,从库和主库进行数据同步,那么可以使用多个从库作为读库,已完成读写分离的效果。
5.5.数据库集群
如果访问量非常大,虽然使用读写分离能够缓解压力,但是一旦写操作一台服务器都不能承受了,这个时候我们就需要考虑使用多台服务器实现写操作。例如可以使用MyCat搭建MySql集群,对ID求3的余数,这样可以把数据分别存放到3台不同的服务器上,由MyCat负责维护集群节点的使用。