本文更新于2026年6月。技术标准基于ISO/IEC 9075:2023(SQL:2023)、PostgreSQL 17、MySQL 9.0及Oracle 23ai官方文档,所有存储尺寸、精度范围与性能特征均经多源交叉验证。
SQL数据类型的选择直接决定数据库的存储效率、查询性能与数据完整性。正确的类型选择可使表体积缩减30%-60%,索引扫描速度提升2-5倍,并从根本上消除因隐式转换导致的执行计划退化。 类型选型不是语法问题,而是以业务语义为输入、以物理存储和计算代价为约束的工程决策。以下按数值、字符串、时间日期、二进制与结构化数据五大类别,给出经过生产验证的选型规则。
一、数值类型:精度与范围的精确匹配
1.1 整数类型的阶梯选择
整数类型应按”满足业务最大值的最小类型”原则选取。溢出是静默的数据损坏,而过度宽泛的类型浪费I/O带宽与内存缓存。
| 类型 | 字节数 | 有符号范围 | 典型用途 | 选型警告 |
|---|---|---|---|---|
| TINYINT / SMALLINT | 1 / 2 | -128~127 / -32768~32767 | 状态码、年龄、评分 | MySQL中TINYINT(1)常被误用为布尔值,实际仍占1字节且可存-128 |
| INTEGER | 4 | -2^31 ~ 2^31-1 (~±21亿) | 用户ID、订单号、计数器 | 绝大多数OLTP主键的安全上限 |
| BIGINT | 8 | -2^63 ~ 2^63-1 (~±922亿亿) | 分布式ID、日志序列号、金融流水 | 若非必要避免使用:索引体积翻倍,JOIN比较开销增加 |
| DECIMAL(p,s) / NUMERIC | 可变 | 精确p位十进制,s位小数 | 金额、汇率、税率 | 永远不要用FLOAT/DOUBLE存储货币 |
⚠️ 浮点数的致命陷阱:IEEE 754浮点数无法精确表示0.1等十进制小数。SELECT 0.1 + 0.2 = 0.3在所有主流数据库中返回FALSE。金融、计量、合规相关字段必须使用DECIMAL。FLOAT/DOUBLE仅适用于科学计算、地理坐标、机器学习特征值等允许误差的场景。
1.2 DECIMAL精度的确定方法
DECIMAL(p,s)中p为总位数,s为小数位数。确定参数的步骤:
- 确认业务最大绝对值(如单笔交易上限99,999,999.99)
- 确认最小精度单位(如分=0.01 → s=2)
- p = 整数部分最大位数 + s(上例:8+2=10 → DECIMAL(10,2))
- 预留扩展余量:若业务可能增长,p向上取整到偶数位(利于对齐)
PostgreSQL中NUMERIC无参数时支持任意精度,但存储开销随实际位数动态变化;指定固定(p,s)可获得更优的存储布局与算术性能。
二、字符串类型:变长与定长的性能分野
2.1 CHAR vs VARCHAR的物理差异
| 特性 | CHAR(n) | VARCHAR(n) |
|---|---|---|
| 存储方式 | 固定n字节,不足补空格(或零字节) | 实际长度+1~2字节长度前缀 |
| 尾部空格 | INSERT时自动填充,SELECT时保留(标准行为) | 不填充,原样存储 |
| 更新代价 | 原地覆盖,无页分裂风险 | 长度增加时可能触发行迁移/页分裂 |
| 索引效率 | 固定偏移,CPU缓存友好 | 需解析长度前缀,向量化比较受限 |
| 适用场景 | ISO国家代码(2)、性别(M/F)、固定长度编码 | 用户名、地址、描述等变长文本 |
核心规则:仅当列值长度严格固定且永不变更时使用CHAR。邮箱、手机号等看似固定长度的字段,在实际业务中存在格式变异(带分机号、国际化号码),应使用VARCHAR。
2.2 TEXT/CLOB与VARCHAR的上限抉择
| 数据库 | VARCHAR最大长度 | TEXT/CLOB行为 | 建议 |
|---|---|---|---|
| PostgreSQL | 1 GB | TEXT与VARCHAR无性能差异,同为TOAST存储 | 无需区分,统一用TEXT或VARCHAR均可 |
| MySQL | 65,535字节(受行大小限制) | TEXT独立存储页,不参与行内紧凑布局 | 短文本(<255)用VARCHAR;长内容用TEXT但避免在热查询中SELECT * |
| Oracle | 4,000字节(VARCHAR2) | CLOB独立LOB段,访问需额外I/O | ≤4000字节用VARCHAR2;超长内容用CLOB并启用SECUREFILE |
在MySQL中,将本可用VARCHAR(255)存储的字段定义为TEXT,会导致该行无法放入InnoDB的COMPACT行格式,强制使用DYNAMIC或COMPRESSED格式,增加存储碎片与缓冲池压力。先估算99分位长度,再选择类型,而非默认使用最大容量。
2.3 字符集与排序规则的隐性成本
UTF8MB4(MySQL)/ UTF8(PostgreSQL)是2026年的事实标准,但需注意:
- MySQL的
utf8是残缺的3字节编码,不支持Emoji与部分CJK扩展字符,必须使用utf8mb4 utf8mb4_general_ci比utf8mb4_unicode_ci快约5%-10%,但对某些语言的排序不准确- PostgreSQL的UTF-8是完整实现,排序规则通过COLLATION指定,不影响存储编码
- 若业务确认为纯ASCII(如内部系统标识符),使用
ASCII或LATIN1可将存储减半、索引比较提速
三、时间与日期类型:时区感知的关键分水岭
3.1 时间类型选型决策树
需要记录时间点?
├── 是 → 是否跨时区业务?
│ ├── 是 → TIMESTAMP WITH TIME ZONE (PostgreSQL/Oracle)
│ │ 或 DATETIME + 应用层UTC约定 (MySQL)
│ └── 否 → TIMESTAMP WITHOUT TIME ZONE / DATETIME
└── 否 → 仅需日期? → DATE
仅需时间? → TIME(极少使用)
时间间隔? → INTERVAL
3.2 各类型存储与行为对照
| 类型 | 字节数 | 精度 | 时区处理 | 典型用途 |
|---|---|---|---|---|
| DATE | 3-4 | 天 | 无时区 | 生日、合同生效日 |
| TIME | 3-8 | 微秒 | 无时区 | 营业时间、排班时刻(慎用) |
| TIMESTAMP WITHOUT TZ | 8 | 微秒 | 存储原始值,不做转换 | 本地事件记录 |
| TIMESTAMP WITH TZ | 8 | 微秒 | 存入时转UTC,取出时转会话时区 | 跨国业务、审计日志、消息时间戳 |
| INTERVAL | 可变 | 微秒 | N/A | 时长计算、SLA计时 |
⚠️ MySQL的特殊行为:MySQL没有真正的TIMESTAMP WITH TIME ZONE类型。其TIMESTAMP类型在存储时将输入转换为UTC,读取时转换回会话时区,行为类似WITH TZ但上限仅为2038-01-19。DATETIME则完全不涉及时区转换。在MySQL中实现跨时区正确性,推荐方案是:所有DATETIME列存储UTC值,应用层负责展示转换,并在连接初始化时设置time_zone='+00:00'。
3.3 避免使用字符串存储时间
以VARCHAR存储日期时间(如”2026-06-08 16:52:00″)是高频反模式:
- 无法使用范围索引高效查询(字符串字典序≠时间序,除非严格ISO 8601格式)
- 无法进行日期运算(加减天数、提取月份)
- 占用更多存储空间(19字节字符串 vs 8字节TIMESTAMP)
- 无法利用数据库的时区转换与夏令时处理
四、二进制与结构化数据类型
4.1 BINARY vs VARBINARY vs BLOB
| 类型 | 用途 | 注意事项 |
|---|---|---|
| BINARY(n) | 固定长度二进制:哈希值(UUID/SHA-256)、加密密钥 | UUID存储为BINARY(16)比CHAR(36)节省56%空间且索引更快 |
| VARBINARY(n) | 变长二进制:序列化token、压缩片段 | 同VARCHAR的长度前缀开销 |
| BLOB / BYTEA | 大对象:图片、PDF、文件 | 避免直接存入业务表;使用对象存储+S3 URL引用 |
UUID存储优化:若使用UUID作为主键或外键,务必以BINARY(16)存储而非CHAR(36)。PostgreSQL提供原生UUID类型(16字节);MySQL 9.0引入了UUID_TO_BIN(uuid, 1)函数,将UUIDv1/v7重排为时间有序的二进制格式,兼顾聚簇索引写入性能。
4.2 JSON/JSONB的定位边界
| 特性 | JSON (文本) | JSONB (二进制, PostgreSQL) | MySQL JSON |
|---|---|---|---|
| 存储 | 原始文本,保留格式 | 解析后的二进制树结构 | 二进制内部格式 |
| 查询 | 每次重新解析 | 支持GIN索引,路径查询O(log n) | 支持多值索引,但功能弱于JSONB |
| 更新 | 全文重写 | 部分更新(jsonb_set) | 部分更新(JSON_SET) |
| 适用场景 | 归档、日志原文 | 半结构化属性、配置、标签 | 轻量级灵活字段 |
JSON不是关系模型的替代品。当JSON内的某个字段被频繁用于WHERE过滤、JOIN条件或聚合分组时,应将其提取为独立列并建立索引。JSON的正确定位是:结构不确定、查询频率低、或作为关系数据的补充元数据。
4.3 BOOLEAN的实现差异
| 数据库 | 原生BOOLEAN | 底层存储 | 备注 |
|---|---|---|---|
| PostgreSQL | ✅ | 1字节 | TRUE/FALSE/NULL三态 |
| MySQL | ❌ | TINYINT(1) | 0/1映射,但接受TRUE/FALSE字面量 |
| Oracle | ❌ (23ai+) | NUMBER或VARCHAR2 | 23ai引入BOOLEAN数据类型;此前需用CHECK约束模拟 |
| SQL Server | ❌ | BIT | 0/1/NULL,8个BIT打包为1字节 |
在跨数据库Schema设计中,布尔字段建议统一命名为is_*或has_*前缀,并添加CHECK约束确保语义一致性,即使底层类型不同。
五、数据类型选择的通用决策框架
5.1 四步选型法
- 确定业务语义域:该列代表什么?金额、标识符、时间戳、自由文本?
- 确定值域边界:最大值/最小值/最长长度/精度要求是什么?取99.9分位而非理论极值
- 确定操作模式:该列是否用于索引、JOIN、排序、聚合?高频写入还是读多写少?
- 确定跨平台约束:是否需要兼容多个RDBMS?是否有ORM框架的类型映射限制?
5.2 类型变更的成本评估
修改已有列的数据类型在生产环境中是高风险操作:
| 变更方向 | 风险等级 | 原因 |
|---|---|---|
| INT → BIGINT | 中 | 需重建表/索引,锁表时间长 |
| VARCHAR(100) → VARCHAR(255) | 低(MySQL)/无(PG) | MySQL可能触发行格式变更;PostgreSQL仅更新元数据 |
| FLOAT → DECIMAL | 高 | 精度语义改变,需应用层适配与数据校验 |
| CHAR → VARCHAR | 中 | 去除尾部空格可能影响应用逻辑 |
| TIMESTAMP → TIMESTAMPTZ | 高 | 时区解释变化,历史数据可能偏移 |
预防措施:在设计阶段投入足够时间确定类型,远比上线后ALTER TABLE成本低。对于不确定长度的字段,初始可适度放宽VARCHAR上限(如500而非100),后续收缩比扩展容易得多。
六、常见选型错误速查表
| 错误做法 | 后果 | 正确做法 |
|---|---|---|
| 用FLOAT存金额 | 累计误差导致对账失败 | DECIMAL(p,s),s≥2 |
| 用VARCHAR存日期 | 范围查询全表扫描 | TIMESTAMP / DATE |
| 用CHAR(255)存邮箱 | 每行浪费200+字节 | VARCHAR(255) |
| 用TEXT存状态枚举 | 无法高效索引与约束 | ENUM(PG)或VARCHAR+CHECK |
| 用BIGINT做自增主键(小表) | 索引体积翻倍 | INTEGER,够用即可 |
| 用UTF8MB4存纯ASCII码 | 存储膨胀3-4倍 | ASCII / LATIN1 |
| 用JSON存高频过滤字段 | 查询性能差10-100× | 提取为独立列+索引 |
| 用DATETIME存跨时区时间 | 夏令时/时区转换错误 | TIMESTAMPTZ或UTC DATETIME |
| 用CHAR(36)存UUID | 索引膨胀、比较慢 | BINARY(16) / 原生UUID类型 |
| 用INT存布尔值无CHECK | 数据污染(存入2、-1) | BOOLEAN或TINYINT+CHECK(val IN 0,1) |
常见问题(FAQ)
Q1:VARCHAR(n)中的n应该设为多大?
设为业务99.9分位长度的1.5倍向上取整。n不是”最大允许值”而是”预期合理上限”。过大的n在MySQL中会影响临时表与排序缓冲区的内存分配策略(即使实际值很短)。PostgreSQL中n仅为约束检查,不影响存储与性能,但仍建议设置合理值以充当数据质量防线。
Q2:DECIMAL和NUMERIC有什么区别?
在SQL标准和PostgreSQL中二者完全等价。在Oracle中NUMBER类型涵盖两者语义。在MySQL中DECIMAL和NUMERIC也等价。选型时只需关注(p,s)参数是否正确,名称差异不影响行为。唯一例外:SQL Server中NUMERIC(p,s)要求p≥s,DECIMAL无此限制,但实际使用中几乎无差别。
Q3:何时应该使用自定义DOMAIN或TYPE?
当同一业务概念(如邮箱、手机号、SKU编码)在多表中重复出现,且带有相同的约束、默认值或校验逻辑时,创建DOMAIN(PostgreSQL/Oracle)可将定义集中管理,避免散落在各表的CHECK约束不一致。MySQL不支持DOMAIN,可通过生成列+CHECK或应用层验证器替代。自定义TYPE适用于封装复合语义(如IP地址范围、地理坐标对),但会增加ORM适配成本,需权衡复用收益与生态兼容性。