“sending data”状态耗时长主因是mysql回表读取非索引列(如varchar(8000) description),而非网络发送慢;该状态实际指“收集+发送数据”,其中“收集”(即根据row_id回聚簇索引逐行读取)占绝大部分时间,可通过show profile验证并删除大字段或建覆盖索引优化。

为什么SHOW PROCESSLIST里卡在Sending data不等于网络慢
这个状态名有严重误导性。MySQL官方文档明确说明:Sending data 实际包含“从存储引擎收集数据”和“发送给客户端”两个阶段,而真正耗时的几乎总是前者——尤其是回表、读大字段、或被行锁阻塞时。
常见错误现象包括:EXPLAIN显示走了索引,但查询越跑越慢;LIMIT 1000,20 分页越来越卡;加了SELECT *后响应时间陡增数倍。
- 不是网络带宽打满,也不是TCP发包慢,
Sending data阶段极少受网络影响(除非返回GB级结果) - 真实瓶颈常在磁盘I/O:比如根据索引拿到1000个
row_id后,还要逐行回到聚簇索引里读VARCHAR(8000)字段,每行可能触发多次页读取 - 若被其他事务锁住某行(如
UPDATE未提交),MySQL会卡在“准备读该行”这一步,SHOW PROCESSLIST状态仍是Sending data,但实际是等锁
用SHOW PROFILE精准定位Sending data是否真占大头
别只看EXPLAIN——它不反映执行时长分布。SHOW PROFILE 才是验证“是不是Sending data惹的祸”的最快方式。
操作步骤:
- 执行
SET profiling = ON; - 运行你的慢SQL
- 执行
SHOW PROFILES;拿到最新Query_ID - 执行
SHOW PROFILE FOR QUERY N;(N为ID),重点关注Sending data和Creating sort index两行的耗时占比
如果Sending data 占比超85%,且SQL中包含未覆盖的TEXT、VARCHAR大字段或JOIN后GROUP BY,基本可锁定问题来源。
怎么快速验证是不是回表或大字段导致的
最直接的办法是做最小化对比:删掉疑似拖慢的字段,重跑SQL,看耗时变化。
典型场景示例:
- 原SQL:
SELECT id, title, description, create_time FROM t WHERE status = 1 ORDER BY create_time DESC LIMIT 20,耗时216秒 - 改写后:
SELECT id, title, create_time FROM t WHERE status = 1 ORDER BY create_time DESC LIMIT 20,耗时15秒 - 结论:
description(VARCHAR(8000))是主因,每次回表都要加载大量数据页
注意:即使只返回20行,只要description平均填充率达70%,单行就可能跨多个16KB页,叠加后I/O压力剧增。
哪些配置或环境因素会让Sending data雪上加霜
innodb_buffer_pool_size 过小是高频隐形杀手——它决定能缓存多少索引页和数据页。若设置仅8MB(默认值之一),而表数据远超此值,就会频繁刷盘读页。
其他易被忽略的点:
-
innodb_buffer_pool_size应设为物理内存的70%~80%,比如16GB内存服务器建议设为12GB;改完需重启MySQL才生效 - 长事务未提交,导致undo日志链过长,MVCC快照读变慢,间接拉长
Sending data阶段 - 磁盘IO负载高(如备份、大批量DELETE正在执行),正常查询被迫排队等I/O,
SHOW PROCESSLIST看似卡在Sending data,实则是底层磁盘忙不过来 -
SELECT *+LIMIT offset, size组合:MySQL仍要扫描并回表前offset行,每行都触发一次聚簇索引查找
真正卡住的地方,往往不在SQL写法本身,而在buffer pool是否够大、锁是否被持有、磁盘是否正被其它任务压垮——这些细节不查SHOW PROFILE和系统监控很难发现。











