我的MYSQL学习心得(16) : 优化

一步一步走来已经写到了第十六篇了~

这一篇主要介绍MYSQL的优化,优化MYSQL数据库是DBA和开发人员的必备技能

MYSQL优化一方面是找出系统瓶颈,提高MYSQL数据库整体性能;另一方面需要合理的结构设计和参数调整,以提高

用户操作响应的速度;同时还有尽可能节省系统资源,以便系统可以提供更大负荷的服务

如果大家看过我写的两篇文章,那么学习MYSQL的索引就不会太难,因为是相通的

SQLSERVER聚集索引与非聚集索引的再次研究(上)

SQLSERVER聚集索引与非聚集索引的再次研究(下)

其实MYSQL也有SQLSERVER堆表的概念

myisam允许没有任何索引和主键的表存在,个人觉得没有主键的myisam表都属于堆表,因为MYSQL不支持非主键的聚集索引。

innodb引擎如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见)

详细参考:MyISAM vs InnoDB:MySQL存储引擎详解

不过《MyISAM vs InnoDB:MySQL存储引擎详解》文章也有一点错误,意向共享锁就是表锁,其实是不对的

1、优化简介

mysql优化是多方面的,原则是减少系统的瓶颈,减少资源的占用,增加系统的反应速度。

例如,通过优化文件系统,提高磁盘I/O的读写速度;通过优化操作系统调度策略,提高mysql在高负荷情况下

的负载能力;优化表结构、索引、查询语句等使查询响应更快

在mysql中,可以使用show status语句查询一些mysql的性能参数

其中value是要查询的参数值,一些常用性能参数如下:

connections:连接mysql服务器的次数

uptime:mysql服务器的上线时间

slow_queries:慢查询的次数

com_select:查询操作次数

com_insert:插入操作次数

com_update:更新操作次数

com_delete:删除操作次数

如果查询mysql服务器的连接次数,可以执行如下语句

如果查询mysql服务器的慢查询次数,可以执行如下语句

2、优化查询

查询是数据库最频繁的操作,提高查询速度可以有效地提高mysql数据库的性能

(1)分析查询语句

通过对查询语句的分析,可以了解查询语句的执行情况找出查询语句执行的瓶颈

mysql中提供了EXPLAIN语句和DESCRIBE语句,用来分析查询语句

EXPLAIN语句的基本语法

使用EXTENDED关键字,EXPLAIN语句将产生附加信息。SELECT_OPTION是SELECT 语句的查询选项,包括FROM WHERE子句等

执行该语句,可以分析EXPLAIN后面的select语句的执行情况,并且能够分析所查询的表的一些特征

使用EXPLAIN语句来分析1个查询语句

下面对结果进行解释

· id

SELECT识别符。这是SELECT的查询序列号。

· select_type

SELECT类型,可以为以下任何一种:

SIMPLE:简单SELECT(不使用UNION或子查询)

PRIMARY:表示主查询,或者是最外层的查询语句(多表连接的时候)

UNION:表示连接查询的第二个或后面的查询语句

DEPENDENT UNION:UNION连接查询中的第二个或后面的SELECT语句,取决于外面的查询

UNION RESULT:UNION连接查询的结果

SUBQUERY:子查询中的第一个SELECT语句

DEPENDENT SUBQUERY:子查询中的第一个SELECT语句,取决于外面的查询

DERIVED:导出表的SELECT(FROM子句的子查询)

· table

表示查询的表

· type

表示表的联接类型

下面给出各种联接类型,按照从最佳类型到最坏类型进行排序:

(1)system

表仅有一行(=系统表)。这是const联接类型的一个特例。

(2)const

表最多只有一个匹配行,它将在查询开始时被读取。余下的查询优化中被作为常量对待。const表查询速度很快,因为它们只读取一次。

const用于常数值比较PRIMARY KEY或UNIQUE索引的所有部分的场合。

在下面的查询中,tbl_name可以用于const表:

(3)eq_ref

对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。

它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY时。

eq_ref可以用于使用“=” 操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。

