本文为墨天轮数据库管理服务团队第43期技术分享,内容原创,如需转载请联系小墨(VX:modb666)并注明来源。

概念描述

到了mysql8.0版本,SQL运行过程中产生的内部临时表已经有了变化,存放位置由innodb\_temp\_tablespaces\_dir参数控制,默认放在 $DATADIR/#innodb\_temp/目录下,由多个.ibt文件构成。并且当数据库连接断开时,.ibt文件的大小会被重置(5个页80K),以达到回收磁盘空间的目的。
但是,实际业务场景中,数据库连接均为长连接,当我们发现了*.ibt文件过大时,该如何定位到问题SQL呢?这里分2种情况:

  • 第一种情况:发现*.ibt文件过大时,此文件大小正在持续增长。此类情况,可以直接查询innodb\_session\_temp_tablespaces表定位到相关SQL。
  • 第二种情况:发现.ibt文件过大时,此文件大小已经不再增长。此类情况,需要结合.ibt文件的创建日期、最后修改日期、长连接的ID、slowlog等来定位到SQL。

测试验证

创建测试数据:

-- 测试数据
use modb;
drop table if exists temp_table;
CREATE TABLE temp_table (
  id int NOT NULL AUTO_INCREMENT,
  create_time datetime DEFAULT NULL,
  code char(10) NOT NULL DEFAULT '0',
  PRIMARY KEY (id)
) ENGINE=InnoDB;

insert into temp_table(create_time, code)
select '2025-01-09', 'c1234'
from information_schema.columns a, information_schema.columns b
limit 400000;

mysql> analyze table temp_table;
+-----------------+---------+----------+----------+
| Table           | Op      | Msg_type | Msg_text |
+-----------------+---------+----------+----------+
| modb.temp_table | analyze | status   | OK       |
+-----------------+---------+----------+----------+
1 row in set (0.19 sec)

mysql> show table status like 'temp_table';
+------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+--------------------+----------+----------------+---------+
| Name       | Engine | Version | Row_format | Rows   | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time         | Check_time | Collation          | Checksum | Create_options | Comment |
+------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+--------------------+----------+----------------+---------+
| temp_table | InnoDB |      10 | Dynamic    | 398958 |             43 |    17317888 |               0 |            0 |   4194304 |         458746 | 2025-01-09 10:59:10 | 2025-01-09 11:12:25 | NULL       | utf8mb4_0900_ai_ci |     NULL |                |         |
+------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+--------------------+----------+----------------+---------+
1 row in set (0.09 sec)

第一种情况,如果是当前运行的SQL导致的*.ibt文件过大,可以通过查询innodb\_session\_temp_tablespaces和processlist表直接定位。

-- 从SQL执行计划可以看出,会用到内部临时表(Using temporary)
mysql> explain select min(create_time) min_time from 
    -> (
    -> select * from temp_table a union all
    -> select * from temp_table a 
    -> ) t
    -> group by create_time;
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-----------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra           |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-----------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 797916 |   100.00 | Using temporary |
|  2 | DERIVED     | a          | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 398958 |   100.00 | NULL            |
|  3 | UNION       | a          | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 398958 |   100.00 | NULL            |
+----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+-----------------+
3 rows in set, 1 warning (0.02 sec)

-- 实际执行SQL,查看临时表空间使用情况:
-- session1: SQL执行中...
mysql> select min(create_time) min_time from 
    -> (
    -> select * from temp_table a union all
    -> select * from temp_table a 
    -> ) t
    -> group by create_time;

-- session2: 查看临时表使用情况(temp_10.ibt被使用,大小已经涨至近18MB,SQL线程ID=7)
mysql> select * from information_schema.innodb_session_temp_tablespaces;
+----+------------+----------------------------+----------+----------+-----------+
| ID | SPACE      | PATH                       | SIZE     | STATE    | PURPOSE   |
+----+------------+----------------------------+----------+----------+-----------+
|  7 | 4243767290 | ./#innodb_temp/temp_10.ibt | 18874368 | ACTIVE   | INTRINSIC |
|  8 | 4243767289 | ./#innodb_temp/temp_9.ibt  |    81920 | ACTIVE   | INTRINSIC |
|  0 | 4243767281 | ./#innodb_temp/temp_1.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767282 | ./#innodb_temp/temp_2.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767283 | ./#innodb_temp/temp_3.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767284 | ./#innodb_temp/temp_4.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767285 | ./#innodb_temp/temp_5.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767286 | ./#innodb_temp/temp_6.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767287 | ./#innodb_temp/temp_7.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767288 | ./#innodb_temp/temp_8.ibt  |    81920 | INACTIVE | NONE      |
+----+------------+----------------------------+----------+----------+-----------+
10 rows in set (0.00 sec)

