常用数据类型

建表时每个字段都必须选择数据类型。

数据类型决定了:

  • 这个字段能存什么值。
  • 最大能存多大。
  • 占用多少空间。
  • 能不能排序、计算、比较。
  • 数据库能不能帮你拦住明显错误的数据。

例如:

age TINYINT UNSIGNED

这句不是随便写的。

它表示:

age 是一个很小的非负整数,范围是 0 到 255

如果连范围都不知道,就很容易把字段类型选大、选小,或者选错。

一、整数类型

整数类型用来保存没有小数的数字。

MySQL 常见整数类型如下:

类型占用空间有符号范围无符号范围
TINYINT1 字节-128 到 1270 到 255
SMALLINT2 字节-32768 到 327670 到 65535
MEDIUMINT3 字节-8388608 到 83886070 到 16777215
INT / INTEGER4 字节-2147483648 到 21474836470 到 4294967295
BIGINT8 字节-9223372036854775808 到 92233720368547758070 到 18446744073709551615

1. 什么是 UNSIGNED

默认情况下,整数类型是有符号的,也就是可以存负数。

age TINYINT

TINYINT 的范围是:

-128 到 127

如果加上 UNSIGNED,就表示无符号,不能存负数。

age TINYINT UNSIGNED

TINYINT UNSIGNED 的范围是:

0 到 255

年龄、浏览次数、库存、主键 ID 这类字段通常不应该是负数,所以一般会加 UNSIGNED

2. 常见整数选型

场景推荐类型原因
年龄TINYINT UNSIGNED0 到 255 足够
性别、状态开关TINYINT UNSIGNED小范围枚举值
阅读数、点赞数INT UNSIGNED范围够大,占用适中
库存数量INT UNSIGNED不允许负数
用户主键 IDBIGINT UNSIGNED业务增长后更稳妥
金额分单位保存BIGINT UNSIGNED例如保存 1999 表示 19.99 元

示例:

CREATE TABLE number_demo (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    age TINYINT UNSIGNED NULL,
    view_count INT UNSIGNED NOT NULL DEFAULT 0,
    stock INT UNSIGNED NOT NULL DEFAULT 0,
    PRIMARY KEY (id)
);

3. INT(11) 不是最大长度

很多人看到旧表结构里有:

INT(11)

容易误以为它表示最多存 11 位数字。

这是错的。

INT 的范围始终是:

-2147483648 到 2147483647

INT(11) 里的 11 以前主要和显示宽度有关,不决定存储范围。MySQL 8 里整数显示宽度已经不推荐使用。

所以新建表时直接写:

INT UNSIGNED

不要写成:

INT(11) UNSIGNED

二、小数类型

小数类型主要有:

类型说明是否精确
DECIMAL定点数,按十进制精确保存精确
FLOAT单精度浮点数不精确
DOUBLE双精度浮点数不精确

1. 金额使用 DECIMAL

金额不要用 FLOATDOUBLE

推荐:

price DECIMAL(10, 2) NOT NULL

DECIMAL(M, D) 的含义:

部分含义
M总位数
D小数位数
M - D整数位数

所以:

DECIMAL(10, 2)

表示:

总共最多 10 位数字,其中小数点后 2 位,整数部分最多 8 位

它能保存的最大值类似:

99999999.99

常见金额写法:

price DECIMAL(10, 2) NOT NULL DEFAULT 0.00

订单金额更大时可以写:

total_amount DECIMAL(12, 2) NOT NULL DEFAULT 0.00

2. DECIMAL 的范围限制

MySQL 里 DECIMAL 的常见限制:

限制
M 最大值65
D 最大值30
DM 的关系D 不能大于 M

例如:

DECIMAL(5, 2)

可以保存:

999.99

但是不能保存:

1000.00

因为整数部分最多只有 3 位。

3. 为什么金额不用 FLOAT

FLOATDOUBLE 是近似值。

它们适合科学计算、坐标、比例这类场景,不适合保存订单金额。

例如金额需要精确到分:

0.1 + 0.2 应该等于 0.3

浮点数在底层可能出现精度误差。

所以金额字段优先使用:

DECIMAL(10, 2)

或者直接用整数保存分:

amount_cent BIGINT UNSIGNED NOT NULL DEFAULT 0

例如 1999 表示 19.99 元。

三、字符串类型

字符串类型用来保存文本。

常见类型:

类型最大长度适合场景
CHAR(n)最多 255 个字符固定长度内容
VARCHAR(n)最多 65535 字节,实际还受行大小限制长度变化的短文本
TINYTEXT最多 255 字节很短的文本
TEXT最多 65535 字节文章正文、评论内容
MEDIUMTEXT最多 16777215 字节较大的文本
LONGTEXT最多 4294967295 字节超大文本

注意:VARCHAR(n) 里的 n 是字符数,但表的一行数据总大小有限制。使用 utf8mb4 时,一个中文或 emoji 最多可能占 4 个字节。

1. CHAR 和 VARCHAR 的区别

CHAR(n) 是固定长度。

例如:

code CHAR(6) NOT NULL

