根本原因在于InnoDB存储机制:TEXT/BLOB超768字节时拆出溢出页,主记录仅存20字节指针,导致INSERT需写主页+溢出页、redo log膨胀、buffer pool污染及额外锁争用。

直接写 INSERT INTO table (content) VALUES ('...') 往 TEXT/BLOB 字段插长文本,性能差不是语句写错了,而是 MySQL 在底层被迫做大量额外工作:溢出页管理、隐式转换、日志膨胀、buffer pool 污染——这些全在你没注意时悄悄发生。
为什么 INSERT TEXT/BLOB 会变慢
根本原因不在“插入”动作本身,而在 InnoDB 的存储机制:
- InnoDB 对超过 768 字节的 TEXT/BLOB 值不存主记录页,而是拆出独立溢出页,主记录只留 20 字节指针;
INSERT实际要写主页 + 一个或多个溢出页,IO 次数翻倍 - 事务日志(redo log)必须完整记录溢出页内容,导致
log_file_size快速打满、频繁刷盘 - 如果表有二级索引,且插入时触发唯一约束检查或自增 ID 计算,InnoDB 还要额外加锁并扫描 buffer pool 中相关页,加剧争用
-
max_allowed_packet默认 4MB,但客户端/服务端任一端设小了,长文本就会被截断或报错Packet too large,而错误提示完全不提字段类型
INSERT 时避开 TEXT/BLOB 的实操策略
真正有效的优化,是让大字段“不参与 INSERT 的关键路径”:
- 把大文本先压缩再入库:应用层用 zlib 或 zstd 压缩,字段类型改用
MEDIUMBLOB(别用COMPRESS()函数,它返回 binary,客户端解压逻辑难统一) - 拆成两步写入:先
INSERT INTO table (id, meta, created_at) VALUES (...)写轻量主干;再UPDATE table SET content = ? WHERE id = ?单独更新大字段——这样事务更短、锁更细、日志更可控 - 业务允许时,用虚拟列存哈希值:
ALTER TABLE docs ADD content_hash VARCHAR(64) AS (SHA2(content, 256)) STORED,后续查重或校验直接走索引,避免读全文 - 绝对不要在
INSERT ... SELECT中带 TEXT/BLOB 字段跨表搬运,尤其当源表没主键时,MySQL 可能建内部临时表落磁盘,Using temporary; Using filesort直接出现
批量插入含 TEXT/BLOB 的正确姿势
多值 INSERT 或 LOAD DATA INFILE 看似快,但对大字段反而危险:
- 多值
INSERT的每批大小建议 ≤ 100 行(不是 1000),否则单条 SQL 超max_allowed_packet或触发内存不足(sort_buffer_size不够也会写磁盘临时表) -
LOAD DATA INFILE虽快,但若 CSV 里含未转义换行符或 NULL 字符,会直接中断;必须配FIELDS OPTIONALLY ENCLOSED BY '"'和ESCAPED BY '\' - 用
executemany(Python)或addBatch(Java)时,确认驱动已启用rewriteBatchedStatements=true,否则还是发 N 条单语句 - 插入前关掉非必要索引:
ALTER TABLE logs DISABLE KEYS,插完再ENABLE KEYS(仅 MyISAM 有效;InnoDB 不支持,需改用SET unique_checks=0+SET foreign_key_checks=0)
最常被忽略的一点:TEXT/BLOB 字段一旦存在,哪怕你 INSERT 时不碰它,只要表结构里有,InnoDB 就会在每次插入时预留溢出页管理开销。真要高性能,就得接受“大文本不该在核心交易表里”的事实——移到对象存储,数据库只存 URL 和哈希值,才是根治方案。










