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

“Sending data”状态持续过久,基本不是网络发送慢,而是MySQL正在从磁盘或缓冲区里反复读取数据行——尤其是当查询需要回表、读大字段、或被锁住时。
为什么“Sending data”不等于“正在发数据”
这个状态名有严重误导性。MySQL官方文档明确说明:Sending data 实际涵盖两个动作:从存储引擎“收集数据” + “发送给客户端”。真正卡住的,几乎总是前者。
- 索引能快速定位到符合条件的
row_id,但如果SELECT里的字段不在索引中(比如description、content等大文本列),MySQL必须根据这些row_id再回到聚簇索引(主键B+树)里逐行捞数据——这就是“回表”,也是Sending data耗时的主因 - 即使只返回200行,若其中某列是
VARCHAR(8000)且平均填充率达70%,单行就可能触发多次页读取,叠加后I/O压力陡增 - 如果被其他事务锁住(如
UPDATE未提交),MySQL会卡在“准备读某行”这一步,SHOW PROCESSLIST看到的状态仍是Sending data,但实际是等待锁
怎么快速确认是不是回表或大字段导致的
用SHOW PROFILE看时间分布,比EXPLAIN更直接:
- 执行
SET profiling = ON;,再跑慢查询 - 查
SHOW PROFILES;拿到Query_ID - 执行
SHOW PROFILE FOR QUERY N;(N为ID),重点看Sending data和Creating sort index两行耗时占比 - 如果
Sending data占90%以上,且SELECT包含非索引列(尤其TEXT/VARCHAR大字段),基本可锁定问题 - 验证方式:把疑似大字段从
SELECT里删掉,重跑,对比耗时变化——曾有案例从216秒降到15秒
哪些操作会让“Sending data”雪上加霜
以下写法看似合理,实则极易放大Sending data开销:
- 用
SELECT *,哪怕只加一个未建索引的updated_at字段,也可能迫使全表回表 -
LIMIT 1000, 20这类偏移分页:MySQL仍要扫描前1000行并丢弃,每行都触发回表逻辑 - JOIN后
GROUP BY或ORDER BY字段未被覆盖索引包含:排序前需先捞出所有匹配行,数据量越大,“收集”阶段越长 - 事务中执行长查询,同时有其他连接在更新同一张表的高频字段(如
status),容易因行锁竞争卡在Sending data
真正有效的优化点往往很具体
别只盯着“加索引”这种宽泛建议。真实见效的操作包括:
- 把常查的大字段(如
description)挪到单独的_ext扩展表,主表只留ID,按需LEFT JOIN - 用游标分页替代
LIMIT offset, size:比如WHERE id > 12345 ORDER BY id LIMIT 20 - 对
GROUP BY字段组合建覆盖索引,例如INDEX(status, create_time, user_id),让聚合能在索引内完成,避免回表 - 检查
innodb_buffer_pool_size是否足够——若该值远小于数据总大小,每次回表都变成磁盘随机读,Sending data必然暴涨
最易被忽略的一点:Sending data时间长,未必代表SQL要重写;有时只是SELECT里多了一个不该出现的字段,或者事务隔离级别太高导致隐式锁等待。先用SHOW PROFILE和information_schema.INNODB_TRX交叉验证,比盲目调优更省时间。











