Mysql临时表突增问题定位与分析

1、问题现象

数据展现系统出现异常,首页刷不出数据,查看日志后发现模块没法链接数据库(从库,如下数据库都表示从库),紧接着数据分析模块出现报警,服务器出现磁盘空间不足报警。mysql

image2020-9-4_11-42-20.png


查看AWS RDS监控发现,在时间段 ‘08.31 19:15:00 - 09.01 01:19:00’ 内,数据库实例存储空间急剧降低sql

image.png


监控显示,数据库链接数量没有明显增长,但在该时间段内磁盘IO明显增大数据库

磁盘IO缓存

image.png

数据库链接数量服务器

image.png

经过查看实例上磁盘的占用状况,发现磁盘临时表占用了大量空间(420G/500G),因而咱们开始定位为什么会有大量的临时表被持久化到磁盘上多线程

image.png



2、问题分析与复现

2.1 临时表

MySQL中有两种临时表:外部临时表和内部临时表。其中外部临时表可有用户查询时手动建立保存一些中间数据,提高查询效率等;并发

内部临时表会在查询过程当中由Mysql自动建立并存储某些中间操做的结果,这种操做可能出如今优化阶段或者执行阶段,因此这种临时表对用户不可见,通常经过EXPLAIN能够查看查询语句是否用到了内部临时表。ide

而内部临时表又能够被分为磁盘临时表和内存临时表,Mysql在使用内部临时表时会优先使用内存临时表,即将临时表存放在内存里,当临时表过大或内存不够用时,就会转化成磁盘临时表,存放在ibtmp1文件中。函数

在Mysql5.7中,内存临时表在查询结束后会被释放,而文件ibtmp1占用的空间不会被自动释放,但会被标记删除,等数据库重启时释放这部分空间。测试

(如何释放?数据库服务关闭时,会直接删除ibtmp1文件,等数据库重启时,会新建一个分配了初始空间新ibtmp1文件)

2.2 相关配置项

  • 内存临时表可以使用的内存空间(min('max_heap_table_size', 'tmp_table_size')):16M
    image.png


  • 初始ibtmp1文件大小(innodb_temp_data_file_path):12M且增加无上限
    image.png


  • ibtmp1文件信息(被扩展的总空间) = TOTAL_EXTENTS(拓展次数) * EXTENT_SIZE(每次扩展的大小): 204次 * 1M/次 = 204M
    image.png

2.3 问题分析

查看mysql的monitor device_base和device_bt_base的rate暴涨,插入等正常,由此猜想是这两个表查询引发的临时表使用空间暴涨,查看是事故时间的query,pharos请求相较平成更加频繁,由此猜想是并发量太大致使,接着预本地模拟,尝试重现事故。

2.5 问题复现

  • 搭建数据库实例Mysql5.7.31
  • 建立测试表

    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;


  • 插入测试数据约30W条,表大小为28.56M
  • 开始测试
    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。代表每次查询的临时表都使用了内存空间,执行完成以后空间被释放

         image.png

  •  
    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。代表查询时大量内部临时表使用了磁盘临时表

    image.png
  • 缘由分析
    不一样客户端并发访问Mysql进行查询时,产生的内存临时表会暂时占据已分配的内存空间,后面查询产生的临时表由于没有足够的内存空间而改用磁盘临时表空间
    而磁盘空间不会由于客户端断开而释放空间,最终致使磁盘空间被消耗殆尽。


2.6 问题解决

  • 致使产生本次问题的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
相关文章
相关标签/搜索