MySQL 8.4 LTS:会话与线程的 1:1 关系,以及如何与操作系统线程绑定排查
MySQL 8.4 LTS:会话与线程的 1:1 关系,以及如何与操作系统线程绑定排查
MySQL 8.4 LTS:会话与线程的 1:1 关系,以及如何与操作系统线程绑定排查
本文基于 MySQL 8.4.10 LTS 实例实测验证,所有数据均来自真实环境查询结果,可直接在 8.4 上复现。
前言
在 MySQL 的日常运维与性能排查中,有三个问题几乎必然遇到:
- 数据库里的"会话"和"线程"到底是什么关系?
- 每次建立一个连接,服务器会不会新建一个线程?
- 用
ps -T -p <mysqld_pid>看到的操作系统线程,怎么和数据库里的会话对上号?
本文用一张 performance_schema.threads 表,把「会话 → MySQL 线程 → OS 线程」三层关系一次讲透,并给出可直接复制执行的排查 SQL。
一、核心结论:默认 1 个会话 = 1 个前台线程
MySQL 8.4(社区版)默认的线程处理方式是 thread-per-connection,由系统变量确认:
SELECT VERSION(), @@thread_handling, @@thread_cache_size, @@max_connections;
-- 实测结果:
mysql> SELECT VERSION(), @@thread_handling, @@thread_cache_size, @@max_connections;
+-----------+---------------------------+---------------------+-------------------+
| VERSION() | @@thread_handling | @@thread_cache_size | @@max_connections |
+-----------+---------------------------+---------------------+-------------------+
| 8.4.10 | one-thread-per-connection | 9 | 151 |
+-----------+---------------------------+---------------------+-------------------+
1 row in set (0.00 sec)
thread_handling = one-thread-per-connection 意味着:
- 建立连接时:客户端完成 TCP/握手 → 认证通过 → 服务器为该连接创建一个专用前台线程(
TYPE='FOREGROUND',也叫会话线程/连接线程)。 - 连接存活期间:该连接上的所有 SQL(解析、优化、执行、返回)都由这一个线程全程独占执行,不会中途换线程。
- 连接断开时:线程不一定销毁——优先进入线程缓存(thread cache) 等待下一个连接复用(见第四节)。
例外:仅 MySQL Enterprise 版 提供 Thread Pool 插件,启用后多会话共享少量工作线程,不再是 1:1。社区版 8.4 没有线程池,永远是 1:1。
实时数据对账:数量严格一一对应
用性能数据交叉验证(同一时刻抓取):
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME LIKE 'Threads%'
ORDER BY VARIABLE_NAME;
| 指标 | 实测值 | 含义 |
|---|---|---|
Threads_connected |
11 | 当前客户端连接数 |
Threads_running |
2 | 正在执行语句的线程数 |
Threads_created |
12 | 自启动以来累计创建的客户端线程数 |
Threads_cached |
1 | 缓存中的空闲线程数 |
在 performance_schema.threads 里数前台客户端连接:11 个(10 个 Sleep + 1 个 Query),与 Threads_connected = 11 逐一对上。
而 Threads_created = 12 = 11(在用)+ 1(缓存) 更是直接证明了:新会话 ≠ 必新建线程,优先复用缓存线程。
二、三层 ID 体系:一张表看懂
一个会话在 MySQL 内部涉及 三个不同的 ID,全在 performance_schema.threads 表里,务必区分:
| 列 | 叫什么 | 代表什么 | 举例(实测) |
|---|---|---|---|
THREAD_ID |
MySQL 内部线程号 | 服务器内部分配的线程标识 | 83 |
PROCESSLIST_ID |
会话/连接 ID | 等于 CONNECTION_ID(),与 information_schema.processlist.ID 一致 |
49 |
THREAD_OS_ID |
操作系统线程 ID | Linux 内核线程号(gettid / LWP) | 4080 |
三者关系:一个会话在存活期间,THREAD_ID 与 PROCESSLIST_ID 一一对应;THREAD_OS_ID 则把 MySQL 线程映射到操作系统线程——这正是第三节"绑定查看"的钥匙。
自证实验:当前会话的三层 ID
SELECT CONNECTION_ID() AS my_connection_id,
t.THREAD_ID, t.PROCESSLIST_ID, t.TYPE,
t.THREAD_OS_ID, t.CONNECTION_TYPE, t.PROCESSLIST_USER
FROM performance_schema.threads t
WHERE t.PROCESSLIST_ID = CONNECTION_ID();
mysql> SELECT CONNECTION_ID() AS my_connection_id,
-> t.THREAD_ID, t.PROCESSLIST_ID, t.TYPE,
-> t.THREAD_OS_ID, t.CONNECTION_TYPE, t.PROCESSLIST_USER
-> FROM performance_schema.threads t
-> WHERE t.PROCESSLIST_ID = CONNECTION_ID();
+------------------+-----------+----------------+------------+--------------+-----------------+------------------+
| my_connection_id | THREAD_ID | PROCESSLIST_ID | TYPE | THREAD_OS_ID | CONNECTION_TYPE | PROCESSLIST_USER |
+------------------+-----------+----------------+------------+--------------+-----------------+------------------+
| 35 | 83 | 35 | FOREGROUND | 4080 | TCP/IP | admin |
+------------------+-----------+----------------+------------+--------------+-----------------+------------------+
1 row in set (0.00 sec)
→ 我发起查询的这个会话,CONNECTION_ID() 正好等于 PROCESSLIST_ID,对应唯一 THREAD_ID=83,并在 OS 层对应线程 4080。
三、操作系统线程绑定查看(重点)
3.1 8.4 的重要变化:列名改了!
MySQL 8.4 把 8.0 的 OS_THREAD_ID 列重命名为 THREAD_OS_ID。
用 8.0 的旧文档写 WHERE OS_THREAD_ID = ... 会直接报错:
ERROR: Unknown column 'OS_THREAD_ID' in 'field list'
在 8.4.10 上核对列清单:
SELECT COLUMN_NAME FROM information_schema.columns
WHERE TABLE_SCHEMA='performance_schema' AND TABLE_NAME='threads';
-- ... THREAD_OS_ID bigint unsigned ...
(8.4 的 threads 表还新增了 CONTROLLED_MEMORY、TOTAL_MEMORY、TELEMETRY_ACTIVE 等内存治理相关列,感兴趣可以自己 DESC performance_schema.threads 查看。)
3.2 绑定原理
MySQL 在 Linux 上把每个内部线程的内核线程 ID(gettid() 返回的 LWP 号)写入 THREAD_OS_ID,而 ps -T -p <PID> 输出的 SPID/LWP 列恰好就是同一个值:
ps -T 的 SPID 列 <══ 一一对应 ══> performance_schema.threads.THREAD_OS_ID
3.3 完整操作步骤(在 MySQL 服务器上执行)
第 1 步:拿到 mysqld 进程 PID
pgrep -x mysqld # 假设得到 1234
第 2 步:OS 侧列出所有线程(注意看 SPID 列)
ps -T -p 1234 -o pid,tid,pcpu,comm
# 按 CPU 排序找热点线程:
ps -eLo pid,tid,pcpu,comm --sort=-pcpu | grep mysqld
# 或实时观察:
top -H -p 1234
第 3 步:DB 侧输出绑定清单
SELECT PROCESSLIST_ID, THREAD_ID, THREAD_OS_ID, TYPE,
PROCESSLIST_USER, PROCESSLIST_DB, PROCESSLIST_COMMAND,
PROCESSLIST_STATE, PROCESSLIST_INFO
FROM performance_schema.threads
WHERE TYPE = 'FOREGROUND' AND PROCESSLIST_ID IS NOT NULL
ORDER BY THREAD_OS_ID;
第 4 步:对照:把第 2 步 ps -T 的 SPID 与第 3 步的 THREAD_OS_ID 逐行对上,就得到完整链路:
OS 线程(SPID=4080) → MySQL线程(THREAD_ID=83) → 会话(CONNECTION_ID=49) → 用户admin,正在执行的SQL
3.4 实测对照样本(本实例真实数据)
| THREAD_ID | PROCESSLIST_ID | THREAD_OS_ID | TYPE | 用户/命令 |
|---|---|---|---|---|
| 1 | NULL | 1023 | BACKGROUND | main 主线程 |
| 38 | 5 | 1287 | FOREGROUND | event_scheduler 调度线程 |
| 60 | 26 | 3493 | FOREGROUND | admin / Sleep |
| 83 | 49 | 4080 | FOREGROUND | admin / Query |
| 4–18 | NULL | 1163–1184 | BACKGROUND | InnoDB io/log 系列线程 |
小彩蛋:示例命令
ps -T -p 1023里的1023是进程 PID,而本机恰好主线程THREAD_OS_ID也是 1023——两者数值相同纯属巧合,一个是进程号、一个是线程号,不要混为一谈。
3.5 反向排查(最常用场景)
top -H -p 1234 发现某个 TID(比如 4080)CPU 飙高,立刻反查它到底是哪个会话:
SELECT PROCESSLIST_ID, THREAD_ID, PROCESSLIST_USER, PROCESSLIST_DB,
PROCESSLIST_COMMAND, PROCESSLIST_STATE, PROCESSLIST_INFO
FROM performance_schema.threads
WHERE THREAD_OS_ID = 4080;
同理可排查 InnoDB 后台线程的 IO 热点(THREAD_OS_ID 对应 NAME,如 thread/innodb/io_read_thread)、复制线程、主线程等问题。
四、线程的生命周期与缓存复用
- 连接断开 → 线程不销毁:线程进入 thread cache(受
thread_cache_size控制,8.4 默认 -1 表示自动调节,上限约为max_connections/5)。 - 新连接 → 优先取缓存线程:因此
Threads_created往往远小于历史会话总数。本实例thread_cache_size=9,实测缓存中有 1 个线程待命。 - 复用带来的绑定陷阱:被复用的 OS 线程,其
THREAD_OS_ID不变,但PROCESSLIST_ID(会话号)是新分配的。所以做绑定排查时,务必用同一时刻的快照对照,不要拿旧结果对。
五、注意事项汇总
- 列名:8.4 用
THREAD_OS_ID,8.0 是OS_THREAD_ID,别照旧文档抄。 - 平台:
THREAD_OS_ID只在 Linux 上填充,Windows/macOS 为 NULL。 - 权限:查看其他用户的线程信息需要
PROCESS权限(同SHOW PROCESSLIST权限),否则只能看到自己。 - 线程池例外:仅 Enterprise 版 Thread Pool 会打破 1:1;社区版
thread_handling恒为one-thread-per-connection。 - 特殊的 FOREGROUND:
event_scheduler与部分 daemon 在表中也是FOREGROUND,但它们不是客户端连接,不计入Threads_connected,筛选时注意用PROCESSLIST_ID IS NOT NULL并排除Daemon命令。 - 线程缓存:连接断开后线程进缓存复用,
THREAD_OS_ID不变而会话 ID 会变,绑定要看同时刻快照。 - OS 层线程名:前台会话线程在 OS 层都叫
mysqld(进程名),无法靠ps的线程名区分是哪个会话——必须靠THREAD_OS_ID反查,这正是本文方法的用武之地。
六、总结
- 会话与线程:MySQL 8.4 社区版中,每个会话从建立到断开,始终对应且独占一个前台线程(1:1);断开后线程进入缓存复用,避免重复创建的开销。
- 三层 ID:
THREAD_ID(内部线程号)≠PROCESSLIST_ID/CONNECTION_ID()(会话号)≠THREAD_OS_ID(OS 内核线程号),三者同列于performance_schema.threads。 - 绑定排查:
ps -T -p <PID>的 SPID 与THREAD_OS_ID一一对应,正向输出清单、反向定位热点会话,一条 SQL 即可完成从「操作系统线程」到「用户与正在执行的 SQL」的全链路定位。
参考
- MySQL 8.4 Reference Manual: The performance_schema.threads Table
- MySQL 8.4 Reference Manual: Status Variables(Threads_connected / Threads_created / Threads_cached / Threads_running)
- MySQL 8.4 Reference Manual: Server System Variables(thread_handling / thread_cache_size / max_connections)
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)