您的位置:首页 > 数据库 > MySQL

MySQL 优化

2010-12-01 14:28 1576 查看

 

1.优化概要

      使系统变快的最重要因素是系统的基础设计.我们需要知道系统正在做什么类型的处理,以及瓶颈是什么.多数情况下,系统瓶颈主要来源于以下几个方面:

      1.1 磁盘访问(Disk seek). 即找到一片数据所花的时间,现代磁盘的平均时间通常小于10ms,相当于每秒寻址100次.由于硬件的限制及很难对单一的表进行优化,所以优化磁盘访问时间的最好办法就是将数据分布在多个磁盘上.

      1.2 磁盘读写(Disk reading and writing). 现代磁盘的吞吐量大约在10-20MB/s,可以通过从多个磁盘并行读取来提升速度.

      1.3 CPU周期(CPU cycles). 经比较,小表的内存量是最常见的限制因素,但对小表来说,速度通常不是问题.

      1.4 内存带宽(Memory bandwidth). 当CPU自身的缓存不能满足需求时,主存带宽便成了一个瓶颈,但对大多数系统来说,这并不是一个常规的瓶颈问题,然而我们需要知道它的存在.

2. SELECT及其它查询语句优化

     2.1 使用EXPLAIN优化查询,参考EXPLAIN手册

     2.2 评估查询性能

           多数情况下,我们可以通过计算磁盘搜索次数来估算查询性能. 对比较小的表来说,通常可以通过一次磁盘搜索就可以找到相应的行(row).对比较大的表来说,在使用BTREE索引的情况下,可以通过下面的公式来估算找到一行的搜索次数:

 

log(row_count
) / log(index_block_length / 3 × 2 / (index_length + data_pointer_length)) + 1

在MYSQL中,index_block_length通常为1024个字节,data_pointer_length通常为4个字节,因此对于一个具有50万条记录的表来说,如果键值长度为4个字节(INT),则通过上式可得:

        log(500000)/log(1024/3x2/(4+4))+1=5.28 (约等于6次)

如果磁盘的搜索访问时间为10ms,那么在50万条记录中查找一条时间大约是52.8ms

      2.3 加速SELECT查询

        通常,使SELECT...WHERE变快的第一件事情,就是要检查是否添加了索引(index).不同表之间的所有引用,原则上都是通过索引来完成的.

