数据展现系统出现异常,首页刷不出数据,查看日志后发现模块没法链接数据库(从库,如下数据库都表示从库),紧接着数据分析模块出现报警,服务器出现磁盘空间不足报警。mysql
查看AWS RDS监控发现,在时间段 ‘08.31 19:15:00 - 09.01 01:19:00’ 内,数据库实例存储空间急剧降低sql
监控显示,数据库链接数量没有明显增长,但在该时间段内磁盘IO明显增大数据库
磁盘IO缓存
数据库链接数量服务器
经过查看实例上磁盘的占用状况,发现磁盘临时表占用了大量空间(420G/500G),因而咱们开始定位为什么会有大量的临时表被持久化到磁盘上多线程
MySQL中有两种临时表:外部临时表和内部临时表。其中外部临时表可有用户查询时手动建立保存一些中间数据,提高查询效率等;并发
内部临时表会在查询过程当中由Mysql自动建立并存储某些中间操做的结果,这种操做可能出如今优化阶段或者执行阶段,因此这种临时表对用户不可见,通常经过EXPLAIN能够查看查询语句是否用到了内部临时表。ide
而内部临时表又能够被分为磁盘临时表和内存临时表,Mysql在使用内部临时表时会优先使用内存临时表,即将临时表存放在内存里,当临时表过大或内存不够用时,就会转化成磁盘临时表,存放在ibtmp1文件中。函数
在Mysql5.7中,内存临时表在查询结束后会被释放,而文件ibtmp1占用的空间不会被自动释放,但会被标记删除,等数据库重启时释放这部分空间。测试
(如何释放?数据库服务关闭时,会直接删除ibtmp1文件,等数据库重启时,会新建一个分配了初始空间新ibtmp1文件)
内存临时表可以使用的内存空间(min('max_heap_table_size', 'tmp_table_size')):16M
初始ibtmp1文件大小(innodb_temp_data_file_path):12M且增加无上限
查看mysql的monitor device_base和device_bt_base的rate暴涨,插入等正常,由此猜想是这两个表查询引发的临时表使用空间暴涨,查看是事故时间的query,pharos请求相较平成更加频繁,由此猜想是并发量太大致使,接着预本地模拟,尝试重现事故。
建立测试表
CREATE TABLE `account` ( `user_id` bigint(20) NOT NULL, `address` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `email` varchar(255) NOT NULL, `nickname` varchar(255) DEFAULT NULL, `password` varchar(255) NOT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
开始测试
1.执行会使用临时表的查询语句,且不显示释放客户端,单线程查询3000次
def groupData(k): i = 0 a = [] while i < 3000: i = i + 1 conn = pymysql.connect(host='127.0.0.1', port=3307, user='root', password='123456', db='mysql') cur = conn.cursor() group_sql = 'SELECT address, COUNT(*) as count from account where user_id > %s GROUP BY address' % (3000 + i / 2 * 10000) print(group_sql) cur.execute(group_sql) for i in a: print(len(i)) time.sleep(100) print("stop") |
执行结果为:ibtmp1文件未进行任何扩展,查询结束后依然为12M。代表每次查询的临时表都使用了内存空间,执行完成以后空间被释放
2.依然使用上面的函数groupData进行查询,但改用多线程并发执行,查询总次数一致,用20个线程每一个查询150次
def groupData(k): i = 0 a = [] while i < 150: i = i + 1 conn = pymysql.connect(host='127.0.0.1', port=3307, user='root', password='123456', db='mysql') cur = conn.cursor() group_sql = 'SELECT address, COUNT(*) as count from account where user_id > %s GROUP BY address' % (3000 + k / 2 * 10000) print(group_sql) cur.execute(group_sql) for i in a: print(len(i)) time.sleep(100) print("stop") if __name__ == '__main__': with ThreadPoolExecutor(max_workers=40) as e: for k in range(40): e.submit(groupData, k) |
执行结果为:ibtmp1文件进行了460次扩展,查询结束后ibtmp1文件大小为460M。代表查询时大量内部临时表使用了磁盘临时表
致使产生本次问题的SQL语句以下。device_type和config_model字段没有索引,查询会产生较大的临时表。须要对该字段添加索引避免产生临时表。
SELECT device_type, config_model, count(*) AS rowCount FROM device_base GROUP BY device_type, config_model |
为什么为突然出现该问题?
系统中利用缓存限制访问数据库的次数,并发访问时会经过缓存获取数据。但因为更新缓存的操做没有加锁,当缓存大面积失效且有大量请求时,出现该问题
数据库实例参数设置
目前咱们的数据库临时表空间的参数为默认参数,即初始大小为12M,可扩展大小无限制。可考虑设置扩展大小上限,避免磁盘被直接耗尽影响到全部的数据库操做。
innodb_temp_data_file_path = ibtmp1:12M:autoextend:max:20000M |