列数超过100且含多个TEXT/JSON字段时会明显拖慢查询;单行超4KB易致页分裂,超8KB触发MySQL外存溢出,PostgreSQL中TOAST字段名存在即影响索引选择。
宽表列数超过多少会明显拖慢查询?
实际业务中,select * 扫描 50+ 列的宽表,在 mysql 8.0 或 postgresql 14 上,单行数据体积超 4kb 就可能触发页分裂或缓存失效;当列数 > 100 且含多个 text/json 字段时,即使只查 2–3 列,全表扫描的 i/o 和内存开销也会陡增。这不是理论阈值,而是从慢查询日志里反复出现的 using temporary; using filesort 和高 handler_read_next 值反推出来的临界点。
常见错误现象:
• 同一条 WHERE id = ? 查询,加了 SELECT * 后响应时间从 5ms 跳到 120ms
• EXPLAIN 显示 key_len 很小,但 rows 预估准确,实际执行却严重超时
• 应用层 GC 频率上升,尤其在批量导出接口里
- 别只看列数量,重点检查是否混入了大字段:一个
body TEXT+ 三个metadata JSON就能让逻辑行膨胀到 16KB+ - PostgreSQL 中,
TOAST表虽能延迟加载大字段,但只要查询语句里写了这些字段名(哪怕没用到),优化器仍可能放弃索引只扫堆表 - MySQL 的
row_format=COMPACT下,单行超 8KB 会强制外存溢出,导致每次访问都要多一次磁盘寻道
如何用结构视图快速定位“伪宽表”?
所谓“伪宽表”,是指表定义里列很多,但真正高频访问的只有 10% 左右——比如用户表有 87 列,但登录、鉴权、展示头像只依赖其中 6 列。这时候结构视图(information_schema.COLUMNS 或 pg_attribute)不是用来数列,而是找“沉默列”。
实操建议:
- 查
information_schema.COLUMNS时加过滤:WHERE table_name = 'user_profile' AND column_default IS NULL AND is_nullable = 'YES',这类列大概率长期为空,属于可拆候选 - 对比
pg_stat_all_columns(PG)或慢查日志中的used_columns(需开启performance_schema),找出半年内从未出现在WHERE/JOIN/ORDER BY中的列 - 警惕带
_backup、_old、_v2后缀的列,它们常是迁移残留,但仍在表结构里占位、参与 MVCC 版本链
垂直拆分前必须验证的三个依赖点
拆表不是删列,而是把原表按访问频次/一致性要求切分成多张物理表。如果跳过依赖验证,很容易出现“拆完更慢”或“事务跨表失败”。
关键检查项:
- 应用层 ORM 是否硬编码了
SELECT *或字段顺序(如 MyBatis 的resultMap按 index 映射)——一旦拆分,ResultSet列数变化直接抛SQLException - 是否存在跨列计算逻辑:比如
CONCAT(first_name, ' ', last_name)在原表里是表达式,拆到两张表后就得改写成 JOIN,而 JOIN 可能破坏原有索引覆盖 - Binlog / CDC 工具是否订阅整表:若用
Debezium订阅user_profile,拆成user_core和user_ext后,下游消费者会收不到完整变更事件,需同步调整 topic 和 schema registry
拆分后索引和统计信息容易被忽略的细节
垂直拆分后,原表上的复合索引大概率失效,但新表的索引不是简单照搬。更麻烦的是统计信息不会自动重采样,优化器仍按旧分布估算,导致执行计划劣化。
必须手动处理:
- MySQL 中,拆分后立即执行
ANALYZE TABLE user_core,否则EXPLAIN里的rows还是按 87 列时的基数估算 - PostgreSQL 中,
VACUUM ANALYZE user_core不够,要确认pg_stats里n_distinct和most_common_vals是否已更新,特别是拆出的user_settings表,其theme列可能从“几乎唯一”变成“90% 是 dark” - 不要给新表盲目加索引:比如原表有
(status, created_at)索引,拆后status在user_core,created_at在user_meta,这个索引就彻底无意义了
最常被跳过的一步:检查外键约束是否还指向原表。拆分后若保留 user_profile.id → order.user_id,但 user_profile 已不存在,某些 ORM 会在启动时报错,而有些则静默降级为无约束关联——后者更危险,数据不一致很难回溯。