3.数据库结构优化

      3.1 使数据表尽可能的小

         一个最基本的优化方式就是使表尽可以占用小的存储空间,为了得到更好的性能及减少存储空间,可以采用下面的技术:

  • 使用最有效的(最小的)数据类型,如可以用MEDIUMINT的地方绝不使用INT,这样可以节省25%空间
  • 如何可能,尽可能使用NOT NULL,这样不仅可以提升速度,且每个Column还可以减少一个BIT空间.如果确实需要使用NULL,那么需要很明确将字段定义为NULL,而不是留空不设.
  • 对于MyISAM表来说,如果不需要任何变长度的列,推荐使用固定大小的行格式.这样会比较快,但需要浪费空间.或者可以以隐含的方式通过CREATE TABLE时的ROW_FORMAT=FIXED,来固定行的大小,即使设置了VARCHAR类型的列也没关系.
  • 对于InnoDB来说,使用压缩存储格式(ROW_FORMAT=COMPACT),在5.0.3版本之前,InnoDB都包含一些冗余信息如,列数,列的大小等,即使是固定大小的列也是如此.默认情况下采用的是压缩格式,当然如果希望恢复到以前的版本,可以使用ROW_FORMAT=REDUNDANT.
  • 主键索引,应该尽可能的短小,这样可以容易且高效的定位一条记录
  • 只创建真正需要的索引.因为索引确实可以加速获取记录,但同时也抑制了存储数据的速度
  • 如果可以确定某一列开始的几个字符可以作为唯一的前缀,那么最好的办法是仅索引这前几个字符使用create index中的leftmost功能.索引越短,速度越快,这并不仅仅是因为所需要的磁盘空间小,更重要的是可以提高索引缓存的命中率,减少磁盘搜索次数.

     3.2 单列索引(Column Indexes)

        MySQL的所有数据类型都可以成为索引,在相关的列上加索引是为了更好的提高SELECT操作的性能.每个表的最多索引数及每个索引的最大长度,都会因存储引擎的不同而有所不同.但所有存储引擎都支持至少16个索引/表,及至少256字节/索引. 需要特别指出的是,列索引前缀的是按字节进行计算的,而在CREATE TABLE中的INDEX(blob_col(10))中的10会被解析成字符.(在使用多字节字符集时要务必考虑).  当然我们要也可以创建FULLTEXT索引用于全文索引,在MYSQL中仅MyISAM引擎支持且仅对CHAR,VARCHAR,及TEXT类型有效.  MEMORY存储引擎默认采用HASH索引,但也支持BTREE(B树)索引.

     3.3 多列索引(Multiple-Column Indexes)

       在MySQL中还可以合建组合索引,即一个索引指向多个列.一个索引最多可以由16个列组成. 可以将一个多列索引,想像成一个被排序的数组,数组的内容就是相关列内容的粘合. 多列索引只有当第一个列的值在WHERE语句中以确定值出现时才起作用,即使没有指派其它列值也无妨.

   如:  CREATE TABLE test(....,....,INDEX NAME(col1,col2)); 当执行SELECT语句时,只有在

        WHERE col1=c(一个确定的值)[AND,OR col2=var]  的情况下起作用,且在where col1=c1 or col2=c2的条件下也不会起作用.

     3.4 MySQL如何使用索引

      MySQL会在以下操作中使用索引:

  • 通过WHERE语句找到匹配行
  • 估算行数时,如果同时有多个索引可供选择,MYSQL会选用找到结果数最少的索引
  • 当通过join从其它表中选择数据行时,如果彼此的索引声明为相同的类型和大小,将会很高效.在当前情形下,VARCHAR与CHAR会被认为是相同类型,但同时还得需要大小一样才行varchar(10) and char(11) are not.
  • 为特定的索引列查找MIN() 和 MAX()值.
  • 分组或排序表时

     B-Tree索引可以使用比较运算符:=,>,>=,<,<=和BETWEEN,除些之外还可以应用于LIKE,但 LIKE的参数不能以通匹符%开始.

     3.5 MyISAM 键值缓存(Key Cache),参考设置: key_buffer_size or key_cache_block_size

     3.6 InnoDB缓冲池

      InnoDB通过在主存中维护一个缓冲池来缓存数据和索引,并将其作为一个列表来处理,使用LRU算法并纳入了中点插入策略. 当需要加入一个新块时,INNODB会使用LRU算法替换出相应的块,并将新块插入到列表的中间.中点插入策略会有效地将列表分成两个子列表:在头部(head),是最近被使用的"新"块的子列,位于尾部的(tail),是最近很少用的"旧"块.

     InnoDB有些系统变量,可以用来控制缓冲池的大小或改善LRU算法

  • innodb_buffer_pool_size, 如有内存足够用,那么可以适当增大此参数来提高性能,减少磁盘I/O
  • innodb_old_blocks_pct,指定"旧"块子列表在整个列表中所占的比例,默认值是37,即整个池的3/8
  • innodb_old_blocks_time,指定位于"旧"块子列表中的某块在第一次被访问之后再需要多长时间才会被转到"新"块子列表中. 默认值是0,即立即被转入.

    3.7 MySQL是如何开关表的

     MySQL是多线程的,所以在同一时刻可能会有多个客户端在查询同一个表,因此为了减少在同一个表上,因多个会话具有不同状态而导致的问题,每一个并行的会话都会独立地打开表.这样做会使用额外的内存,但也会提高性能. 对于MyISAM表来说,每个客户端都会需要一个额外的数据文件(data file)的描述文件;相反,所有会话会共享一个索引描述文件. table_open_cache,max_connections,以及max_tmp_tables,都会影响系统同时打开文件的数量.

    3.8在同一数据库下创建太多表的坏处

    如果在同一数据库下有太多的MyISAM表,那么打开,关闭和创建操作会变得很慢.当在很多不同的表上执行SELECT时,会在table cache满时,会有小的开销,因为要打开新的表,就必须得关掉其它的表,为些,可以通过适应提高table_open_cache值,为降低这种开销.