在下面的例子中,MySQL可以使用eq_ref联接来处理ref_tables:

(4)ref

对于每个来自于前面的表的任意行组合,将从该表中读取所有匹配的行。

如果联接只使用索引键的最左边的前缀,或如果索引键不是UNIQUE或PRIMARY KEY,则使用ref。

如果使用的键仅仅匹配少量行,该联接类型是不错的。

ref可以用于使用=或<=>操作符的带索引的列。

在下面的例子中,MySQL可以使用ref联接来处理ref_tables:

(5)ref_or_null

该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行,在解决子查询中经常使用该联接类型的优化。

在下面的例子中,MySQL可以使用ref_or_null联接来处理ref_tables:

(6) index_merge

该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了所用到的索引的清单,key_len列包含了所用到的索引的最长长度。

(7) unique_subquery

该类型替换了下面形式的IN子查询的ref:

unique_subquery是一个索引查找类型,可以完全替换子查询,效率更高。

(8) index_subquery

该联接类型类似于unique_subquery,不过索引类型不需要是唯一索引,可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:

(9) range

只检索给定范围的行,使用一个索引来检索行数据。key列显示使用了哪个索引,key_len显示所使用索引的长度。

在该类型中ref列为NULL。

当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,类型为range。

下面介绍几种检索指定行数据的情况

(10)  index

该联接类型与ALL相同,除了扫描索引树。其他情况都比ALL快,因为索引文件通常比数据文件小。

当查询只使用作为单索引一部分的列时,MySQL可以使用该联接类型。

(11)   ALL

对于每个来自于先前的表的行组合,进行完整的表扫描。

如果第一个表没标记为const,这样执行计划就不会很好。

通常可以增加更多的索引来摆脱ALL,使得行能基于前面的表中的常数值或列值被检索出。

possible_keys

possible_keys列指出MySQL能供给使用的索引键有哪些。注意,该列完全独立于EXPLAIN输出所示的表的次序。

这意味着在possible_keys中的某些索引键实际上不能按生成的表次序使用。

如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句查看是否可以引用某些列或适合的索引列来提高查询性能。

如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询。

如果要查询一张表有什么索引,可以使用

key

key列显示MySQL实际决定使用的键(索引)。如果没有选择索引,那么可能列的值是NULL。

要想强制MySQL使用或忽略possible_keys列中的索引,在查询中可以使用

对于MyISAM引擎和BDB引擎的表,运行 ANALYZE TABLE 可以帮助优化器选择更好的索引。

对于MyISAM表,可以使用myisamchk –analyze。

key_len

key_len列显示MySQL决定使用的索引键的长度(按字节计算)。如果键是NULL,则长度为NULL。

注意通过key_len值我们可以确定MySQL将实际使用一个多索引键索引的几个字段。

ref

ref列显示使用哪个列或常数与索引一起查询记录。

rows

rows列显示MySQL预估执行查询时必须要检索的行数。

Extra

该列包含MySQL处理查询时的详细信息。下面解释了该列可以显示的不同的文本字符串:

Distinct

MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。

Not exists

MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。

下面是一个可以这样优化的查询类型的例子:

假定t2.id定义为NOT NULL。在这种情况下,MySQL使用t1.id的值扫描t1并查找t2中的行。

如果MySQL在t2中发现一个匹配的行,它知道t2.id绝不会为NULL,并且不再扫描t2内有相同的id值的行。换句话说,对于t1的每个行,MySQL只需要在t2中查找一次,无论t2内实际有多少匹配的行。

