vlambda博客
学习文章列表

【交易技术前沿】浅谈Mysql私有云数据库内存优化

本文选自《交易技术前沿》总第三十五期文章(2019年6月)

高强 / 上海证券交易所技术公司 上海 200120 

E-mail :[email protected]


  摘要:对于任何一个数据库管理系统来说,内存的分配使用绝对可以算的上是其核心之一了,所以很多希望更为深入了解某数据库管理系统的人,都会希望一窥究竟。本文将结合MySQL私有云数据库实际运行案例对MySQL数据库的内存使用情况进行详细分析,希望对数据库开发和管理有所帮助。
  关键词:数据库;内存分析;缓存优化;内存监控

1. 引言

  MySQL私有云数据库(以下简称RDS)作为上证私有云平台的重要组件,承载着所内绝大部分业务系统MySQL数据库的日常管理以及稳定运行,RDS的性能优化势在必行,而内存优化是性能优化的重要指标之一。不同的应用场景需要不同的配置调整,详细了解各参数的作用及调整方法才能在优化时得心应手。(本文涉及参数名的中文翻译为小编翻译习惯,不一定为官方翻译。)

2. MySQL内存使用主要组成

  MySQL使用内存主要分为全局共享内存和线程独享内存。(本文只介绍对内存影响较大的参数,参数默认值在不同的版本可能不一致,本文采用5.7.18的默认值。)计算公式:

【交易技术前沿】浅谈Mysql私有云数据库内存优化

图1 MySQL内存结构

2.1 全局共享内存

  全局共享内存主要是MySQL实例以及底层存储引擎用来缓存各种全局运算和可共享的缓存信息等。计算公式:

【交易技术前沿】浅谈Mysql私有云数据库内存优化

  ■key_buffer_size:索引缓存大小。MyISAM索引缓存将MyISAM表的索引信息(.MYI文件)缓存在内存中,以提高其访问性能。决定索引处理的速度,尤其是索引读的速度。默认值为8M。如果应用系统中使用的表以MyISAM存储引擎为主,可以适当增加该参数的值,以便尽可能的缓存索引,提高访问的速度。可以通过检查状态值Key_read_requests和Key_reads,控制key_reads / key_read_requests的比例在1:1000左右较为合理。如果请求并发数较大,为了进一步避免对索引缓存的争用,可为不同的索引键指定使用的索引缓存。如果应用系统中使用的表全部采用InnoDB存储引擎,仍需要定义一个索引缓存区,因为MySQL元信息与MyISAM定义相同。
  ■innodb_buffer_pool_size:InnoDB缓存池大小。InnoDB Buffer Pool对 InnoDB存储引擎的作用类似于Key Buffer Cache对MyISAM存储引擎的影响,主要的不同在于InnoDB Buffer Pool不仅仅缓存索引数据,还会缓存表的数据,而且完全按照数据文件中的数据块结构信息来缓存。默认值128M。可以通过(Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100%计算得到InnoDB Buffer Pool的命中率。当缓存池大小大于1G时,将innodb_buffer_pool_instances设置大于1的值可以提高缓存读写的并发,能显著提高高I/O负载时的性能。
  ■innodb_additional_mem_pool_size:InnoDB附加内存池大小。InnoDB附加内存池主要用来存放数据字典信息以及一些内部数据结构。默认值为8M。当MySQL实例中的InnoDB存储引擎表的数量非常多的时候,可适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率。随着多核心CPU的广泛应用和操作系统的成熟,操作系统能够提供性能更高、可伸缩性更好的内存分配器,InnoDB实现的内存分配器已经没有优势,所以该参数在MySQL 5.7.4中移除,统一使用操作系统的内存分配器。
  ■innodb_log_buffer_size:InnoDB日志缓存大小。InnoDB在写事务日志的时候,为了提高性能,先将信息写入Innofb Log Buffer中,当满足 innodb_flush_log_trx_commit参数所设置的相应条件(或者日志缓冲区写满)之后,才会将日志写到文件(或者同步到磁盘)中。默认值为8M。
  ■query_cache_size:查询缓存大小。查询缓存是MySQL用来缓存特定Query的结果集(Result Set)信息,且共享给所有客户端。通过对Query语句进行特定的Hash计算之后与结果集对应存放在Query Cache中,以提高完全相同的Query语句的相应速度。默认值为0。当任何一个表的数据发生任何变化之后,与该表相关的所有Query Cache全部会失效,所以Query Cache对变更比较频繁的表并不是非常适用,但对那些变更较少的表是非常合适的,可以极大程度的提高查询效率,如那些静态资源表,配置表等等。

2.2 线程独享内存

  线程独享内存主要用于各客户端连接线程存储各种操作的独享数据,如线程栈信息,分组排序操作,数据读写缓存,结果集暂存等等。计算公式:

【交易技术前沿】浅谈Mysql私有云数据库内存优化

  ■read_buffer_size:顺序读取缓存大小。主要用于当需要顺序读取MyISAM表数据的时候的缓存(如无法使用索引的情况下的全表扫描,全索引扫描等)。默认值为128K。MySQL按照数据的存储顺序依次读取数据块,每次读取的数据块首先会缓存在Read Buffer中,当buffer空间被写满或者全部数据读取结束后,再将buffer中的数据返回给上层调用者,以提高效率。
  ■read_rnd_buffer_size:随机读取缓存大小。和顺序读取相反,主要用于非顺序读取(如排序后的顺序)数据的时候的缓存,但该缓存适用于任何存储引擎的数据。默认值为256K。
  ■sort_buffer_size:排序缓存大小。该缓存用来进行MySQL的排序操作,当排序缓存大小无法满足排序实际所需内存的时候,MySQL会将数据写入磁盘文件来完成排序。默认值为2M。
  ■thread_stack:线程堆栈。当MySQL创建一个新的连接线程时,需要给它分配一定大小的内存堆栈空间,用来存放线程自身的标识信息和各种运行时状态等。默认值为256K。
  ■join_buffer_size:关联缓存大小。MySQL在完成某些Join需求的时候(all,index,range,index_merge),为了减少参与Join的被驱动表的读取次数以提高性能,需要使用到Join Buffer来协助完成Join操作。默认值为128K。当Join Buffer太小,MySQL不会将该Buffer存入磁盘文件,而是先将Join Buffer中的结果集与需要Join的表进行Join操作,然后清空Join Buffer中的数据,继续将剩余的结果集写入此Buffer中,如此往复,会造成被驱动表需要被多次读取,成倍增加IO访问,降低效率。
  ■bulk_insert_buffer_size:批量插入缓存大小。在进行单INSERT语句批量插入数据时,MySQL会先将提交的数据放入Bulk Insert Buffer中,当该缓存被写满或者提交完所有数据之后,MySQL才会一次性将该缓存空间中的数据写入数据库并清空缓存。默认值为8M。当进行LOAD DATA INFILE操作来将文本文件中的数据Load进数据库的时候,同样会使用到此缓冲区。
  ■binlog_cache_size:二进制日志缓存大小。用来缓存由于各种数据变更操做所产生的Binary Log信息。默认值为256K。为了提高系统的性能,MySQL 并不是每次都是将二进制日志直接写入Log File,而是先将信息写入Binlog Buffer中,当满足某些特定的条件(如sync_binlog参数设置)之后再一次写入Log File中。
  ■tmp_table_size:临时表大小。默认值为2M。当临时表小于tmp_table_size 参数所设置大小的时候,MySQL会将临时表创建成内存临时表,只有当tmp_table_size所设置的大小无法装下整个临时表的时候,MySQL才会将该表创建成MyISAM存储引擎的表存放在磁盘上。不过,当另一个系统参数 max_heap_table_size的大小还小于tmp_table_size的时候,MySQL将使用max_heap_table_size参数所设置大小作为最大的内存临时表大小,而忽略tmp_table_size所设置的值。
  ■connections:连接数。可通过配置max_connections参数限制允许最大连接数。

3. 案例分析及优化:

  案例背景:
  RDS部分实例内存使用率不断升高,无法释放,频繁触发预警值,即使对实例进行内存扩容后,内存使用率仍会继续升高,再次触发预警值,只能通过重启数据库实例进行内存释放。尤其是初始内存分配在2-8G左右的实例情况尤为突出。
  实例内存使用率不断增大,首先计算一下各主要内存相关参数配置是否合理。通过查看MySQL配置文件,计算结果为:
total_memory = ((8388608 + 2576980378 + 67108864 + 0 + 0) + (475136 + 954437 + 477219 + 238592 + 262144 + 2097152 + 262144 + 8388608) * 114) / 1024 / 1024 / 1024 = 3.87G。考虑到计算公式是根据参数最大值计算,实际使用可能会更小,实例分配的最大内存为4个G,参数配置是合理的。
  其次需要确认是什么线程或事物消耗了大量内存。MySQL 5.7中performance_schema新增了几张表用于从不同维度查看内存消耗:

【交易技术前沿】浅谈Mysql私有云数据库内存优化

  要开启所有内存的指标监控,需要通过instrument来实现,配置文件中添加performance_schema_instrument = 'memory%=counted'。也可直接在线修改对应参数生效:update performance_schema.setup_instruments set enabled = 'yes' where name like 'memory%';但在线修改参数后只对新增的内存对象有效。
  打开内存监控后,通过查看全局内存使用情况发现2个问题:

1、消耗最大的事务无疑是innodb_buffer_pool,但是发现该事务占用的内存为3G,与配置文件中配置的2.4G不一致。
  memory/innodb/buf_buf_pool  
  2、第二消耗大的事务为SQL执行,且为同一个IP的同一个账号。
  memory/sql/String::value
  针对问题1,通过查询官方文档发现,在5.7.5后,MySQL引入一个新特性,在增大和减小缓存池大小时,是以chunk的形式进行执行操作,chunk大小由innodb_buffer_pool_chunk_size配置选项定义,默认值为128 MB。所以缓存池大小必须始终等于innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数。如果将缓冲池大小更改为不等于或等于innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数的值,则缓冲池大小将自动调整为innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的倍数的值(向上取整)。当前实例innodb_buffer_pool_instances参数配置为8,所以innodb_buffer_pool_size必须为1G的倍数。RDS实例在初始化或内存资源配置调整时,会根据当前分配内存动态调整innodb_buffer_pool_size和相关内存参数,innodb_buffer_pool_size取值为分配内存的60%,如果取值不为1G的整数,实际使用缓存大小仍会很大。但当innodb_buffer_pool_size值小于1G时,innodb_buffer_pool_instances参数不会生效,此时缓存实际大小等同于配置大小。这就解释了为什么内存小于2G的实例内存使用率反而较为正常。
  针对问题2,通过查询获得的IP和账号找到可能的进程或服务,分析数据库连接代码。
  conn = MysqlConn(dbconf)
  cursor = conn.cursor()
  cursor.execute(‘show global status;’)
  result = cursor.fetchall()
  return result
  发现该段代码中在使用cursor执行完SQL后并没有close cursor,在单个数据库连接请求中,如果close connection后,系统会自动释放cursor申请的内存。但如果客户端是使用的连接池,单个connection执行完后并不会立即释放,会回到连接池中,等待下一个请求上来时继续使用以节省断链和建链开销。但从连接池中获取的连接并没有释放上次使用cursor所申请的内存,执行下一次cursor调用时,会重新再申请一次内存,时间一长,分配未释放的内存就会逐渐增多。
  对上述代码进行优化修改,在return result前添加cursor.close()代码后,该问题得以解决。

4. 总结及建议

  数据库在系统运行中的重要性是不言而喻,保障数据库的安全稳定运行也是运维人员的重要职责之一。不管是开源组件还是商业化产品,只有了解它的运行原理,熟悉它的各种功能,才能在出现问题的时候快速定位并解决问题。RDS是在开源Mysql的基础上进行的功能封装,本文介绍了底层Mysql的一些重要内存参数及使用场景,希望能对Mysql内存调优方面的工作有所帮助。



免责声明




-------------------------- 上海证券交易所为证券公司、基金管理公司等市场参与者及相关行业机构提供交易技术支持与服务,包括日常交易技术支持、技术交流研讨、市场调查反馈、证券信息技术知识库、测试等服务。

点击"阅读全文"了解详情