4.MySQL Server优化

     4.1系统因素与启动参数优化

      操作系统的选择是非常重要的.为了使多CPU机器发挥出最佳性能,应该Solaris或Linux操作系统.除此之外,还可以参与以下帮助信息:

  • 如果有足够的RAM,则应该移除所有的交换设备
  • 避免外部锁定(external locking). 从MySQL4.0版本之后,默认情况下外部锁定是不可用的.可以通过--external-locking and --skip-external-locking 来开关此功能. 需要注意的是,如果你只运行一个Server,那么外部锁不会影响MYSQL的功能,只需要记得在进行myisamchk之前lock和flush相关的表. 但如果当多个MYSQL SERVER使用同一个DATA目录时,就必须需要开启外部锁功能. LOCK TABLES和UNLOCK TABLES使用的是内部锁定(internal locking),因此与外部锁无关.

     4.2 启动参数优化

     我们可能通过以下命令来查看MYSQL当前系统变量值及SERVER状态:

  • mysql> show variables;
  • mysql> show status;
  • shell> mysqladmin variables [-u -p]
  • shell> mysqladmin extended-status [-u -p]

     MYSQL的算法设计应该是很灵活的,所以只需要很少的内存就可以运行.但是,如果给予更多内存的,就会得到更好的恨不能.

     MYSQL SERVER优化中两个最重要的变量是:key_buffer_size与table_open_cache, 因此在尝试修改其它变量之前,需要确定这两个变量已经被正确或切当的设置.

    4.3 控制查询优化器性能

       查询优化器的任务是找到一个对执行某个SQL查询最优的方案.因为优化性能在"好"与"坏"之间的差异可以是以数量级算的,所以很多的查询优化器,包括MYSQL的,会在所有可能的查询评估方案中,或多或少地详尽搜索出一个最优方案. 就连接查询(join)而言,MYSQL优化器的优化方案会随查询中涉及表的数目成指数增长. 对少于7-10表的查询来说,这不是问题,但是,如果更多的查询被提交,那么在查询优化上所花的时间就会成为服务器性能的主要瓶颈.

      通常的观点是,优化器可供选择的方案越少,其在编译查询时所花的时间就越少;另一方面,因为优化器忽略了一些方案,所以有可能就失去了最佳方案. 优化器的可用评估方案的数量,可通过下面两个系统变量来控制:

  • optimizer_prune_level 用来告诉优化器,可以通过评估每个表被访问记录的数量来忽略某些方案. 经验显示,这种类型的"学习猜想(educated guess)",很少会错失掉最佳方案,并会很明显地减少查询编译次数.这也是为什么MYSQL默认会设置:optimizer_prune_level=1的原因. 当然我们也可以将optimizer_prune_level=0,但需要承担编译查询可能会持续很长时间的危险.
  • optimizer_search_depth,用来告诉优化器,对一个不完整方案的最大评估深度是多少.其值越小,那么查询编译的次数(时间)就会越少.如果此值为0,那么优化器会自动决定其值.

    4.4 MySQL查询缓存(query cache)

      SELECT语句及其返回结果都会放入到查询缓存中,如果再有同样的语句出现,服务器会从查询缓存中读取结果.查询缓存是SESSION共享的,因此一个客户端产生的结果,同样也可以被另外一个客户端采用. 查询缓存不会返回过时的数据,当对应表被更新时,缓存中的相应条目都会被刷新.

      查询缓存可以提供大量的性能改进的潜力,但是您不能认为在所有情况下都如此.在不同的查询缓存配置或服务器工作负载,可能会出现性能下降的情况:

  • 需要注意的是并不是缓存越大越好,缓存过大会使缓存维护的成本加大,因此很可能适得其反.通常在几十兆字节是有益的,几百兆字节就难说了
  • 服务器工作负载对查询缓存的效率有着明显的影响.很明显,具有固定结果集的SELECT语句要比具有频繁INSERT语句执行的结果集更可能受益于查询缓存. 某些情况下,解决方法是使用SQL_NO_CACHE选项,以防止使用了会变频繁修改的表的SELECT语句的结果被缓存.

     4.4.1 查询缓存如何运作

      服务器在收到查询语句并解析之前,先查看查询缓存中有没有一样的语句.需要注意的是相比较的两个查询语句必须绝对的一致才可以,即大小也是敏感的. 且缓存不会用在以下情况:

  • 查询本身是outer查询的子查询
  • 查询在存储函数,触发器或事件中的查询

      当从查询缓存中返回结果之后,服务器会将Qcache_hits值 加1.

       如果某个表发生了改变,那么缓存中所有与此表相关的查询都将被从缓存中移除.

      4.4.2 查询缓存SELECT参数

  • SQL_CACHE 如果此查询语句是可缓存的,且变量query_cache_type的值需要为ON 或 DEMAND时,返回的结果会被缓存
  • SQL_NO_CACHE 查询结果不会被缓存

      4.4.3 查询缓存配置

      系统通过判断have_query_cache来判断是否需要缓存,通过query_cache_size来设定缓存大小,如果为0,则说明查询缓存功能关闭,如果不为0,那么query_cache_size的最小值是40KB,小于时会提示警告(show warnings\G). 如果query_cache_size大于0,那么query_cache_type将影响其工作方式:

  • 0/OFF 将阻止缓存及从缓存中读取结果
  • 1/ON 将允许缓存,除非使用SELECT SQL_NO_CACHE
  • 2/DEMAND 只有使用SELECT SQL_CACHE时才会使用缓存

     可以通过query_cache_limit 来限定每个查询可缓存的记录大小,默认为1M. 查询缓存在需要时通过申请内存块来存储数据,因此当一个块已经满时,另一个新块又会被申请.因为内存申请操作的代价是比较昂贵的,所有我们需要用query_cache_min_res_unit来指定每个块的最小大小.当一个查询被执行之后,最近申请的结果块会被修剪成实际的大小,而没有使用到的部分会被释放掉. 依据不同的查询类型,通过调整query_cache_min_res_unit可以达到比较好的效果:

  • query_cache_min_res_unit = 4K是默认值,对大多数情况来说,应该是足够了
  • 如果有很多返回小结果的查询,那么默认的大小会导致内存碎片.碎片会迫使查询缓存在内存不足时裁剪(删除)查询. 为此,我们需要降低query_cache_min_res_unit的值. 我们可以通过Qcache_free_blocks及Qcache_lowmem_prunes来查询释放的内存块及因裁剪而删除的查询.
  • 如果大多数查询都会返回大的结果(通过Qcache_total_blocks and Qcache_queries_in_cache两个状态来查看),可以适当增大query_cache_min_res_unit值来提高性能,不过切记不要太大(参考上一条)

     4.4.4 查询缓存状态与维护

      通过FLUSH QUERY CACHE 可以进行碎片整理,且不会多缓存中移除任何查询,如果执行REST QUERY CACHE 或 FLUSH TABLES则会从查询缓存中移除所有查询结果.

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  MySQL 优化