SQL 数据类型选择对查询性能影响

2次阅读

char(11)存纯11位手机号比varchar更高效,因定长利于索引对齐;状态值优先选tinyint而非enum以避免序号偏移风险;全时区统一场景用datetime优于timestamp;text字段应拆分避免select *触发溢出页i/o。

SQL 数据类型选择对查询性能影响

VARCHAR 存手机号,查得慢?先看实际存储长度

多数人默认用 VARCHAR(20) 存手机号,但 mysqlVARCHAR 的比较和索引查找效率,会受实际存入内容长度影响——尤其是当字段被用于 WHEREJOIN 时。

如果所有手机号都是 11 位纯数字(如 '13812345678'),用 CHAR(11) 反而更稳:定长字段在 B+ 树索引中对齐更好,排序和范围扫描更少跳转。

  • MySQL 8.0+ 对短 VARCHAR(≤ 255 字节)的行内存储已优化,但 CHARutf8mb4 下仍固定占 44 字节(11×4),而 VARCHAR 多 1–2 字节长度头
  • 若字段含空格、括号或国际前缀(如 '+86-138-1234-5678'),VARCHAR 更合理,但需在应用层清洗后写入,避免查询时用 TRIM()REPLACE()——这些函数会让索引失效
  • 别在 VARCHAR 字段上建前缀索引(如 INDEX(phone(6)))来“省空间”,11 位手机号前 6 位高度重复(如全是 138159),索引区分度极低,等效于全表扫

TINYINTENUM 存状态值,哪个更快?

TINYINT 查询性能略优,但前提是状态值真的只有几个且稳定;ENUM 表面省空间,实际在排序、GROUP BY 和主从同步中容易出隐性问题。

比如定义 status ENUM('pending','paid','shipped','cancelled'),MySQL 内部按序号(1–4)存储,但一旦 ALTER table 新增值(如中间插 'refunded'),原有数据的序号会整体偏移,导致主从不一致或 ORDER BY 结果错乱。

  • TINYINT UNSIGNED + 注释说明映射(如 // 1=pending, 2=paid...),应用层转换,数据库只管数值比较——索引查找快,无隐式转换开销
  • 如果状态值未来可能扩展到 10+ 种,直接上 SMALLINT,别硬撑 TINYINT,溢出后加字段代价远高于预留 1 字节
  • 别把 ENUM 当约束用:它不阻止插入非法字符串(MySQL 5.7+ 默认警告而非报错),真要强校验,用 CHECK 约束(MySQL 8.0.16+)或应用层控制

DATETIME vs TIMESTAMP:时区和范围怎么选不拖慢查询?

二者底层存储不同:TIMESTAMP 存 UTC 时间戳(4 字节),DATETIME 存字面值(5–8 字节),但真正影响性能的是时区转换逻辑。

如果业务全在中国时区(Asia/Shanghai),又频繁做 WHERE created_at > '2024-01-01',用 TIMESTAMP 反而多一层时区转换——MySQL 每次比较都要把输入字符串转为 UTC,再跟磁盘里存的 UTC 值比;而 DATETIME 是直存直比。

  • 跨时区服务(如订单中心支持全球下单)才用 TIMESTAMP,否则统一用 DATETIME,避免隐式转换打乱执行计划
  • TIMESTAMP 范围只到 2038 年,如果存历史档案或长期合约时间,必须用 DATETIME,否则 INSERT 会静默截断或报错
  • 别在 TIMESTAMP 字段上用 CONVERT_TZ() 做查询条件,函数包裹字段 = 索引失效;需要本地时区展示,应该在应用层转,不是 SQL 层转

大文本字段(TEXT/MEDIUMTEXT)为什么让 SELECT * 变慢?

不是因为它们“大”,而是因为 MySQL 默认把 TEXT 类型存在单独的溢出页(off-page),主记录只留 20 字节指针。只要 SELECT 涉及该字段,就要额外 I/O 去读那些页——哪怕你只取 1 行,哪怕字段值只有 100 字节。

更糟的是,如果表里有多个 TEXT 字段,或者用了 ROW_FORMAT=COMPACT(默认),小值也可能被挤出去,触发无谓的溢出读取。

  • 把非必查的大字段(如日志原文、HTML 内容)拆到独立关联表,主表只留 ID,用 JOIN 按需加载
  • 确认是否真需要 MEDIUMTEXT:如果最长内容不超过 64KB,TEXT 就够,MEDIUMTEXT 不提升性能,只增加管理复杂度
  • SELECT id, title FROM posts 代替 SELECT *,尤其当表含 TEXT 时,能直接避开溢出页读取

类型选错不会立刻报错,但会在慢查询、锁等待、复制延迟里慢慢咬你一口。最常被忽略的,是字段语义和实际数据分布之间的断层——比如用字符串存金额、用浮点存精度要求高的计数,这种错,索引和缓存都救不回来。

text=ZqhQzanResources