适合:

  • 固定 6 位验证码。
  • 固定长度编码。
  • 固定长度性别标识。

VARCHAR(n) 是可变长度。

例如:

username VARCHAR(50) NOT NULL

适合:

  • 用户名。
  • 邮箱。
  • 手机号。
  • 标题。

大多数业务短文本优先用 VARCHAR

2. 常见字符串选型

场景推荐类型
用户名VARCHAR(50)
邮箱VARCHAR(100)
手机号VARCHAR(20)
密码哈希VARCHAR(255)
文章标题VARCHAR(200)
商品名称VARCHAR(100)
头像地址VARCHAR(500)
个人简介VARCHAR(500)TEXT
文章正文TEXT

示例:

CREATE TABLE string_demo (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    code CHAR(6) NOT NULL,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    bio VARCHAR(500) NULL,
    content TEXT NULL,
    PRIMARY KEY (id)
);

3. 手机号不要用整数

手机号看起来是数字,但它不是拿来做数学计算的。

手机号应该用字符串保存:

phone VARCHAR(20) NOT NULL

原因:

  • 可能有国家区号,例如 +86
  • 可能有前导 0。
  • 不需要加减乘除。
  • 超长号码可能超过普通整数范围。

身份证号、银行卡号、邮政编码也一样,通常用字符串保存。

4. TEXT 不要乱用

短文本不要都用 TEXT

例如用户名:

username TEXT

这就不合适。

用户名长度本来有限,应该写:

username VARCHAR(50) NOT NULL

简单原则:

  • 长度明确、经常查询筛选的字段,用 VARCHAR
  • 内容可能很长的正文、备注、评论,用 TEXT

四、日期和时间类型

日期时间类型用来保存日期、时间和时间戳。

常见类型:

类型范围适合场景
DATE1000-01-019999-12-31生日、日期
TIME-838:59:59838:59:59时间段、时长
DATETIME1000-01-01 00:00:009999-12-31 23:59:59业务创建时间、更新时间
TIMESTAMP1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC时间戳、记录变更时间
YEAR19012155,也可为 0000年份

1. DATETIME 和 TIMESTAMP 的区别

DATETIME 保存的是日期时间本身。

created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP

它更像是在保存:

2026-06-09 14:30:00

TIMESTAMP 会受时区影响,MySQL 存储和读取时会做时区转换。

简单选择:

场景推荐
普通业务系统的创建时间、更新时间DATETIME
需要明显依赖时区转换的场景TIMESTAMP
只保存日期,不需要具体时间DATE
保存时长TIME

普通后端项目里,created_atupdated_at 常用 DATETIME

2. 创建时间和更新时间

常见写法:

created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

含义:

字段含义
created_at插入数据时自动记录当前时间
updated_at插入和更新数据时自动维护修改时间

示例:

