常用数据类型

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

数据类型决定了:

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

例如:

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

金额不要用 FLOAT 或 DOUBLE。

推荐:

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
D 和 M 的关系D 不能大于 M

例如:

DECIMAL(5, 2)

可以保存:

999.99

但是不能保存:

1000.00

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

3. 为什么金额不用 FLOAT

FLOAT 和 DOUBLE 是近似值。

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

例如金额需要精确到分:

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-01 到 9999-12-31生日、日期
TIME-838:59:59 到 838:59:59时间段、时长
DATETIME1000-01-01 00:00:00 到 9999-12-31 23:59:59业务创建时间、更新时间
TIMESTAMP1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC时间戳、记录变更时间
YEAR1901 到 2155,也可为 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_at、updated_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 没有真正独立的布尔存储类型。

BOOLEAN 和 BOOL 在 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)
);

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

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

适合值很稳定的场景:

场景示例
文章状态draft、published、archived
用户状态active、disabled
订单支付状态unpaid、paid、refunded

注意:

  • 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

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

十二、类型选择速查表

场景推荐类型
主键 IDBIGINT UNSIGNED
年龄TINYINT UNSIGNED
状态码TINYINT UNSIGNED 或 ENUM
浏览次数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

记住一句话:

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