应避免用TEXT/BLOB存短文本,优先VARCHAR;主键优选BIGINT自增;宽表按访问正交性拆分;JSON仅用于非查询场景,需虚拟列索引才可高效过滤。

避免使用 TEXT 或 BLOB 类型存储短文本
MySQL 对 TEXT 和 BLOB 的处理开销明显高于 VARCHAR,尤其在排序、临时表、索引构建时容易触发磁盘临时表或内存不足。如果字段内容基本在 500 字符以内(比如标题、摘要、状态说明),直接用 VARCHAR(500) 更稳妥。
- 若必须用
TEXT,确保不把它加进ORDER BY、GROUP BY或联合索引的前导列 -
TINYTEXT和MEDIUMTEXT并不会比TEXT更快,底层机制一致,别被名字误导 - InnoDB 行格式为
COMPACT或REDUNDANT时,TEXT值前 768 字节会存入行内,其余外存——这会导致单行物理尺寸不可控,影响缓冲池效率
主键必须是 BIGINT 自增还是可以选 UUID?
绝大多数 OLTP 场景下,BIGINT UNSIGNED AUTO_INCREMENT 是更优选择。不是因为 UUID 功能弱,而是它会显著破坏聚簇索引的写入局部性。
-
UUID(尤其是UUID_SHORT()以外的)是随机值,新记录大概率插入到 B+ 树中间页,引发频繁页分裂和大量随机 I/O -
CHAR(36)占用 36 字节,作为主键会放大所有二级索引的体积(因二级索引叶子节点存主键值) - 若业务强依赖分布式 ID,优先考虑
ULID或KSUID,或用CONCAT(YEAR(NOW()), LPAD(FLOOR(RAND()*10000),4,'0'), LPAD(id,6,'0'))类时间前缀方案,而非纯随机UUID
什么时候该拆分大宽表?
不是“字段多”就要拆,关键是看访问模式是否正交。典型信号是:查询中长期只读取某几列,但每次都要加载几十列,且其中包含 TEXT、JSON、大 VARCHAR 或频繁更新的计数字段。
- 把高频访问的“热点字段”(如
user_id、status、created_at)留在主表;低频/大体积字段(如profile_json、remark)移到扩展表,用相同主键关联 - 避免跨表 JOIN 取数据后在应用层拼装——这会放大网络和 GC 开销;更适合用应用层两次单表查询(前提是缓存友好)
- 如果扩展表字段也常被 WHERE 过滤,记得在扩展表上单独建索引,不要指望主表索引能覆盖
JSON 字段真的方便,但要注意这些硬伤
JSON 类型适合存结构松散、不参与 JOIN、不用于精确 WHERE 查询的配置类数据。一旦开始在 WHERE 中写 JSON_CONTAINS(col, '"paid"') 或 col->>"$.amount" > 100,性能就很容易掉坑里。
- MySQL 5.7+ 支持对
JSON字段建虚拟列 + 索引,但必须显式定义:ALTER TABLE t ADD amount DECIMAL AS (data->>"$.amount"); CREATE INDEX idx_amount ON t(amount); - 没有虚拟列索引时,
JSON_EXTRACT()操作无法走索引,全表扫描风险高 -
JSON_SET()、JSON_REPLACE()会重写整字段,即使只改一个 key,也可能触发行锁升级或 MVCC 版本膨胀
JSON 字段,比一个带冗余索引的 VARCHAR 更危险;而一个因主键混乱导致页分裂率长期高于 30% 的表,加再多缓存也救不回写吞吐。