CREATE TABLE time_demo (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

3. 生日字段不要用 DATETIME

生日只需要日期,不需要时分秒。

推荐:

birthday DATE NULL

不要写成:

birthday DATETIME NULL

除非业务确实需要具体出生时间。

五、布尔值怎么存

MySQL 没有真正独立的布尔存储类型。

BOOLEANBOOL 在 MySQL 里本质上是 TINYINT(1) 的别名。

常见写法:

is_deleted TINYINT UNSIGNED NOT NULL DEFAULT 0

约定:

含义
0
1

例如:

CREATE TABLE boolean_demo (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    is_deleted TINYINT UNSIGNED NOT NULL DEFAULT 0,
    PRIMARY KEY (id)
);

如果想限制只能是 01,可以加检查约束:

CONSTRAINT chk_boolean_demo_is_deleted CHECK (is_deleted IN (0, 1))

六、枚举类型 ENUM

ENUM 表示字段只能从固定值里选一个。

语法:

字段名 ENUM('值1', '值2', '值3')

例如文章状态:

status ENUM('draft', 'published', 'archived') NOT NULL DEFAULT 'draft'

这表示 status 只能是:

  • draft
  • published
  • archived

适合值很稳定的场景:

场景示例
文章状态draftpublishedarchived
用户状态activedisabled
订单支付状态unpaidpaidrefunded

注意:

  • ENUM 的可选值写死在表结构里。
  • 如果状态经常变化,改起来不方便。
  • 如果状态需要后台配置,应该单独建状态表。

例如:

CREATE TABLE enum_demo (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    status ENUM('draft', 'published', 'archived') NOT NULL DEFAULT 'draft',
    PRIMARY KEY (id)
);

七、JSON 类型

MySQL 支持 JSON 类型,适合保存结构不固定的扩展信息。

例如用户扩展资料:

profile JSON NULL

插入示例:

INSERT INTO users (username, email, profile)
VALUES (
    'tom',
    'tom@example.com',
    JSON_OBJECT('city', 'Shanghai', 'level', 3)
);

查询 JSON 字段:

SELECT
    username,
    JSON_EXTRACT(profile, '$.city') AS city
FROM users;

也可以使用简写:

SELECT
    username,
    profile->'$.city' AS city
FROM users;

JSON 适合什么

适合:

  • 不固定的扩展配置。
  • 第三方平台返回的原始信息。
  • 临时不确定结构的补充字段。

不适合:

  • 经常用于 WHERE 查询的字段。
  • 经常用于排序的字段。
  • 经常和其他表关联的字段。

例如用户等级经常要筛选:

WHERE level >= 3

那就不要长期放在 JSON 里,应该设计成普通字段:

level INT UNSIGNED NOT NULL DEFAULT 0

简单原则:

经常查询、排序、关联的数据,优先设计成普通字段。

八、二进制类型

二进制类型用来保存字节数据。

常见类型:

类型最大长度适合场景
BINARY(n)最多 255 字节固定长度二进制
VARBINARY(n)最多 65535 字节,实际受行大小限制可变长度二进制
BLOB最多 65535 字节二进制大对象
MEDIUMBLOB最多 16777215 字节较大二进制
LONGBLOB最多 4294967295 字节超大二进制

实际项目里,不建议把图片、视频这类大文件直接塞进 MySQL。

更常见做法是:

  • 文件上传到对象存储或服务器磁盘。
  • MySQL 只保存文件地址、大小、类型等元数据。

例如:

avatar_url VARCHAR(500) NULL

九、完整用户表示例

下面是一张用户表的字段设计:

CREATE TABLE users (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    phone VARCHAR(20) NULL,
    password_hash VARCHAR(255) NOT NULL,
    age TINYINT UNSIGNED NULL,
    birthday DATE NULL,
    status ENUM('active', 'disabled') NOT NULL DEFAULT 'active',
    profile JSON NULL,
    is_deleted TINYINT UNSIGNED NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字段说明:

字段类型原因
idBIGINT UNSIGNED适合做自增主键
usernameVARCHAR(50)用户名长度有限
emailVARCHAR(100)邮箱不是数学数字
phoneVARCHAR(20)手机号可能有区号或前导 0
password_hashVARCHAR(255)保存密码哈希,不保存明文密码
ageTINYINT UNSIGNED年龄范围小且不能为负
birthdayDATE只需要日期
statusENUM状态值固定
profileJSON保存不固定扩展资料
is_deletedTINYINT UNSIGNED约定 0 否,1 是
created_atDATETIME记录创建时间
updated_atDATETIME记录修改时间

十、完整文章表示例

下面是一张文章表的字段设计:

CREATE TABLE posts (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    user_id BIGINT UNSIGNED NOT NULL,
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL,
    summary VARCHAR(500) NULL,
    status ENUM('draft', 'published', 'archived') NOT NULL DEFAULT 'draft',
    view_count INT UNSIGNED NOT NULL DEFAULT 0,
    like_count INT UNSIGNED NOT NULL DEFAULT 0,
    published_at DATETIME NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字段说明:

字段类型原因
idBIGINT UNSIGNED适合做自增主键
user_idBIGINT UNSIGNED和用户表主键类型保持一致
titleVARCHAR(200)标题长度有限
contentTEXT正文可能较长
summaryVARCHAR(500)摘要长度有限
statusENUM状态值固定
view_countINT UNSIGNED浏览次数不应为负数
like_countINT UNSIGNED点赞数不应为负数
published_atDATETIME草稿未发布时可以为空
created_atDATETIME记录创建时间
updated_atDATETIME记录修改时间

十一、常见错误

1. 用 INT 保存手机号

错误:

phone INT

正确:

phone VARCHAR(20)

手机号不是用来计算的数字。

2. 金额使用 FLOAT

错误:

price FLOAT

正确:

price DECIMAL(10, 2)

金额必须精确。

3. 年龄用 INT

不是不能用,但没必要。

age INT

更合适:

age TINYINT UNSIGNED

4. 主键 ID 用 INT 太保守

小项目用 INT UNSIGNED 也能跑很久。

但新项目如果不确定数据量,主键 ID 推荐直接用:

BIGINT UNSIGNED

尤其是用户、订单、日志、流水这类可能增长很快的表。

5. 所有文本都用 TEXT

错误:

username TEXT
email TEXT
title TEXT

更合适:

username VARCHAR(50)
email VARCHAR(100)
title VARCHAR(200)

TEXT 留给真正可能很长的内容。

6. 可以为空和不能为空不明确

不推荐:

username VARCHAR(50)

推荐明确写:

username VARCHAR(50) NOT NULL

或者:

bio VARCHAR(500) NULL

建表时明确写 NULLNOT NULL,后面读表结构的人更容易理解。

十二、类型选择速查表

场景推荐类型
主键 IDBIGINT UNSIGNED
年龄TINYINT UNSIGNED
状态码TINYINT UNSIGNEDENUM
浏览次数INT UNSIGNED
金额DECIMAL(10, 2)
金额分单位BIGINT UNSIGNED
手机号VARCHAR(20)
邮箱VARCHAR(100)
用户名VARCHAR(50)
密码哈希VARCHAR(255)
标题VARCHAR(200)
正文TEXT
生日DATE
创建时间DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
更新时间DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
是否删除TINYINT UNSIGNED NOT NULL DEFAULT 0
扩展信息JSON

记住一句话:

能用更准确的类型,就不要用更模糊的类型。