一、了解数据类型
建表时最关键的决策之一就是字段类型的选择。MySQL 的数据类型大致分为数值、字符串、日期时间、二进制和 JSON 五大类,选型时优先考虑「够用就好」,避免盲目使用大类型。
1.1 数值类型
| 类型 | 字节 | 范围 / 说明 | 典型用途 |
|---|---|---|---|
TINYINT |
1 | -128 ~ 127(无符号 0 ~ 255) | 状态、布尔标记 |
SMALLINT |
2 | -32768 ~ 32767 | 小范围计数 |
MEDIUMINT |
3 | -8388608 ~ 8388607 | 中等计数 |
INT |
4 | -2147483648 ~ 2147483647 | 主键、普通整数 |
BIGINT |
8 | -2^63 ~ 2^63-1 | 雪花 ID、超大计数 |
DECIMAL(M,D) |
变长 | 精确小数,M 为总位数,D 为小数位 | 金额、精确计算 |
FLOAT / DOUBLE |
4 / 8 | 近似浮点数,存在精度误差 | 科学计算、统计值 |
金额类字段务必使用 DECIMAL,不要用 FLOAT 或 DOUBLE,否则会出现精度丢失。整数类型可以加 UNSIGNED 表示非负,从而扩大正数范围。
sql
CREATE TABLE t_account (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
balance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
status TINYINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
1.2 字符串类型
| 类型 | 说明 | 典型用途 |
|---|---|---|
CHAR(N) |
定长字符串,不足补空格,N 最大 255 | 固定长度编码、性别等 |
VARCHAR(N) |
变长字符串,需额外 1~2 字节记录长度 | 用户名、标题、地址 |
TINYTEXT / TEXT / MEDIUMTEXT / LONGTEXT |
大文本,最大约 255B / 64KB / 16MB / 4GB | 文章正文、日志、富文本 |
ENUM |
枚举,只能取预设值之一 | 固定状态集合 |
SET |
集合,可存多个预设值 | 多选标签 |
选型建议:长度基本固定的用 CHAR,长度变化大的用 VARCHAR;VARCHAR 的长度要按实际业务上限设置,不要一律写 255 或 1000。大文本字段尽量拆到独立表,避免影响主表查询性能。
sql
CREATE TABLE t_user (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
username VARCHAR(32) NOT NULL,
gender CHAR(1) NOT NULL DEFAULT 'U',
intro TEXT,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username)
);
1.3 日期时间类型
| 类型 | 字节 | 说明 |
|---|---|---|
DATE |
3 | 日期,格式 YYYY-MM-DD |
TIME |
3 | 时间,格式 HH:MM:SS |
DATETIME |
8 | 日期时间,与时区无关,范围 1000 ~ 9999 年 |
TIMESTAMP |
4 | 时间戳,随服务器时区转换,范围 1970 ~ 2038 年 |
YEAR |
1 | 年份 |
业务时间字段推荐使用 DATETIME,不受时区和 2038 年问题影响;需要自动记录创建和更新时间时,可以配合 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP。
sql
CREATE TABLE t_order (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
1.4 二进制与 JSON 类型
BINARY(N) 和 VARBINARY(N) 用于存储定长和变长二进制数据,BLOB 系列用于存储大块二进制内容(如图片、文件),但生产环境更推荐把文件放到对象存储,数据库只保存路径。
MySQL 5.7 起支持 JSON 类型,可以存储并校验 JSON 文档,配合 -> 和 ->> 操作符查询字段:
sql
CREATE TABLE t_config (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ext JSON,
PRIMARY KEY (id)
);
INSERT INTO t_config (ext) VALUES ('{"theme":"dark","lang":"zh"}');
SELECT ext->>'$.theme' AS theme FROM t_config;
JSON 适合结构不固定的扩展字段,但高频查询和统计的字段仍应拆成独立列并建立索引。
1.5 选型小结
- 能用小类型就不用大类型 :状态用
TINYINT,不要用INT。 - 金额用
DECIMAL,避免浮点误差。 - 字符串按实际长度设置 ,
VARCHAR不要盲目给到最大值。 - 时间优先
DATETIME,避免时区与 2038 年问题。 - 大字段和 JSON 谨慎使用,高频查询字段应独立成列。
二、最基础的建库命令
MySQL 中创建数据库使用 CREATE DATABASE 语句,最简形式如下:
sql
CREATE DATABASE mydb;
执行后会在当前实例中创建一个名为 mydb 的数据库。如果同名数据库已存在,MySQL 会报错 ERROR 1007 (HY000): Can't create database 'mydb'; database exists。
2.1 避免重复建库报错:IF NOT EXISTS
在脚本或自动化部署中,通常加上 IF NOT EXISTS,让命令具备幂等性:
sql
CREATE DATABASE IF NOT EXISTS mydb;
这样当数据库已存在时,MySQL 只会给出一个 Warning,而不会中断执行。
三、查看库
建库之后,第一步通常是确认当前实例中有哪些数据库、当前连接的是哪个库,以及库的字符集和排序规则。以下命令无需切换数据库即可执行。
sql
-- 查看当前实例中的所有数据库
SHOW DATABASES;
-- 查看库名匹配某个模式(支持 % 和 _ 通配符)
SHOW DATABASES LIKE 'my%';
-- 查看当前正在使用的数据库(未选择时返回 NULL)
SELECT DATABASE();
-- 查看指定库的创建语句(含字符集、排序规则)
SHOW CREATE DATABASE mydb;
其中 SHOW DATABASES 只列出当前账号有权限访问的库,看不到无权限的库属于正常现象;SHOW CREATE DATABASE 输出的信息最完整,排查字符集和排序规则问题时优先使用它。
常用命令速查:
SHOW DATABASES;:列出所有可见数据库。SELECT DATABASE();:确认当前所在库。SHOW CREATE DATABASE mydb;:查看库的字符集与排序规则。information_schema.SCHEMATA:批量查询库元信息。information_schema.TABLES:按库统计容量占用。
四、创建表
建库并切换到目标库后,就可以使用 CREATE TABLE 创建表。最简形式只需指定表名和字段定义:
sql
USE mydb;
CREATE TABLE t_user (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
username VARCHAR(32) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
其中 AUTO_INCREMENT 表示自增主键,NOT NULL 表示字段不可为空,DEFAULT 用于指定默认值。字段类型的选择可以参考前面的数据类型章节。
实际建表时通常还需要指定存储引擎、字符集和排序规则,并补充索引与注释:
sql
CREATE TABLE t_order (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户 ID',
order_no VARCHAR(32) NOT NULL COMMENT '订单号',
amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额',
status TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '状态:0 待支付,1 已支付',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no),
KEY idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='订单表';
ENGINE=InnoDB:支持事务和行级锁,是生产环境的默认选择。CHARSET=utf8mb4:完整支持中文和 Emoji,避免乱码问题。UNIQUE KEY:唯一索引,用于保证业务唯一性,如订单号。KEY:普通索引,用于加速按用户 ID 等字段的查询。COMMENT:字段和表的注释,便于后续维护。
查看表
查看当前库中的所有表,可以使用 SHOW TABLES 语句,支持指定库名和模糊匹配:
sql
-- 查看当前库中的所有表
SHOW TABLES;
-- 查看指定库中的表
SHOW TABLES FROM mydb;
-- 按模式匹配表名(支持 % 和 _ 通配符)
SHOW TABLES LIKE 't_%';
如果需要查看表的结构,可以使用 DESC 表名 或 SHOW CREATE TABLE 表名,后者输出的信息更完整,包含索引、引擎和字符集。
删除表
删除表使用 DROP TABLE 语句,语法如下:
sql
DROP TABLE [IF EXISTS] 表名;
加上 IF EXISTS 可以避免表不存在时报错,让脚本具备幂等性。注意:DROP TABLE 会同时删除表结构和全部数据,且无法回滚,执行前务必确认。
修改表
表结构变更统一使用 ALTER TABLE 语句,下面按常见场景逐一说明。
1. 修改表名
方式一,使用 ALTER TABLE ... RENAME:
sql
ALTER TABLE 表名 RENAME 新表名;
方式二,使用 RENAME TABLE ... TO,该方式还支持一次重命名多张表:
sql
RENAME TABLE 表名 TO 新表名;
2. 新增列
语法如下,[AFTER 列名 | FIRST] 用于指定新列的位置,省略时默认追加到最后一列:
sql
ALTER TABLE 表名 ADD 新列名 列类型 [AFTER 列名 | FIRST];
示例一,在表末尾增加一列 password:
sql
ALTER TABLE student1 ADD password CHAR(8);
示例二,使用 AFTER 在指定列之后插入新列 notes:
sql
ALTER TABLE student1 ADD notes VARCHAR(30) AFTER age;
示例三,使用 FIRST 在第一列插入新列:
sql
ALTER TABLE student1 ADD aa INT FIRST;
注意:MySQL 没有 BEFORE 关键字,只能通过 AFTER 或 FIRST 控制位置。
3. 删除列
语法如下:
sql
ALTER TABLE 表名 DROP 列名;
示例,删除上例中新增的 notes 和 aa 列:
sql
ALTER TABLE student1 DROP notes;
ALTER TABLE student1 DROP aa;
4. 修改列名
使用 CHANGE 可以同时修改列名和列类型,旧列名和新列名都必须写全:
sql
ALTER TABLE 表名 CHANGE 旧列名 新列名 列类型;
示例,把 password 改名为 passwd:
sql
ALTER TABLE student1 CHANGE password passwd CHAR(8);
5. 修改列类型
只修改列类型、不改列名时使用 MODIFY:
sql
ALTER TABLE 表名 MODIFY 列名 列类型;
示例,先新增一列 birthday,再把它修改为 DATE 类型:
sql
ALTER TABLE student1 ADD birthday CHAR(10);
ALTER TABLE student1 MODIFY birthday DATE;
6. 修改列位置
使用 MODIFY 配合 AFTER 或 FIRST 可以调整列的位置:
sql
ALTER TABLE 表名 MODIFY 列名 列类型 AFTER 某列;
ALTER TABLE 表名 MODIFY 列名 列类型 FIRST;
示例,把 notes 移动到 birthday 之后:
sql
ALTER TABLE student1 MODIFY notes VARCHAR(30) AFTER birthday;
复制表
复制表结构
使用 LIKE 只复制表结构,不复制数据:
sql
CREATE TABLE 新表名 LIKE 源表;
复制表结构 + 数据
使用 CREATE TABLE ... SELECT 可以同时复制结构和数据:
sql
CREATE TABLE 新表名 SELECT * FROM 源表;
注意:这种方式不会复制源表的索引、主键和自增属性,需要的话要手动补充。
复制表数据
如果目标表已经存在,只想复制数据,使用 INSERT INTO ... SELECT:
sql
INSERT INTO 新表名 SELECT * FROM 源表;
如果两张表的列不完全一致,建议显式写出列名,避免因列顺序不同导致数据错位:
sql
INSERT INTO 新表名 (id, username, created_at)
SELECT id, username, created_at FROM 源表;
总结
下面用一张速查表汇总本文涉及的核心 SQL 语句,方便日常查阅。
| 操作分类 | 核心 SQL 语句 | 用途说明 |
|---|---|---|
| 建库 | CREATE DATABASE [IF NOT EXISTS] 库名; |
创建数据库,加 IF NOT EXISTS 可保证幂等 |
| 查看库 | SHOW DATABASES; / SELECT DATABASE(); / SHOW CREATE DATABASE 库名; |
列出所有库、确认当前库、查看库的字符集与排序规则 |
| 建表 | CREATE TABLE 表名 (字段定义, 索引) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
创建表结构,指定字段类型、主键、索引、引擎和字符集 |
| 查看表 | SHOW TABLES; / DESC 表名; / SHOW CREATE TABLE 表名; |
列出表、查看字段结构、查看完整建表语句 |
| 删除表 | DROP TABLE [IF EXISTS] 表名; |
删除表结构和全部数据,不可回滚,需谨慎执行 |
| 修改表名 | ALTER TABLE 表名 RENAME 新表名; / RENAME TABLE 表名 TO 新表名; |
重命名表,后者支持一次重命名多张表 |
| 新增列 | `ALTER TABLE 表名 ADD 列名 列类型 [AFTER 列名 | FIRST];` |
| 删除列 | ALTER TABLE 表名 DROP 列名; |
删除字段及其数据 |
| 修改列名 | ALTER TABLE 表名 CHANGE 旧列名 新列名 列类型; |
同时修改列名和列类型 |
| 修改列类型 | ALTER TABLE 表名 MODIFY 列名 列类型; |
只改类型,不改列名 |
| 修改列位置 | `ALTER TABLE 表名 MODIFY 列名 列类型 [AFTER 某列 | FIRST];` |
| 复制表结构 | CREATE TABLE 新表名 LIKE 源表; |
只复制结构,含索引,不含数据 |
| 复制表结构 + 数据 | CREATE TABLE 新表名 SELECT * FROM 源表; |
同时复制结构和数据,但不复制索引与自增属性 |
| 复制表数据 | INSERT INTO 新表名 SELECT * FROM 源表; |
目标表已存在时只复制数据,建议显式写出列名 |
建表与改表的三条最佳实践:
- 字段类型够用就好 :状态用
TINYINT、金额用DECIMAL、时间优先DATETIME,VARCHAR按实际业务上限设置,避免盲目使用大类型造成空间浪费和性能下降。 - 建表时一次把约束和索引写全 :主键、
NOT NULL、默认值、唯一索引、普通索引、ENGINE=InnoDB、CHARSET=utf8mb4和COMMENT尽量在建表阶段确定,减少后期反复ALTER TABLE。 - 改表前先评估影响并做好备份 :大表执行
ALTER TABLE可能长时间锁表,建议在低峰期操作、先在测试库验证,并确认DROP类操作已加IF EXISTS且数据可恢复。