惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
I
InfoQ
F
Fortinet All Blogs
J
Java Code Geeks
Last Week in AI
Last Week in AI
美团技术团队
大猫的无限游戏
大猫的无限游戏
有赞技术团队
有赞技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件

博客园 - 幽州散人

SQL优化案例 synchronized 为啥会让虚拟线程卸载失效 主线任务和使命 温州3日之旅 CompletableFuture + 异步Servlet:同步的姿势、异步的效果 Snowflake雪花算法与发号器的应用 windows Java 幽灵转发壳进程 也谈MySQL limit offset深翻页问题 MySQL 5.7 Profiles 执行性能分析 MySQL临时表与文件排序 MySQL 5.7 MRR多范围读优化 MySQL 5.7 ICP索引条件下推优化 MySQL符合索引与最左前缀原则 从I/O 的物理成本理解回表和索引覆盖 第4个电瓶以及补漆 Flink原理:并行度与keyGroup桶 什么是数字批发银行 Java虚拟线程(三)实现原理 Java虚拟线程(二) Java虚拟线程(一) 大模型推理层服务化架构 传奇调查员之路:理智值-99,但我必须听懂深渊的语言 js里调用智能合约读/写函数的方法的区别 字节与其16进制字符表示转换的bug Redisson分布式锁 交易心得 DexScreener接口初探 某安全软件跑飞了。。 Ed25519算法签名与验签的Java实现 Tendermint拜占庭容错引擎
SHOW SESSION STATUS 与 Handler 变量
幽州散人 · 2026-08-14 · via 博客园 - 幽州散人

会话级内存计数变量SESSION STATUS 中,'Handler%'开头的这一组变量、反映了存储引擎取数的一些细节,可以用来分析SQL性能。

FLUSH STATUS;
select count(*) from order_items oi ;
SHOW SESSION STATUS LIKE 'Handler%';

查询结果:

Variable_name             |Value  |
--------------------------|-------|
Handler_commit            |1      |
Handler_delete            |0      |
Handler_discover          |0      |
Handler_external_lock     |2      |
Handler_mrr_init          |0      |
Handler_prepare           |0      |
Handler_read_first        |1      |
Handler_read_key          |1      |
Handler_read_last         |0      |
Handler_read_next         |3000000|
Handler_read_prev         |0      |
Handler_read_rnd          |0      |
Handler_read_rnd_next     |0      |
Handler_rollback          |0      |
Handler_savepoint         |0      |
Handler_savepoint_rollback|0      |
Handler_update            |0      |
Handler_write             |0      |
  1. FLUSH STATUS 清的是什么

MySQL 维护着一批会话级状态计数器(就是一堆内存变量),记录当前这个连接执行过多少查询、扫过多少行、发生过多少次排序等等。FLUSH STATUS 做了两件事:

① 把当前会话的计数"加总"到全局计数器(累加,不是清空全局)
② 把当前会话的计数器全部清零

所以它不写任何系统表、不落盘,纯内存操作。目的就是:清掉历史干扰,让接下来的计数只反映你接下来那条 SQL 的行为。 这也是为什么这个套路是三连:FLUSH → 执行 SQL → SHOW。

  1. SHOW SESSION STATUS 是啥意思

读当前会话的计数器。SESSION 是作用域,另一个选项是 GLOBAL(全实例累计):

SHOW SESSION STATUS LIKE 'Handler%'; -- 当前这个连接的计数
SHOW GLOBAL STATUS LIKE 'Handler%'; -- 整个 MySQL 实例的累计计数

LIKE 'Handler%' 是过滤——Handler 前缀的变量记录的是存储引擎"怎么取行"的底层动作。

  1. 这组数据是教科书级别的

Handler_read_key = 1 ← 1 次 B+Tree 下潜:定位到 idx_created_at 第一个叶子
Handler_read_first = 1 ← 读了第一个索引条目
Handler_read_next = 3,000,000 ← 沿叶子链表读了 300 万次"下一个"
Handler_read_rnd_next = 0 ← 没有全表扫描

完美还原了刚才那 2 秒在干什么:从索引最左叶子出发,沿双向链表一路走到最右,数了 300 万条。注意 read_next 精确是 3,000,000 而 EXPLAIN 估的 rows 是 2,989,004——这就是"Handler 变量是真实值、EXPLAIN rows 是估算值"的直观对照。

判读口诀(以后自己排查用)

Handler_read_next 大 → 索引范围扫描/全索引扫描(B+Tree 顺着链表走)
Handler_read_rnd_next 大 → 全表扫描或回表无序读(按页随机跳)
Handler_read_key 大 → 点查/等值匹配多

执行EXPLAIN,

id|select_type|table|...|type |possible_keys|key           |key_len|...
 1|SIMPLE     |oi   |...|index|             |idx_created_at|5      |...
                                                  ↑
                                          实际使用的索引

用了idx_created_at,所以这个count(*)是做了一次全索引扫描:
① 全表扫描 (Full Table Scan)
扫的是聚集索引 —— 表数据本身(所有列都在里面)
EXPLAIN: type=ALL
Handler 计数器: Handler_read_rnd_next 逐行累加

② 全索引扫描 (Full Index Scan)
扫的是某个二级索引 —— 只有索引列+主键
EXPLAIN: type=index
Handler 计数器: Handler_read_first 定位 + Handler_read_next 沿链表走

COUNT() 是*②全索引扫描:扫的是 idx_created_at 这棵二级索引树(77MB),不是表数据(几百 MB 含所有列)。所以 read_next = 3,000,000 而 read_rnd_next = 0。

计数器名字背后的含义

read_rnd_next = "随机地读下一行" → 按聚集索引页的物理顺序一行行读
(叫 rnd 是因为从执行器视角看,它没说"按哪个键序",就是表里的下一行)

read_next = "按索引键序读下一个" → 顺着某棵 B+Tree 的叶子链表走

这个区分为什么重要

两种都是 O(N),但成本差不少:

全索引扫描:只读 77MB 的二级索引,每页能装更多条目
全表扫描: 要读几百 MB 的完整表数据(TEXT 列、所有字段)

这正是 COUNT(*) 挑最小二级索引的原因