range checked for each record (index map: #)

MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用。

对前面的表的每个行组合,MySQL检查是否可以使用range或index_merge访问方法来获取行。

这并不很快,但比执行没有索引的联接要快得多。

可以参考一下这篇文章:一个用户SQL慢查询分析,原因及优化

里面就提到了range checked for each record

Using filesort

MySQL需要额外的一次传递,以找出如何按排序顺序检索行。

通过根据联接类型浏览所有行并为所有匹配WHERE子句的行保存排序关键字和行的指针来完成排序。

然后关键字被排序,并按排序顺序检索行

如果是order by操作就会用到这个Using filesort,当然filesort不是指使用文件来排序,大家不要误会了。。。

Using index

从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息。当查询只使用作为单一索引一部分的列时,可以使用该策略。

Using temporary

为了解决查询,MySQL需要创建一个临时表来容纳结果。

典型情况如查询包含可以按不同情况列出列的GROUP BY和ORDER BY子句时。

一般用到临时表都会看到 Using temporary

Using where

WHERE子句用于限制哪一个行匹配下一个表或发送到客户端。

除非你专门从表中索取或检查所有行,如果Extra值不为Using where并且表联接类型为ALL或index,查询可能会有一些错误。

Using index for group-by

类似于访问表的Using index方式,Using index for group-by表示MySQL发现了一个索引,可以用来查询GROUP BY或DISTINCT查询的所有列,

而不要额外搜索硬盘访问实际的表。并且,按最有效的方式使用索引,以便对于每个组,只读取少量索引条目。

DESCIBE语句的使用方法与EXPLAIN语句是一样的,并且分享结果也是一样的DESCIBE语句的语法如下

DESCIBE可以缩写成DESC

(2)索引对查询速度的影响

mysql中提高性能的一个最有效的方式就是对数据表设计合理的索引。索引提供了高效访问数据的方法,并且加快查询速度

因此索引对查询速度有着至关重要的影响。

如果查询没有索引,查询语句将扫描表中所有记录。在数据量大的情况下,这样查询的速度会很慢。如果使用索引进行查询,

查询语句可以根据索引快速定位到待查询记录,从而减少查询的记录数,达到提高查询速度的目的。

下面是查询语句中不使用索引和使用索引的对比,首先分析未使用索引的查询情况,EXPLAIN语句执行如下

可以看到,rows列的值是3说“SELECT ID,name FROM test.emp WHERE name =’nihao’” 语句扫描了表中的3条记录

然后在emp表加上索引

现在再分析上面的查询语句,执行的EXPLAIN语句结果如下

结果显示,rows列的值为1。这表示这个查询语句只扫描了表中的一条记录,其他查询速度自然比扫描3条记录快。

而且possible_keys 和key的值都是ix_emp_name ,这说明查询时使用了ix_emp_name 索引

如果表中记录有100条、1000条、10000条优势就显现出来了

 (3)使用索引查询

索引可以提高查询速度,但并不是使用带有索引的字段查询时,索引都会起作用。

下面的几种情况跟跟SQLSERVER一样,有可能用不到索引

(1)使用like关键字的查询语句

使用like关键字进行查询的时候,如果匹配字符串的第一个字符为“%”,索引不起作用。只有“%”不在第一个位置,索引

才会起作用

使用like关键字,并且匹配字符串中含有“%”字符,EXPLAIN语句如下

name上有索引ix_emp_name

第一个查询type为ALL,表示要全表扫描

第二个查询TYPE为index,表示会扫描索引

like 关键字是否能利用上索引跟SQLSERVER是一样的

我之前写过一篇文章:like语句百分号前置会使用到索引吗?

(2)使用多列索引的查询语句

mysql可以为多个字段创建索引。一个索引可以包括16个字段(跟SQLSERVER一样)对于多列索引,只有查询条件中使用了

这些字段中的第一个字段时,索引才会被使用,这个字段叫:前导索引或前导列

在表person中name,age字段创建多列索引,验证多列索引的情况

从第一条查询看出,WHERE Name =’suse’的记录有一条,扫描了一条记录并且使用了ix_person_name_age 索引

从第二条记录可以看出,rows列的值为4,说明共扫描了4条记录,并且key列值为NULL,说明EXPLAIN SELECT ID,Name,Age,job FROM person WHERE age =12

语句并没有使用索引。因为age字段是多列索引的第二个字段,只有查询条件中使用了name字段才会使用ix_person_name_age 索引

这个跟SQLSERVER是一样的,详细请看:SQLSERVER聚集索引与非聚集索引的再次研究(下)

(3)使用OR关键字的查询语句

查询语句的查询条件中只有OR关键字,而且OR前后的两个条件中的列都是索引时,查询中才使用索引,否则,查询不使用索引

查询语句使用OR关键字的情况

我们再创建一个索引

大家要注意,这里跟刚才不一样,这次我们select的字段只有name和age,而不是select出全部字段

因为并没有在job这个字段上建立索引,所以第一个查询使用的是全表扫描

第二个查询因为name字段和age字段都有索引,那么mysql可以利用这两个索引的其中之一,这里是ix_person_name_age索引来查找记录

利用索引来查找记录会快很多

(4)优化子查询

mysql从4.1版本开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结果作为另一个SELECT语句的条件

子查询可以一次性完成很多逻辑需要多个步骤才能完成的SQL操作。子查询虽然使查询语句灵活,但是执行效率不高。

执行子查询时,mysql需要为内层查询语句结果建立一个临时表。然后外层查询语句从临时表中查询记录

查询完毕后,再撤销临时表。因此,子查询的速度会受到一定影响,如果查询的数据量特别大,这种影响就会更大。

在mysql中,可以使用连接(join)查询来代替子查询。连接查询不需要建立临时表,其速度比子查询快,如果查询中使用索引的话,性能会更好。

所以很多网上的文章都说尽量使用join来代替子查询,虽然网上也说mysql5.7对于子查询有很大的改进,但是如果不是使用mysql5.7还是需要注意的

如果系统中join语句特别多还需要注意修改my.ini或my.cnf文件中的join_buffer_size大小,预防性能问题


优化数据库结构

一个好的数据库设计方案对于数据库的性能常常起到事半功倍的效果。

数据库结构的设计需要考虑数据冗余、查询和更新速度、字段的数据类型是否合理等多方面

(1)将字段很多的表拆分成多个表

有时候有些字段使用频率很低或者字段的数据类型比较大,那么可以考虑垂直拆分的方法,把不常用的字段和大字段拆分出去

(2)增加中间表

对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入到中间表中,

然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。

(3)增加冗余字段

设计数据库表时应尽量遵循范式理论,尽可能减少冗余字段,但是现今存储硬件越来越便宜,有时候查询数据的时候需要join多个表

这样在高峰期间会影响查询的效率,我们需要反范式而为之,增加一些必要的冗余字段,以空间换时间

需要这样做会增加开发的工作量和维护量,但是如果能换来可观的性能提升,这样做也是值得的

(4)优化插入记录的速度

插入记录时,影响插入速度的主要是索引、唯一性校验、一次插入记录条数等。

根据实际情况,可以分别进行优化

对于myisam表,常见优化方法如下:

1、禁用索引

对于非空表,插入记录时,mysql会根据表的索引对插入的记录建立索引。如果插入大量数据,建立索引会降低插入记录的速度。

为了解决这个问题,可以在插入记录之前禁用索引,数据插入完毕后再开启索引

禁用索引语句如下:

其中table_name是禁用索引的表的表名

重新开启索引语句如下:

对于空表批量导入数据,则不需要进行此操作,因为myisam表是在导入数据之后才建立索引!

2、禁用唯一性检查

插入数据时,mysql会对插入的记录进行唯一性校验。这种唯一性校验也会降低插入记录的速度。

为了降低这种情况对查询速度的影响,可以在插入记录之前禁用唯一性检查,等到记录插入完毕之后再开启

禁用唯一性检查的语句如下:

开启唯一性检查的语句如下:

3、使用批量插入

插入多条记录时,可以使用一条INSERT语句插入一条记录,也可以使用一条INSERT语句插入多条记录。

第一种情况

第二种情况

第二种情况要比第一种情况要快

4、使用LOAD DATA INFILE批量导入

当需要批量导入数据时,如果能用LOAD DATA INFILE语句,就尽量使用。因为LOAD DATA INFILE语句导入数据的速度比INSERT语句快很多

对于INNODB引擎的表,常见的优化方法如下:

1、禁用唯一性检查

插入数据时,mysql会对插入的记录进行唯一性校验。这种唯一性校验也会降低插入记录的速度。

为了降低这种情况对查询速度的影响,可以在插入记录之前禁用唯一性检查,等到记录插入完毕之后再开启

禁用唯一性检查的语句如下:

开启唯一性检查的语句如下:

2、禁用外键约束

插入数据之前执行禁止对外键的检查,数据插入完成之后再恢复对外键的检查。禁用外键检查的语句如下:

恢复对外键的检查语句如下

3、禁止自动提交

插入数据之前禁止事务的自动提交,数据导入完成之后,执行恢复自动提交操作

或显式指定事务

(5)分析表、检查表、优化表、修复表和CHECKSUM表

mysql提供了分析表、检查表和优化表的语句

分析表主要是分析关键字的分布;

检查表主要是检查表是否存在错误;

优化表主要是消除删除或者更新造成的空间浪费

修复表主要对myisam表文件进行修复

CHECKSUM表主要对表数据传输前和传输后进行比较

1、分析表

mysql中提供了ANALYZE TABLE 语句分析表,ANALYZE TABLE 语句的基本语法如下

LOCAL关键字是NO_WRITE_TO_BINLOG关键字的别名,二者都是执行过程不写入二进制日志,tbl_name为分析的表的表名

可以有一个或多个

使用ANALYZE TABLE 分析表的过程中,数据库系统会自动对表加一个只读锁。在分享期间,只能读取表的记录,不能更新和插入记录

ANALYZE TABLE 语句能分析INNODB、BDB和MYISAM类型的表

使用ANALYZE TABLE 来分析emp表,执行语句如下:

上面结果显示说明

table:表示分析的表名

op:表示执行的操作,analyze表示进行分析操作

msg_type:表示信息类型其值通常是状态(status)、信息(info)、注意(note)、警告(warning)和错误(error)之一

msg_text:显示信息

实际上分析表跟SQLSERVER里的更新统计信息是差不多的

主要就是为了索引的基数更加准确,从而使查询优化器能够更加准确的预估行数

emp表的记录行数是18

分析表之后,Cardinality 基数更加准确了

2、检查表

mysql中使用check table语句来检查表。check table语句能够检查innodb和myisam类型的表是否存在错误。

对于myisam类型的表,check table语句还会更新关键字统计数据。而且,check table也可以检查视图是否有错误,

比如在视图定义中被引用的表已不存在。

该语句基本语法如下:

其中,tbl_name是表名;option参数有5个取值分别是QUICK、FAST、MEDIUM、EXTENDED、CHANGED

各个选项的意思分别是

QUICK:不扫描行,不检查错误的连接

FAST:只检查没有被正确关闭的表

MEDIUM:扫描行,以验证被删除的连接是有效的,也可以计算各行的关键字校验和,并使用计算出的校验和验证这一点

EXTENDED:对每行的所有关键字进行一个全面的关键字查找。这可以确保表是100%一致的,但是花的时间较长

CHANGED:只检查上次检查后被更改的表和没有被正确关闭的表

option只对myisam表有效,对innodb表无效。check table语句在执行过程中也会给表加上只读锁。

3、优化表

mysql中使用OPTIMIZE TABLE语句来优化表。该语句对INNODB和MYISAM表都有效。但是,OPTIMIZE TABLE语句只能优化表中的

VARCHAR、BLOB、TEXT类型的字段

OPTIMIZE TABLE语句的基本语法如下:

LOCAL和NO_WRITE_TO_BINLOG关键字的意义和分析表相同,都是指定不写入二进制日志

tbl_name是表名

通过OPTIMIZE TABLE语句可以消除删除和更新造成的文件碎片。

OPTIMIZE TABLE语句在执行过程中也会给表加上只读锁。

提示:一个表使用了TEXT或者BLOB这样的数据类型,如果已经删除了表的一大部分,或者已经对含有可变长度行的表(含有VARCHAR、BLOB或TEXT列的表)

进行了很多更新,则应使用OPTIMIZE TABLE来重新利用未使用的空间,并整理数据文件的碎片。在多数设置中,根本不需要运行OPTIMIZE TABLE。

即使对可变长度的行进行了大量更新,也不需要经常运行,每周一次或每月一次即可,并且只需要对特定表进行OPTIMIZE TABLE

OPTIMIZE TABLE语句类似于SQLSERVER的重建索引和收缩数据文件的功能

4、修复表

mysql中使用Repair Table来修复myisam表,只对MyISAM和ARCHIVE类型的表有效。

选项的意思分别是:

QUICK:最快的选项,只修复索引树。
EXTENDED:最慢的选项,需要逐行重建索引。
USE_FRM:只有当MYI文件丢失时才使用这个选项,全面重建整个索引。

与Analyze Table一样,Repair Table也可以使用local来取消写入binlog。

5、Checksum 表

数据在传输时,可能会发生变化,也有可能因为其它原因损坏,为了保证数据的一致,我们可以计算checksum(校验值)。

使用MyISAM引擎的表会把checksum存储起来,称为live checksum,当数据发生变化时,checksum会相应变化。

语法如下:

quick:表示返回存储的checksum值

extended:表示重新计算checksum

如果没有指定选项,则默认使用extended。

Checksum 表主要用来对比在传输表数据之前和表数据之后,表的数据是否发生了变化,例如插入了数据或者删除了数据,或者有数据损坏

CHECKSUM值都会改变。


优化MYSQL服务器

水电费优化mysql服务器主要从两个方面入手,一方面是对硬件进行优化;另一方面是对mysql服务器的参数进行优化

1、优化服务器硬件

服务器的硬件性能直接决定着MYSQL数据库的性能。硬件的性能瓶颈直接决定MYSQL数据库的运行速度和效率。

优化服务器硬件的几种方法

(1)配置较大的内存。足够大的内存,是提高mysql数据库性能之一。内存速度比磁盘I/O快得多,可以通过增加系统缓冲区容量,使数据库

在内存停留时间更长,以减少磁盘I/O

(2)配置高速磁盘系统,以减少读盘等待时间,提高响应速度

(3)合理分布磁盘I/O,把磁盘I/O分散在多个设备上,以减少资源竞争,提高并行操作能力

(4)配置多处理器,mysql是多线程的数据库,多处理器可同时执行多个线程

2、优化MYSQL的参数

通过优化MYSQL的参数可以提高资源利用率,从而达到提高MYSQL服务器的性能的目的。

MYSQL服务器的配置参数都在my.cnf或者my.ini文件的[mysqld]组中。

下面对几个对性能影响较大的参数进行介绍

配置完参数之后,需要重启MYSQL服务才能生效


如何使用查询缓冲区

查询缓冲区可以提高查询的速度,但是这种方式只适合查询语句多、更新较少的情况。默认情况下查询缓冲区的大小为0,也就是不可用

可以修改query_cache_size以调整查询缓冲区大小;修改 query_cache_type以调整查询缓冲区的类型。

在my.ini中修改query_cache_size和query_cache_type的值如下所示

query_cache_type=1表示开启查询缓冲区。只有在查询语句中包含SQL_NO_CACHE关键字时,才不会使用查询缓冲区。

可以使用FLUSH QUERY CACHE语句来刷新缓冲区,清理查询缓冲区中的碎片

注意:开启查询缓冲区是有风险的,如果命中率不高,或者更新修改语句较多,都会使查询缓冲区失效,从而使命中率更加低

建议使用memcached等软件来做二级缓存,除非系统中修改语句较少,命中率较高,这样才会看到明显的性能提升


总结

本文阐述了MYSQL的性能优化面的内容,虽然网上对于MYSQL优化的资料很多

但是,MYSQL优化方面需要长期的进行研究探索才能找到适合于自己公司的业务系统最佳参数,否则只是使用网上的介绍只会人云亦云

1 5 收藏 2 评论

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部