目录
一、背景
二、案例场景举证和分析
三、优化分析措施总结
四、常见优化相关配置
五、延伸分析探讨
很早以前针对多次的系统性能压测和优化,有过一份ppt经验总结,在内部团队也进行过分享。在这里结合当时的ppt梳理下mysql优化遇到过的一些问题、解决问题的过程,以及总结的一些经验。
一般在系统正式上线前,会结合系统的使用场景和热点功能进行一次压力测试,从中分析出系统性能缺陷点,以调整提升系统瓶颈。
下面抽取压测中遇到的两个比较典型的问题作为分析:
案例1-事务响应时间变慢——随着并发数增加,在服务器资源未到达瓶颈时,服务器接口数据返回变慢
接口响应时间趋势图
服务端资源监控数据
从上图的压测数据看出,随着并发数的上升,部分功能接口响应时间成倍增长,但服务器资源监控平稳。也就是说服务器的资源未能利用起来,接口的逻辑也比较简单,执行sql返回数据,最大的可能性也就出现在mysql的查询上。
拿sql直接放在数据库执行,在平时执行很快(未进行压测时)毫秒级返回,但是在压测的时候执行,却需要秒级甚至十几秒才能完成。正好印证上面的接口响应趋势图,随着并发数上升,接口响应时间成倍增长。
确定瓶颈在数据库后,着手监控数据库服务器的资源情况,发现CPU等资源压力很小,IO也不高。查看IO读写详情,发现有大量的小文件读写操作(最后确定为mysql临时表文件)。调整sql,去掉子查询中sql字段中的大字段(TEXT、BLOB和长度大于512字节)后,发现sql执行恢复到毫秒级。至此,确定mysql磁盘临时表导致的查询瓶颈。
众所周知,IO的峰值和写入文件大小的情况有关,持续写入一个大文件磁盘所能达到的IO峰值肯定远远大于持续写入大量的小文件。正是由于磁盘临时表产生的是大量小文件,单单从数据库IO的峰值情况不能判断数据库的写IO是否达到瓶颈。
案例2-每秒事务数TPS突然下降——程序运行一段时间后,在服务器资源未到达瓶颈时,服务端处理能力突然下降
每秒事务数TPS趋势图
服务端资源监控数据
从上图的压测数据看出,随着并发数上升,前期的tps数据呈现健康的增长趋势。在运行到一定时间后tps断崖式下降,但是服务器资源监控正常,如果说是遇到性能瓶颈,tps应该是维持在高位水平,无法上升才对,不应该出现TPS断崖式下降,因为服务器资源并未减少。
排查应用日志,发现大量如下错误:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
引起上述异常的原因:应用端线程池空闲线程数设置过低,当大量请求并发访问时会创建大量的数据库连接,由于空闲连接数据设置过低,使用完后就会关闭连接。但此时仍有大量请求,连接数不够又将会重新建立新的数据库连接,频繁销毁和建立数据库连接,将会引起系统层面的异常,导致上面的非预期异常。
解决方案:根据实际情况调大数据库连接池的最大空闲线程数。
在非压力情况下,sql执行正常。当并发量大时,sql执行非常缓慢,有的情况下执行效率可能会降了几十倍。导致这种原因的情况有很多种:
1、服务器资源不够了,比如cpu、内存、io等。在这样的情况下,sql执行慢还算正常,如果是并发量不大,服务器资源占用过高,就得排查具体sql的执行情况,优化业务逻辑。
2、服务器资源占用不高,但是sql执行时间明显延长。引起这种问题的原因大多数时候都是语句执行查询时产生的磁盘临时表。Sql执行中多数时候都会需要用到临时表(内存临时表、磁盘临时表),生成的临时表性质对整个sql的执行至关重要。
判断sql执行生成临时表的性质:
SHOW STATUS LIKE '%tmp%';
Created_tmp_disk_tables –产生的磁盘临时表数
Created_tmp_tables—产生的所有临时表数
直接使用磁盘临时表的场景(尽量避免磁盘临时表)
1)表包含TEXT或者BLOB列;
2)GROUP BY 或者 DISTINCT 子句中包含长度大于512字节的列;
3)使用UNION或者UNION ALL时,SELECT子句中包含大于512字节的列;
内存临时表相关配置
tmp_table_size:指定系统创建的内存临时表最大大小
max_heap_table_size: 指定用户创建的内存表的最大大小
注意:最终的系统创建的内存临时表大小是取上述两个配置值的最小值。
大量并发请求时,执行一段时间后sql查询变慢
当某一连接访问一个表时,MySQL会检查当前已缓存表的数量。如果该表已经在缓存中打开,则会直接访问缓存中的表已加快查询速度;如果该表未被缓存,则会将当前的表添加进缓存并进行查询。
在执行缓存操作之前,table_cache用于限制缓存表的最大数目:如果当前已经缓存的表未达到table_cache,则会将新表添加进来;若已经达到此值,MySQL将根据缓存表的最后查询时间、查询率等规则释放之前的缓存。
如果Open_tables的值已经接近table_cache的值,且Opened_tables还在不断变大,则说明mysql正在将缓存的表释放以容纳新的表,此时可能需要加大table_cache的值。
大量并发请求时,update锁表
Innodb的行级锁,只是在条件为主键时有效,非主键条件的update会根据隔离级别选择间隙锁或Next-key锁。所以,在进行update操作时尽量通过主键去操作。同时,需要选择合适的事务隔离级别,spring等框架默认是采用RR事务隔离级别,RR级别最高最安全,但是会产生一些不必要的范围锁而影响性能,大多数场景下RC级别的事务已经可以满足要求。
Mysql在5.0.37版本以后,本身提供了profile分析工具,用以分析sql的执行瓶颈。
主要的使用命令
set profiling = 1; --开启profile
show profiles; --最近执行的sql
show profile for query (查询语句id); --查看sql的每个步骤的执行耗时情况
记录下所有执行超过long_query_time时间的SQL语句, 帮你找到执行慢的SQL, 方便我们对这些SQL进行优化.
--开启
slow_query_log = 1
--设置日志存放路径
slow_query_log_file = "G:/mysqllog/slow_sql.log“
--超时阀值,执行超过long_query_time的sql将会被记录下来
long_query_time = 2
Mysql数据库关键参数配置
#innodb_buffer_pool_size = 6G --mysql缓冲池,缓存数据和索引
#innodb_additional_mem_pool_size=256M --设置 InnoDB 存储的数据目录信息和其它内部数据结构的内存池大小
#innodb_flush_log_at_trx_commit=0 –事务日志flush到硬盘的方式 0、1、2
#innodb_thread_concurrency=0 –默认0由系统自动去分配线程,没必要不要改变
#table_open_cache = 4096 --所有线程同时能打开表的数量,并发大查询快建议调高
#thread_cache_size = 512 --mysql空闲线程连接数
# 线程独享内存
#sort_buffer_size = 2M
#read_buffer_size = 2M
#read_rnd_buffer_size = 2M
#join_buffer_size = 4M
#net_buffer_length = 512KB
#bulk_insert_buffer_size = 100M
#tmp_table_size = 512M
#max_heap_table_size = 512M
临时表存储性质区分
MySQL临时表分为“内存临时表”和“磁盘临时表”,其中内存临时表使用MySQL的MEMORY存储引擎,磁盘临时表使用MySQL的MyISAM存储引擎;
一般情况下,MySQL会先创建内存临时表,但内存临时表超过配置指定的值后,MySQL会将内存临时表导出到磁盘临时表;
Linux平台上缺省是/tmp目录,/tmp目录小的系统要注意啦。
使用临时表的场景
1)ORDER BY子句和GROUP BY子句不同, 例如:ORDERY BY price GROUP BY name;
2)在JOIN查询中,ORDER BY或者GROUP BY使用了不是第一个表的列 例如:SELECT * from TableA, TableB ORDER BY TableA.price GROUP by TableB.name
3)ORDER BY中使用了DISTINCT关键字 ORDERY BY DISTINCT(price)
4)SELECT语句中指定了SQL_SMALL_RESULT关键字 SQL_SMALL_RESULT的意思就是告诉MySQL,结果会很小,请直接使用内存临时表,不需要使用索引排序 SQL_SMALL_RESULT必须和GROUP BY、DISTINCT或DISTINCTROW一起使用 一般情况下,我们没有必要使用这个选项,让MySQL服务器选择即可。