MySQL数据库性能优化.docx
《MySQL数据库性能优化.docx》由会员分享,可在线阅读,更多相关《MySQL数据库性能优化.docx(17页珍藏版)》请在冰豆网上搜索。
MySQL数据库性能优化
MySQL数据库性能优化
作者:
Sky.Jian|可以任意转载,但转载时务必以超链接形式标明文章原始出处和作者信息及版权声明
链接:
在平时被问及最多的问题就是关于MySQL数据库性能优化方面的问题,所以最近打算写一个MySQL数据库性能优化方面的系列文章,希望对初中级MySQLDBA以及其他对MySQL性能优化感兴趣的朋友们有所帮助。
第1章MySQL数据库性能优化之缓存参数优化
数据库属于IO密集型的应用程序,其主要职责就是数据的管理及存储工作。
而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级。
所以,要优化数据库,首先第一步需要优化的就是IO,尽可能将磁盘IO转化为内存IO。
本文先从MySQL数据库IO相关参数(缓存参数)的角度来看看可以通过哪些参数进行IO优化:
∙query_cache_size/query_cache_type(global)
Querycache作用于整个MySQLInstance,主要用来缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。
当我们打开了QueryCache功能,MySQL在接受到一条select语句的请求后,如果该语句满足QueryCache的要求(未显式说明不允许使用QueryCache,或者已经显式申明需要使用QueryCache),MySQL会直接根据预先设定好的HASH算法将接受到的select语句以字符串方式进行hash,然后到QueryCache中直接查找是否已经缓存。
也就是说,如果已经在缓存中,该select请求就会直接将数据返回,从而省略了后面所有的步骤(如SQL语句的解析,优化器优化以及向存储引擎请求数据等),极大的提高性能。
当然,QueryCache也有一个致命的缺陷,那就是当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在QueryCache中的缓存数据失效。
所以,当我们的数据变化非常频繁的情况下,使用QueryCache可能会得不偿失。
QueryCache的使用需要多个参数配合,其中最为关键的是query_cache_size和query_cache_type,前者设置用于缓存ResultSet的内存大小,后者设置在何场景下使用QueryCache。
在以往的经验来看,如果不是用来缓存基本不变的数据的MySQL数据库,query_cache_size一般256MB是一个比较合适的大小。
当然,这可以通过计算QueryCache的命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))来进行调整。
query_cache_type可以设置为0(OFF),1(ON)或者2(DEMOND),分别表示完全不使用querycache,除显式要求不使用querycache(使用sql_no_cache)之外的所有的select都使用querycache,只有显示要求才使用querycache(使用sql_cache)。
∙binlog_cache_size(global)
BinlogCache用于在打开了二进制日志(binlog)记录功能的环境,是MySQL用来提高binlog的记录效率而设计的一个用于短时间内临时缓存binlog数据的内存区域。
一般来说,如果我们的数据库中没有什么大事务,写入也不是特别频繁,2MB~4MB是一个合适的选择。
但是如果我们的数据库大事务较多,写入量比较大,可与适当调高binlog_cache_size。
同时,我们可以通过binlog_cache_use以及binlog_cache_disk_use来分析设置的binlog_cache_size是否足够,是否有大量的binlog_cache由于内存大小不够而使用临时文件(binlog_cache_disk_use)来缓存了。
∙key_buffer_size(global)
KeyBuffer可能是大家最为熟悉的一个MySQL缓存参数了,尤其是在MySQL没有更换默认存储引擎的时候,很多朋友可能会发现,默认的MySQL配置文件中设置最大的一个内存参数就是这个参数了。
key_buffer_size参数用来设置用于缓存MyISAM存储引擎中索引文件的内存区域大小。
如果我们有足够的内存,这个缓存区域最好是能够存放下我们所有的MyISAM引擎表的所有索引,以尽可能提高性能。
此外,当我们在使用MyISAM存储的时候有一个及其重要的点需要注意,由于MyISAM引擎的特性限制了他仅仅只会缓存索引块到内存中,而不会缓存表数据库块。
所以,我们的SQL一定要尽可能让过滤条件都在索引中,以便让缓存帮助我们提高查询效率。
∙bulk_insert_buffer_size(thread)
和key_buffer_size一样,这个参数同样也仅作用于使用MyISAM存储引擎,用来缓存批量插入数据的时候临时缓存写入数据。
当我们使用如下几种数据写入语句的时候,会使用这个内存区域来缓存批量结构的数据以帮助批量写入数据文件:
insert…select…
insert…values(…),(…),(…)…
loaddatainfile…into…(非空表)
∙innodb_buffer_pool_size(global)
当我们使用InnoDB存储引擎的时候,innodb_buffer_pool_size参数可能是影响我们性能的最为关键的一个参数了,他用来设置用于缓存InnoDB索引及数据块的内存区域大小,类似于MyISAM存储引擎的key_buffer_size参数,当然,可能更像是Oracle的db_cache_size。
简单来说,当我们操作一个InnoDB表的时候,返回的所有数据或者去数据过程中用到的任何一个索引块,都会在这个内存区域中走一遭。
和key_buffer_size对于MyISAM引擎一样,innodb_buffer_pool_size设置了InnoDB存储引擎需求最大的一块内存区域的大小,直接关系到InnoDB存储引擎的性能,所以如果我们有足够的内存,尽可将该参数设置到足够打,将尽可能多的InnoDB的索引及数据都放入到该缓存区域中,直至全部。
我们可以通过(Innodb_buffer_pool_read_requests–Innodb_buffer_pool_reads)/Innodb_buffer_pool_read_requests*100%计算缓存命中率,并根据命中率来调整innodb_buffer_pool_size参数大小进行优化。
∙innodb_additional_mem_pool_size(global)
这个参数我们平时调整的可能不是太多,很多人都使用了默认值,可能很多人都不是太熟悉这个参数的作用。
innodb_additional_mem_pool_size设置了InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,所以当我们一个MySQLInstance中的数据库对象非常多的时候,是需要适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率的。
这个参数大小是否足够还是比较容易知道的,因为当过小的时候,MySQL会记录Warning信息到数据库的errorlog中,这时候你就知道该调整这个参数大小了。
∙innodb_log_buffer_size(global)
这是InnoDB存储引擎的事务日志所使用的缓冲区。
类似于BinlogBuffer,InnoDB在写事务日志的时候,为了提高性能,也是先将信息写入InnofbLogBuffer中,当满足innodb_flush_log_trx_commit参数所设置的相应条件(或者日志缓冲区写满)之后,才会将日志写到文件(或者同步到磁盘)中。
可以通过innodb_log_buffer_size参数设置其可以使用的最大内存空间。
注:
innodb_flush_log_trx_commit参数对InnoDBLog的写入性能有非常关键的影响。
该参数可以设置为0,1,2,解释如下:
0:
logbuffer中的数据将以每秒一次的频率写入到logfile中,且同时会进行文件系统到磁盘的同步操作,但是每个事务的commit并不会触发任何logbuffer到logfile的刷新或者文件系统到磁盘的刷新操作;
1:
在每次事务提交的时候将logbuffer中的数据都会写入到logfile,同时也会触发文件系统到磁盘的同步;
2:
事务提交会触发logbuffer到logfile的刷新,但并不会触发磁盘文件系统到磁盘的同步。
此外,每秒会有一次文件系统到磁盘同步操作。
此外,MySQL文档中还提到,这几种设置中的每秒同步一次的机制,可能并不会完全确保非常准确的每秒就一定会发生同步,还取决于进程调度的问题。
实际上,InnoDB能否真正满足此参数所设置值代表的意义正常Recovery还是受到了不同OS下文件系统以及磁盘本身的限制,可能有些时候在并没有真正完成磁盘同步的情况下也会告诉mysqld已经完成了磁盘同步。
∙innodb_max_dirty_pages_pct(global)
这个参数和上面的各个参数不同,他不是用来设置用于缓存某种数据的内存大小的一个参数,而是用来控制在InnoDBBufferPool中可以不用写入数据文件中的DirtyPage的比例(已经被修但还没有从内存中写入到数据文件的脏数据)。
这个比例值越大,从内存到磁盘的写入操作就会相对减少,所以能够一定程度下减少写入操作的磁盘IO。
但是,如果这个比例值过大,当数据库Crash之后重启的时间可能就会很长,因为会有大量的事务数据需要从日志文件恢复出来写入数据文件中。
同时,过大的比例值同时可能也会造成在达到比例设定上限后的flush操作“过猛”而导致性能波动很大。
上面这几个参数是MySQL中为了减少磁盘物理IO而设计的主要参数,对MySQL的性能起到了至关重要的作用。
—EOF—
按照mcsrainbow朋友的要求,这里列一下根据以往经验得到的相关参数的建议值:
∙query_cache_type:
如果全部使用innodb存储引擎,建议为0,如果使用MyISAM存储引擎,建议为2,同时在SQL语句中显式控制是否是哟你gquerycache
∙query_cache_size:
根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大
∙binlog_cache_size:
一般环境2MB~4MB是一个合适的选择,事务较大且写入频繁的数据库环境可以适当调大,但不建议超过32MB
∙key_buffer_size:
如果不使用MyISAM存储引擎,16MB足以,用来缓存一些系统表信息等。
如果使用MyISAM存储引擎,在内存允许的情况下,尽可能将所有索引放入内存,简单来说就是“越大越好”
∙bulk_insert_buffer_size:
如果经常性的需要使用批量插入的特殊语句(上面有说明)来插入数据,可以适当调大该参数至16MB~32MB,不建议继续增大,某人8MB
∙innodb_buffer_pool_size:
如果不使用InnoDB存储引擎,可以不用调整这个参数,如果需要使用,在内存允许的情况下,尽可能将所有的InnoDB数据文件存放如内存中,同样将但来说也是“越大越好”
∙innodb_additional_mem_pool_size:
一般的数据库建议调整到8MB~16MB,如果表特别多,可以调整到32MB,可以根据errorlog中的信息判断是否需要增大
∙innodb_log_buffer_size:
默认是1MB,系的如频繁的系统可适当增大至4MB~8MB。
当然如上面介绍所说,这个参数实际上还和另外的flush参数相关。
一般来说不建议超过32MB
∙innodb_max_dirty_pages_pct:
根据以往的经验,重启恢复的数据如果要超过1GB的话,启动速度会比较慢,几乎难以接受,所以建议不大于1GB/innodb_buffer_pool_size(GB)*100这个值。
当然,如果你能够忍受启动时间比较长,而且希望尽量减少内存至磁盘的flush,可以将这个值调整到90,但不建议超过90
注:
以上取值范围仅仅只是我的根据以往遇到的数据库场景所得到的一些优化经验值,并不一定适用于所有场景,所以在实际优化过程中还需要大家自己不断的调整分析,也欢迎大家随时通过Mail与我联系沟通交流优化或者是架构方面的技术,一起探讨相互学习。
第2章MySQL数据库性能优化之表结构
很多人都将数据库设计范式作为数据库表结构设计“圣经”,认为只要按照这个范式需求设计,就能让设计出来的表结构足够优化,既能保证性能优异同时还能满足扩展性要求。
殊不知,在N年前被奉为“圣经”的数据库设计3范式早就已经不完全适用了。
这里我整理了一些比较常见的数据库表结构设计方面的优化技巧,希望对大家有用。
由于MySQL数据库是基于行(Row)存储的数据库,而数据库操作IO的时候是以page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个page中可存放的数据行数增大,那么每次IO可访问的行数也就增多了。
反过来说,处理相同行数的数据,需要访问的page就会减少,也就是IO操作次数降低,直接提升性能。
此外,由于我们的内存是有限的,增加每个page中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存换中数据命中的几率,也就是缓存命中率。
∙数据类型选择
数据库操作中最为耗时的操作就是IO处理,大部分数据库操作90%以上的时间都花在了IO读写上面。
所以尽可能减少IO读写量,可以在很大程度上提高数据库操作的性能。
我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。
下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题:
1.数字类型:
非万不得已不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。
同样,固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。
对于整数的存储,在数据量较大的情况下,建议区分开TINYINT/INT/BIGINT的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。
当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。
2.字符类型:
非万不得已不要使用TEXT数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。
定长字段,建议使用CHAR类型,不定长字段尽量使用VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。
3.时间类型:
尽量使用TIMESTAMP类型,因为其存储空间只需要DATETIME类型的一半。
对于只需要精确到某一天的数据类型,建议使用DATE类型,因为他的存储空间只需要3个字节,比TIMESTAMP还少。
不建议通过INT类型类存储一个unixtimestamp的值,因为这太不直观,会给维护带来不必要的麻烦,同时还不会带来任何好处。
4.ENUM&SET:
对于状态字段,可以尝试使用ENUM来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。
如果是存放可预先定义的属性数据呢?
可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。
5.LOB类型:
强烈反对在数据库中存放LOB类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。
在数据库中存储LOB数据就像让一个多年前在学校学过一点Java的营销专业人员来写Java代码一样。
∙字符编码
字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。
6.纯拉丁字符能表示的内容,没必要选择latin1之外的其他字符编码,因为这会节省大量的存储空间
7.如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费
8.MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低IO操作次数并提高缓存命中率
∙适当拆分
有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。
当我们的表中存在类似于TEXT或者是很大的VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。
这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理IO次数,也能大大提高内存中的缓存命中率。
上面几点的优化都是为了减少每条记录的存储空间大小,让每个数据库中能够存储更多的记录条数,以达到减少IO操作次数,提高缓存命中率。
下面这个优化建议可能很多开发人员都会觉得不太理解,因为这是典型的反范式设计,而且也和上面的几点优化建议的目标相违背。
∙适度冗余
为什么我们要冗余?
这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?
确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做:
1.被频繁引用且只能通过Join2张(或者更多)大表的方式才能得到的独立小字段
这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的IO,完全可以通过空间换取时间的方式来优化。
不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新
∙尽量使用NOTNULL
NULL类型比较特殊,SQL难优化。
虽然MySQLNULL类型和Oracle的NULL有差异,会进入索引中,但如果是一个组合索引,那么这个NULL类型的字段会极大影响整个索引的效率。
此外,NULL在索引中的处理也是特殊的,也会占用额外的存放空间。
很多人觉得NULL会节省一些空间,所以尽量让NULL来达到节省IO的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。
所以尽量确保DEFAULT值不是NULL,也是一个很好的表结构设计优化习惯。
第3章MySQL数据库性能优化之SQL优化
有人反馈之前几篇文章过于理论缺少实际操作细节,这篇文章就多一些可操作性的内容吧。
注:
这篇文章是以MySQL为背景,很多内容同时适用于其他关系型数据库,需要有一些索引知识为基础
∙优化目标
1.减少IO次数
IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是IO操作所占用的,减少IO次数是SQL优化中需要第一优先考虑,当然,也是收效最明显的优化手段。
2.降低CPU计算
除了IO瓶颈之外,SQL优化中需要考虑的就是CPU运算量的优化了。
orderby,groupby,distinct…都是消耗CPU的大户(这些操作基本上都是CPU处理内存中的数据比较运算)。
当我们的IO优化做到一定阶段之后,降低CPU计算也就成为了我们SQL优化的重要目标
∙优化方法
3.改变SQL执行计划
明确了优化目标之后,我们需要确定达到我们目标的方法。
对于SQL语句来说,达到上述2个目标的方法其实只有一个,那就是改变SQL的执行计划,让他尽量“少走弯路”,尽量通过各种“捷径”来找到我们需要的数据,以达到“减少IO次数”和“降低CPU计算”的目标
∙常见误区
4.count
(1)和count(primary_key)优于count(*)
很多人为了统计记录条数,就使用count
(1)和count(primary_key)而不是count(*),他们认为这样性能更好,其实这是一个误区。
对于有些场景,这样做可能性能会更差,应为数据库对count(*)计数操作做了一些特别的优化。
5.count(column)和count(*)是一样的
这个误区甚至在很多的资深工程师或者是DBA中都普遍存在,很多人都会认为这是理所当然的。
实际上,count(column)和count(*)是一个完全不一样的操作,所代表的意义也完全不一样。
count(column)是表示结果集中有多少个column字段不为空的记录
count(*)是表示整个结果集有多少条记录
6.selecta,bfrom…比selecta,b,cfrom…可以让数据库访问更少的数据量
这个误区主要存在于大量的开发人员中,主要原因是对数据库的存储原理不是太了解。
实际上,大多数关系型数据库都是按照行(row)的方式存储,而数据存取操作都是以一个固定大小的IO单元(被称作block或者page)为单位,一般为4KB,8KB…大多数时候,每个IO单元中存储了多行,每行都是存储了该行的所有字段(lob等特殊类型字段除外)。
所以,我们是取一个字段还是多个字段,实际上数据库在表中需要访问的数据量其实是一样的。
当然,也有例外情况,那就是我们的这个查询在索引中就可以完成,也就是说当只取a,b两个字段的时候,不需要回表,而c这个字段不在使用的索引中,需要回表取得其数据。
在这样的情况下,二者的IO量会有较大差异。
7.orderby一定需要排序操作
我们知道索引数据实际上是有序的,如果我们的需要的数据和某个索引的顺序一致,而且我们的查询又通过这个索引来执行,那么数据库一般会省略排序操作,而直接将数据返回,因为数据库知道数据已经满足我们的排序需求了。
实际上,利用索引来优化有排序需求的S