亚马逊AWS官方博客
快马加鞭 – MYSQL RDS 写入性能参数优化
Amazon Relational Database Service 也称为 Amazon RDS,是AWS托管的关系型数据库服务。Amazon Relational Database Service (以下简称 RDS) 让您能够在云中轻松设置、操作和扩展关系型数据库,把您从繁琐耗时的管理任务(如硬件预置、数据库设置、修补和备份)中解放出来,让您能专注于自身业务。
如果您的RDS数据库实例在创建时未指定数据库参数组,数据库实例将使用默认的数据库参数组。 每个默认数据库参数组包含数据库引擎默认值和 Amazon RDS 系统默认值,具体根据引擎、计算等级及实例的分配存储空间而定。您也可以根据实际情况,创建新参数组并让您的数据库实例使用新参数组,然后修改参数。本文将就如何通过调整参数来提高MYSQL RDS写入性能进行讨论,希望给您的调优工作带来一点启示。
本文首先会为您介绍修改和显示RDS参数的方法,然后会列出一些与写入性能相关的数据库参数,最后会通过sysbench工具对参数调整效果进行测试。
一、修改和显示RDS参数
在开始介绍一些性能相关参数之前,我们先来介绍一下如何修改和显示RDS的参数。您可以修改客户创建的数据库参数组中的参数值,但不能更改默认数据库参数组中的参数值,所以如果您要修改参数必须先让你的数据库使用自定义的参数组。
您可以通过AWS console的RDS部分的参数组界面创建或者修改您的自定义参数组。点击参数组名称链接可以列出所有参数,在此可以筛选和修改相关参数。
我们也可以通过登录数据库使用show variables命令来查看和修改参数,对于全局参数前面要加global,否则显示的是session级参数,另外我们也可以通过命令修改session级别的参数,只要加上session关键字,如下图所示,我们修改的autocommit参数只对当前session有效,对其他session无影响。
二、写入性能优化的一些参数
说完了如何修改和查看RDS参数,我们接下来看一下一些和写入性能相关的参数,限于篇幅,我们不能介绍所有的MYSQL参数。
innodb_buffer_pool_size
在MYSQL中buffer pool用来缓存表和索引的数据,以便加速对数据的处理。如果在buffer在pool中无法获取数据(所谓cache miss),那么就会产生磁盘的随机IO请求,这会降低处理速度,所以配置一个合适大小的buffer pool对性能至关重要。RDS中innodb_buffer_pool_size会设置为{DBInstanceClassMemory*3/4}。
我们可以通过show engine innodb status \G和SHOW GLOBAL STATUS命令观察innodb_buffer_pool_size设置是否合适。
通过show engine innodb status \G我们可以看到free buffers、buffer pool hit rate以及evicted without access(数据被加载到buffer pool没有被访问前就又被推出buffer pool)。
- 如果较长时间内即使业务高峰期,free buffers都很大,buffer pool hit rate 高于99.5%,evicted without access为0,则innodb_buffer_pool_size可能过量设置了。
- 如果较长时间内尤其业务高峰期,free buffers都很小,buffer pool hit rate 低于99%,evicted without access不为0,则说明innodb_buffer_pool_size设置得太小,需要增大。
show global status where variable_name in (‘Innodb_buffer_pool_read_requests’, ‘Innodb_buffer_pool_reads’); 我们可以关注以下数据,来判断innodb_buffer_pool_size设置的是否合适。
- innodb_buffer_pool_reads表示InnoDB缓冲池无法满足的请求数。需要从磁盘中读取。
- innodb_buffer_pool_read_requests表示从内存中逻辑读取的请求数。
- Innodb_buffer_pool_read_requests / (Innodb_buffer_pool_read_requests + Innodb_buffer_pool_reads) * 100 = InnoDB Buffer Pool hit ratio
注意:对于 MySQL 5.7,管理 InnoDB 缓冲池的方式当前存在错误https://bugs.mysql.com/bug.php?id=79379。MySQL 5.7 可能将 innodb_buffer_pool_size 参数的值调整为较大的值,这会导致 InnoDB 缓冲池增长得过大并占用过多内存。此效果会导致 MySQL 数据库引擎停止运行或阻止 MySQL 数据库引擎启动。可用内存较少的数据库实例类更易出现此问题。要解决此问题,请将 innodb_buffer_pool_size 参数的值设置为 innodb_buffer_pool_instances 参数值和 innodb_buffer_pool_chunk_size 参数值的积的倍数。例如,您可以将 innodb_buffer_pool_size 参数值设置为 innodb_buffer_pool_instances 参数值和 innodb_buffer_pool_chunk_size 参数值的积的 8 倍,如以下示例所示。
innodb_buffer_pool_chunk_size = 536870912
innodb_buffer_pool_instances = 4
innodb_buffer_pool_size = (536870912 * 4) * 8 = 17179869184
innodb_log_file_size
在mysql 5.5和5.5以前innodb的logfile最大设置为4GB,在5.6以后的版本中innodb_log_file_size * innodb_log_files_in_group(每个log group中日志文件的个数,默认为2)不能超过512GB。innodb的logfile就是事务日志,用来在mysql 崩溃后的恢复,设置合理的大小对于mysql的性能非常重要。logfile大小对于性能的影响主要体现在checkpoint上,对于 checkpoint的原理可以参考相关的关系数据库理论,更大的logfile大小可以减少checkpoint的次数,减少disk I/O,但是也会增加崩溃后恢复时间。
在MYSQL RDS默认参数组中,innodb_log_file_size默认值为134,217,728 (大约128 MB),通常对您的应用来说该数值嫌小。那么设置多大的logfile合适呢? 通常我们建议innodb_log_file_size * innodb_log_files_in_group要可以承载我们业务高峰期一个小时的日志量,我们可以用命令show engine innodb status\G select sleep(60); show engine innodb status\G 计算每分钟的日志量,进而确定参数的合适大小。此外,基于Bug #69477,日志文件大小应该至少是数据库中最大的BLOB的大小的十倍。
5.0以上版本的MYSQL也可以通过以下命令去查询一分钟内生成日志的大小:
show global status like ‘Innodb_os_log_written%’;
innodb_io_capacity与innodb_io_capacity_max
innodb_io_capacity决定了innodb后台任务(譬如从buffer pool刷新页面或从change pool合并页面)每秒可用的IO操作次数(IOPS)。innodb_io_capacity默认值是200, 可选范围100–18,446,744,073,709,551,615,而innodb_io_capacity_max是当刷新滞后时,后台任务可以执行的最大IOPS,可选范围100–18,446,744,073,709,551,615,如果您设置了innodb_io_capacity但没有设置innodb_io_capacity_max,则innodb_io_capacity_max默认是innodb_io_capacity的两倍,。
关于这两个参数,若你真的需要对它进行调整,最好的方法是要了解系统可以支持多大的 IOPS。对于SSD我们可以设置innodb_io_capacity为1000,虽然可以设置很大值,但是innodb_io_capacity一般很少设置超过20000,除非您已经尝试过较低的值否则不要设置较大的值。
那么我们如何判断脏页刷新不够快呢?我们可以通过SHOW GLOBAL STATUS LIKE ‘Innodb_buffer_pool_%’;命令,如下图所示,脏页在buffer中的百分比就是Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total * 100%
- Innodb_buffer_pool_pages_data 和Innodb_buffer_pool_bytes_data: buffer pool中buffer总数。
- Innodb_buffer_pool_pages_dirty 和Innodb_buffer_pool_bytes_dirty: buffer pool中脏buffer的数量。
- Innodb_buffer_pool_pages_total: buffer pool总页数。
如果使用了information_schema,我们也可以通过以下语句来查询
总之,如果脏页占比长时间较高且从监控看IO有较大余裕,则可调大innodb_io_capacity,但不要一下调整过大,要逐步提高。
innodb_flush_log_at_trx_commit
innodb_flush_log_at_trx_commit定义数据库写log buffer到磁盘的频率以及方式 ,磁盘写入会影响性能,故而此参数让您可以在性能和持久性之间做出选择。
- 如果innodb_flush_log_at_trx_commit设置为0,每秒一次会将log buffer写入log file(这时数据只是从innodb内存到了操作系统的cache,依然没有进入磁盘), log file的flush(刷到磁盘)操作也同时进行。该模式下,在事务提交的时候,不会主动触发写入磁盘的操作。
- 如果innodb_flush_log_at_trx_commit设置为1,每次事务提交时MySQL都会把log buffer的数据写入log file,并且flush(刷到磁盘)中去。
- 如果innodb_flush_log_at_trx_commit设置为2,每次事务提交时MySQL都会把log buffer的数据写入log file.但是flush(刷到磁盘)操作并不会同时进行。该模式下,MySQL会每秒执行一次 flush(刷到磁盘)操作。
当设为0和2的时候,flush操作不能100%保证,所以在崩溃或断电的时候可能会丢失最后一秒的数据,而1是默认也是最安全的设置,保证commit的数据不会丢失。MYSQL RDS默认 innodb_flush_log_at_trx_commit也是1,这会对性能有消极影响但除非您可以接受丢失数据,否则不建议修改。
sync_binlog
sync_binlog 控制 MySQL server 将 binary log 同步到磁盘的频率,MYSQL RDS默认设置为最安全的1,但是同样会对性能有消极影响,除非您可以接受丢失数据,否则不建议修改。
- sync_binlog=0: MySQL不控制binlog的刷新,由文件系统自己控制它的缓存的刷新。这时候的性能是最好的,但是风险也是最大的。因为一旦系统崩溃,在binlog_cache中的所有binlog信息都会被丢失。
- sync_binlog=1: 这是最安全的设置,表示每次事务提交,MySQL都会把binlog刷下去,不过最安全也是性能损耗最大的设置,尤其对高并发的场景sync_binlog设置为0和1之间写入性能相差会很大,甚至达到5倍之多。
- sync_binlog=N, N 是 0 和1以外的正整数: binary log 会在收集N 个binary log commit groups 之后同步,同样在系统崩溃的时候 ,可能会丢失数据,N越大性能越好但同时丢失数据的风险也越大。
tmp_table_size 与max_heap_table_size
MYSQL在很多场景譬如UNION操作,子查询,排序分组操作中都可能会生成temporary table,我们通过 EXPLAIN 输出执行计划的Extra列可以看到SQL是否使用了temporary table。temporary table的最大大小由tmp_table_size 与max_heap_table_size其中较小的值决定(MYSQL RDS这两个参数默认值都是16777216即16M),temporary table在大小未超过上限时创建在内存中,一旦超过该上限,temporary table就会自动转为磁盘上的表,这将增加磁盘IO和消耗的时间。我们可能都遇到过复杂查询执行很长时间,从show full processlist可以看到copying to tmp table on disk的状态,这就说明我们的temporary table超过了上限,转为了磁盘上的表。
我们可以通过命令SHOW GLOBAL STATUS LIKE ‘Created_tmp%’;查看内存和磁盘中temporary table得数量,我们可以通过 Created_tmp_disk_tables 和 Created_tmp_tables 了解在磁盘中temporary table的比例。
如果您的应用有大量复杂查询,Created_tmp_disk_tables 比例居高不下,IO成为系统瓶颈而内存又有较大余裕,您可以考虑逐步调大tmp_table_size 与max_heap_table_size。但是请注意,这会给内存带来压力,此外,某些场景下,temporary table一定会创建在磁盘而不是内存中,譬如:
- 表中存在BLOB或文本列。这包括具有字符串值的用户定义变量,依据列值是二进制还是非二进制字符串而分别被视为BLOB或文本列。
- 在一个GROUP BY或DISTINCT子句中存在大于512字节(二进制字符串)或大于512字符(非二进制字符串)的任何字符串列。
- 使用UNION或UNION ALL,且选择列表中存在最大长度大于512(二进制字符串为字节,非二进制字符串为字符)的任何字符串列。
- SHOW列和DESCRIBE语句使用BLOB作为某些列的类型,因此用于结果的临时表是磁盘上的表
foreign_key_checks与unique_checks
当我们对MYSQL RDS进行大批量数据导入或加载,尤其涉及大表时,我们可以通过暂时关闭外键和唯一约束检查来提高插入性能,但是这会带来潜在的风险,您务必要确保相应的数据一致和唯一性。否则约束检查不能通过。
我们可以按以下方式进行数据库导入:
Innodb_read_io_threads 和 Innodb_write_io_threads
innodb_read_io_threads and innodb_write_io_threads决定了InnoDB使用多少后台线程来处理各种类型的IO请求。通过这个配置InnoDB可以有更好的扩展性,每个后台进程可以处理256个IO请求,InnoDB希望可以平衡请求的负载。两个参数有效值范围是1-64,默认值是4。参数决定后台进程数,但是linux系统默认会设置innodb_use_native_aio 不为0,启动异步IO,就会突破参数限制
如果您有高端的IO系统,且在SHOW ENGINE INNODB STATUS的输出中看到64 × innodb_read_io_threads的pending read requests,那么提升innodb_read_io_threads的值会对提升性能有帮助。
三、测试
3.1 测试环境准备
3.1.1 硬件环境
本次测试将通过sysbench软件对AWS MYSQL5.6 RDS default参数组以及调整后的参数组进行压测,比对调优效果。请注意:sysbench测试是用来辅助我们判断系统性能的一个方法,用您的实际业务进行压测永远更有针对性。而且在性能测试中,不同的资源配置(包括测试客户端,数据库本身以及网络等),参数配置乃至测试流程都会导致不同的结果,所以进行比对分析永远要先统一测试环境、流程才有意义。
随着软件迭代,本文中描述的测试流程可能需要更新,请读者把握测试流程和指标含义,在工作中按实际情况进行调整。
本文所用硬件环境如下:
2个sysbench客户端配置如下:
机型 | vCPU | 内存(GiB) | 磁盘 | IOPS |
r5.2xlarge EC2 | 8 | 64 | IO1 SSD 50G | 2020 |
数据库配置如下:数据库开启了multi-AZ,保证数据库高可用,但是会对写入性能有较大影响
数据库 | instance class | vCPU | 内存(GiB) | 磁盘 | IOPS | 多AZ |
MYSQL5.6 RDS | db.r5.2xlarge | 8 | 64 | IO1 SSD 500G | 18000 | 是 |
3.1.2 参数调整
根据硬件环境以及测试时产生的日志量,对参数做了针对性调整,有些参数需要数据库重启才能生效,请在执行相应测试步骤前确认参数值。
请注意:下面的参数仅仅针对本测试中的硬件环境和测试工作量负载,并不具有普遍性,请读者理解参数的含义,根据自身环境和工作负载进行相应配置。正如前述,除非您可以接受丢失数据,否则不建议修改innodb_flush_log_at_trx_commit、sync_binlog等参数为了性能而牺牲安全性,所以本次测试没有对这两个参数进行修改,如果您有类似需求,可以根据本文提供的方法自行测试。
参数 | 默认值 | 调整后的值 |
innodb_io_capacity | 200 | 2000 |
innodb_io_capacity_max | 2000 | 18000 |
innodb_log_file_size | 134217728 | 4294967296 |
innodb_read_io_threads | 4 | 8 |
innodb_write_io_threads | 4 | 8 |
innodb_flush_log_at_trx_commit | 1 | 1 |
sync_binlog | 1 | 1 |
多可用区 | 是 | 是 |
注意:因为sysbench1.0.18会有大量prepared statement,所以要设置max_prepared_stmt_count为最大值1048576保证测试顺利进行,否则初始化时可能遇到错误FATAL: MySQL error: 1461 “Can’t create more than max_prepared_stmt_count statements (current value: 16382)”
3.1.3 测试环境准备
在2台测试客户端安装sysbench1.0.18
1.从git中下载sysbench
2.打开sysbench目录
3.切换到sysbench 1.0.18版本,运行autogen.sh
4.编译
3.1.4 打开限制
开始测试前,需要在Sysbench客户端执行 以下配置,告诉Linux kernel可以用所有的CPU cores 去处理 packets(默认只可以用两个),且减少cores 之间的context switching. 这两个设置是为了用更少的Sysbench 客户端达成吞吐目标.( ffffffff表示使用32个核。请根据实际配置修改,例如实际只有8核,则输入ff)
3.2 测试流程
3.2.1 只读负载测试
测试数据注入
首先通过sysbench客户端在测试数据库上生成测试表,这里生成250个表,每个表有行数25000条,您也可以根据您的目标,调整表的数目和大小,请替换<>之中的各种连接信息,再执行命令,如果您直接从blog拷贝命令请注意格式。后续命令的注意事项相同,将不再赘述
测试
在所有sysbench client同时执行命令模拟负载,每次持续20分钟,同时从console查看数据库CPU,IO,网络等metrics。在这里我们将通过修改num_threads参数连续测试2*32, 2*64,2*128,2*256多种并发连接的场景。
清除测试数据
测试后清除数据命令如下:
3.2.2 只写负载测试
数据注入
测试
同样地,在所有sysbench client同时执行命令模拟只写负载,每次持续20分钟,同时从console查看数据库CPU,IO,网络等metrics。在这里我们将通过修改num_threads参数连续测试4*32,4*64,4*128和4*256多种并发连接的场景。
清除测试数据
3.2.3 读/写混合压力测试
测试数据注入
测试
同样地,在所有sysbench client同时执行命令模拟读写混合负载,每次持续20分钟,同时从console查看数据库CPU,IO,网络等metrics。在这里我们将通过修改num_threads参数连续测试4*32,4*64,4*128和4*200多种并发连接的场景。
清除测试数据
四、 结果分析
4.1 结果解读
数据库性能测试输出结果中的指标包括:
- 每秒执行事务数TPS(Transactions Per Second) 数据库每秒执行的事务数,以COMMIT成功次数为准。
- SysBench标准OLTP读写混合场景中一个事务包含18个读写SQL。
- SysBench标准OLTP只读场景中一个事务包含14个读SQL(10条主键查询、4条范围查询)。
- SysBench标准OLTP只写场景中一个事务包含4个写SQL(2条UPDATE、1条DETELE、1条INSERT)。
- 每秒执行请求数QPS(Queries Per Second) 数据库每秒执行的SQL数。
4.2 结果统计
在不同并发连接数下,使用默认参数组和优化参数组的MYSQL RDS只读负载测试数据如下:
64并发 | 128并发 | 256并发 | 512并发 | |
QPS MYSQL RDS默认参数组 | 125.8K | 125K | 123.8K | 116.8K |
QPS MYSQL RDS优化参数组 | 129.4K | 129.8K | 123.9K | 118.5K |
Response time MYSQL RDS默认参数组(ms) | 5.08 | 10.19 | 20.65 | 43.73 |
Response time MYSQL RDS优化参数组(ms) | 4.87 | 9.82 | 20.6 | 43.05 |
在不同并发连接数下,使用默认参数组和优化参数组的MYSQL RDS只写负载测试数据如下:
64并发 | 128并发 | 256并发 | 512并发 | |
TPS MYSQL RDS默认参数组 | 1723 | 3025 | 2252 | 1554 |
TPS MYSQL RDS优化参数组 | 1747 | 3372 | 5770 | 8005 |
Response time MYSQL RDS默认参数组(ms) | 37.09 | 42.27 | 113.24 | 325.31 |
Response time MYSQL RDS优化参数组(ms) | 36.85 | 37.94 | 44.29 | 63.73 |
在不同并发连接数下,使用默认参数组和优化参数组的MYSQL RDS读/写混合负载测试数据如下:
64并发 | 128并发 | 256并发 | 400并发 | |
TPS MYSQL RDS默认参数组 | 1360 | 2128 | 2430 | 1863 |
TPS AWS MYSQLP RDS | 1399 | 2129 | 2623 | 2787 |
QPS MYSQL RDS默认参数组 | 27.1K | 42.5K | 48.5K | 37.2K |
QPS AWS MYSQLP RDS | 27.9K | 42.6K | 52.4K | 55.7K |
Response time MYSQL RDS默认参数组(ms) | 46.94 | 60.11 | 105.31 | 213.54 |
Response time MYSQL RDS优化参数组(ms) | 45.72 | 59.99 | 97.42 | 142.87 |
4.3 结论
通过以上测试数据,我们可以发现:
- 因为我们调整的参数主要针对写入性能,故而对读取性能影响较小,而对写入性能影响较大 ,这从测试结果中得到了体现。
- 并发连接越多,优化相关参数对MYSQL RDS写入和读写混合场景下的性能提升越明显,最多时,只写场景下同等负载,优化参数组后RDS的平均响应时间可以缩小到默认参数组RDS平均响应时间的约1/5,TPS可以提升到默认参数组TPS的约5倍。
- 同理,对于读写混合场景,当并发数较大或者说写入负载达到一定量时,参数调整的效果才可以展现出来,这是因为同样的并发数读写混合场景的写入负载小于只写场景。
- 测试数据库开启了Multi-AZ以保证安全性,这会对性能产生较大影响,在分析数据时也必须考虑。
结束语
本文为大家介绍的参数只是MYSQL和性能相关的一部分,数据库性能调优是一个系统工程,修改参数增加写入性能的同时,也可能会影响安全或者额外消耗其他资源,sysbench的测试结果也仅仅是供我们调优的参考之一。故而本文只是起到“抛砖引玉”的作用,希望给大家一些参考,您在实际的调优中还需要根据您应用的实际情况进行调整。
附录
《Amazon Relational Database Service (RDS)用户指南》