-- session2: 根据SQL线程ID列获取SQL文本
mysql> select * from information_schema.processlist where id = 7\G
*************************** 1. row ***************************
     ID: 7
   USER: root
   HOST: 192.168.199.132:45830
     DB: modb
COMMAND: Query
   TIME: 12
  STATE: converting HEAP to ondisk
   INFO: select min(create_time) min_time from 
(
select * from temp_table a union all
select * from temp_table a 
) t
group by create_time
1 row in set (0.02 sec)

-- session3: 查看\*.ibt文件大小(从操作系统层看,temp_10.ibt文件大小也在不断增长)
[root@vm-mysql-01 #innodb_temp]# ls -lht --full-time
total 23M
-rw-r-----. 1 mysql mysql 22M 2025-01-09 15:36:59.313599607 +0800 temp_10.ibt
-rw-r-----. 1 mysql mysql 96K 2025-01-09 15:36:57.534599633 +0800 temp_9.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.376602094 +0800 temp_8.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.374602094 +0800 temp_6.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.374602094 +0800 temp_7.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.372602094 +0800 temp_5.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.371602094 +0800 temp_4.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.370602094 +0800 temp_3.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.369602094 +0800 temp_2.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.368602094 +0800 temp_1.ibt

如上所示,如果某个*.ibt文件正在持续增长,可以根据innodb\_session\_temp_tablespaces表直接定位到相关SQL,此表字段含义如下:

  • ID:SQL线程ID
  • SPACE:表空间ID。会话临时表保留了40万个表空间id
  • PATH:表空间数据文件路径
  • SIZE:表空间大小(单位:字节)
  • STATE:表空间的状态:

    • ACTIVE-表空间当前正在被会话使用
    • INACTIVE-表空间在临时表空间池中但未被使用
  • PURPOSE:表空间的用途:

    • INTRINSIC-表空间用于优化器使用的优化的内部临时表
    • SLAVE-分配该表空间用于存储复制从节点上用户创建的临时表
    • USER-该表空间用于用户创建的临时表
    • NONE-该表空间未被使用

第二种情况,如果发现.ibt文件过大时,此文件大小已不再变化,且由于占用它的连接是长连接,那么.ibt文件的大小就不会被重置为80K,这时我们就得结合*.ibt文件的创建日期、最后修改日期、长连接的ID、slowlog等来定位SQL了。以上述例子中temp_10.ibt为例:

-- 由于线程ID=7的长连接并未断开,并且SQL已经执行完,故查看temp_10.ibt文件的大小始终为64MB
[root@vm-mysql-01 #innodb_temp]# ls -lht --full-time
total 65M
-rw-r-----. 1 mysql mysql 64M 2025-01-09 15:37:39.524599032 +0800 temp_10.ibt
-rw-r-----. 1 mysql mysql 96K 2025-01-09 15:36:57.534599633 +0800 temp_9.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.376602094 +0800 temp_8.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.374602094 +0800 temp_6.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.374602094 +0800 temp_7.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.372602094 +0800 temp_5.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.371602094 +0800 temp_4.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.370602094 +0800 temp_3.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.369602094 +0800 temp_2.ibt
-rw-r-----. 1 mysql mysql 80K 2025-01-09 15:34:05.368602094 +0800 temp_1.ibt

-- 查看innodb_session_temp_tablespaces表,可以看出线程ID=7占用了temp_10.ibt空间
mysql> select * from information_schema.innodb_session_temp_tablespaces;
+----+------------+----------------------------+----------+----------+-----------+
| ID | SPACE      | PATH                       | SIZE     | STATE    | PURPOSE   |
+----+------------+----------------------------+----------+----------+-----------+
|  7 | 4243767290 | ./#innodb_temp/temp_10.ibt | 67108864 | ACTIVE   | INTRINSIC |
|  8 | 4243767289 | ./#innodb_temp/temp_9.ibt  |    98304 | ACTIVE   | INTRINSIC |
|  0 | 4243767281 | ./#innodb_temp/temp_1.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767282 | ./#innodb_temp/temp_2.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767283 | ./#innodb_temp/temp_3.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767284 | ./#innodb_temp/temp_4.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767285 | ./#innodb_temp/temp_5.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767286 | ./#innodb_temp/temp_6.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767287 | ./#innodb_temp/temp_7.ibt  |    81920 | INACTIVE | NONE      |
|  0 | 4243767288 | ./#innodb_temp/temp_8.ibt  |    81920 | INACTIVE | NONE      |
+----+------------+----------------------------+----------+----------+-----------+
10 rows in set (0.01 sec)

-- 尝试获取temp_10.ibt的创建时间
#获取temp_10.ibt的文件描述符(FD: 25uW) 及进程ID(48080)
[root@vm-mysql-01 #innodb_temp]# lsof temp_10.ibt
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
mysqld  48080 mysql   25uW  REG  253,0 67108864 40934343 temp_10.ibt

#获取temp_10.ibt的文件描述符的创建时间
[root@vm-mysql-01 #innodb_temp]# ls -lht --full-time /proc/48080/fd/25
lrwx------. 1 root root 64 2025-01-09 15:34:19.829601887 +0800 /proc/48080/fd/25 -> /data/mysql/mysql_3307/#innodb_temp/temp_10.ibt

至此,我们知道了:

  • SQL线程ID=7
  • temp_10.ibt创建时间为:2025-01-09 15:34:19
  • temp_10.ibt最后修改时间为:2025-01-09 15:37:39

最后再从slowlog中查找相关SQL:

# Time: 2025-01-09T15:37:39.116179+08:00
# User@Host: root[root] @  [192.168.199.132]  Id:     7
# Query_time: 53.946563  Lock_time: 0.000520 Rows_sent: 1  Rows_examined: 800000 Thread_id: 7 Errno: 0 Killed: 0 Bytes_received: 137 Bytes_sent: 74 Read_first: 3 Read_last: 0 Read_key: 800021 Read_next: 13 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 2123782 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 1 Created_tmp_tables: 2 Start: 2025-01-09T15:36:45.169616+08:00 End: 2025-01-09T15:37:39.116179+08:00
use modb;
SET timestamp=1736408205;
select min(create_time) min_time from 
(
select * from temp_table a union all
select * from temp_table a 
) t
group by create_time;

在slowlog中查找这样的SQL:

  • 线程Id=7
  • 时间在 2025-01-09 15:34:19~2025-01-09 15:37:39 之间
  • Created\_tmp\_disk_tables > 0
  • 执行时间较长
  • 可能会产生临时表的SQL

但这里要注意一个问题,就是temp_10.ibt的创建时间,它可能会不准确,原因如下:

  1. temp\_1.ibt ~ temp\_10.ibt的的文件描述符创建日期,是在mysqld进程启动后就创建的,即使后来由于连接断开,*.ibt文件被重置成80K,这10个文件描述符的创建日期也不会变化,除非重启mysql。
  2. 如果并发使用临时空间的线程超过10个了,就会自动生成temp\_11.ibt、temp\_12.ibt…,这些文件描述符创建日期,相当于*.ibt文件的创建日期,有一定的参考价值。
  3. 如果操作系统支持,可以从系统层面获取*.ibt文件的创建日期,也有一定的参考价值。

另外要注意的问题,就是*.ibt文件的重建,具体以下特点:

  1. MySQL重启时删除并重建,包括文件描述符
  2. 数据库连接断开时,文件重置大小,但文件描述符不变
  3. 数据库连接不断开时,文件大小始终保留使用后的最大值

知识总结

本文总结了当mysql临时表空间文件变大时的定位方法。分两种情况:

  1. 临时文件(*.ibt)正在变大时,容易定位原因。此时使用innodb\_session\_temp_tablespaces表即可定位。
  2. 临时文件(.ibt)不再变大时,分析历史原因。此时可分析.ibt文件的创建日期、最后编辑日期、相关SQL线程ID去slowlog中定位相关SQL。

参考文档:

https://mp.weixin.qq.com/s/ctSAnSjQqC444WqJaeANNg
https://dev.mysql.com/doc/refman/8.0/en/information-schema-in...
https://dev.mysql.com/doc/refman/8.0/en/innodb-temporary-tabl...
https://mysql.net.cn/doc/refman/8.0/en/innodb-temporary-table...


📣活动预告:3月13日20点,《数据库服务团队技术分享第十期直播》邀请到墨天轮服务团队总监杨明翰,探入探讨MySQL中需谨慎执行的命令,欢迎来【墨天轮】视频号看直播~
7c07b25e09201e2c8ada535517c5614.png

图片
墨天轮从乐知乐享的数据库技术社区蓄势出发,全面升级,提供多类型数据库管理服务。墨天轮数据库管理服务旨在为用户构建信赖可托付的数据库环境,并为数据库厂商提供中立的生态支持。
服务官网:https://www.modb.pro/service
如需获取更多服务团队干货还可关注【墨天轮】公众号哟~


墨天轮
30 声望18 粉丝