MySQL DDL 高频操作速查表

DDL(Data Definition Language,数据定义语言)用于定义和修改数据库结构,常见命令包括 CREATEALTERDROPTRUNCATERENAME

本文以 MySQL 8.0 为准,示例围绕电商中的用户、商品和订单场景展开。

DDL 经常会获得元数据锁,并且许多 DDL 会隐式提交事务。生产环境执行前应确认影响范围、表大小、锁表风险和回滚方案。

1. 数据库操作

1.1 创建数据库

CREATE DATABASE IF NOT EXISTS shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

1.2 查看和切换数据库

SHOW DATABASES;
USE shop;
SELECT DATABASE();

1.3 查看建库语句

SHOW CREATE DATABASE shop;

1.4 删除数据库

DROP DATABASE shop;

DROP DATABASE 会删除数据库及其中全部对象,通常无法通过普通 SQL 回滚。生产环境不要把它当作清理数据的命令。

2. 中文语境下的字符集和排序规则

中文项目推荐统一使用 utf8mb4。MySQL 中字符集决定“字符如何编码保存”,排序规则(Collation)决定“字符串如何比较和排序”。

2.1 为什么使用 utf8mb4

CREATE DATABASE shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

不要为了“只保存中文”选择 gbk。现代 Web、Node.js、JSON 和跨系统接口通常都以 UTF-8 为基础,统一使用 utf8mb4 更容易避免转换和乱码问题。

2.2 高频排序规则

排序规则 比较特点 适用场景
utf8mb4_0900_ai_ci 不区分大小写、忽略重音 普通用户名、标题、中文业务文本
utf8mb4_0900_as_cs 区分大小写和重音 需要严格区分英文大小写的文本
utf8mb4_zh_0900_as_cs 中文语言规则,区分大小写和重音 希望按中文语言规则比较、排序
utf8mb4_bin 按二进制值比较 Token、哈希值、严格区分字符的编码

后缀含义:

普通中文业务表通常可以使用:

DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci

但以下字段可能需要严格比较:

api_token VARCHAR(128)
  CHARACTER SET ascii
  COLLATE ascii_bin
  NOT NULL,

external_code VARCHAR(64)
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_bin
  NOT NULL

安全 Token 如果本身只包含 ASCII 字符,使用 ascii 可以更准确地表达数据范围;ascii_bin 会严格区分大小写。

2.3 中文排序不等于拼音排序

默认的 utf8mb4_0900_ai_ci 能很好地完成普通 Unicode 比较,但产品要求“按汉语拼音排序”时,不应想当然地认为任何 Unicode 排序规则都等同于业务需要的拼音顺序。

可以先测试中文专用排序规则:

SELECT username
FROM users
ORDER BY username COLLATE utf8mb4_zh_0900_as_cs;

如果产品对多音字、自定义称谓或精确拼音顺序有明确要求,更稳定的方式是增加专门的排序字段:

ALTER TABLE users
ADD COLUMN username_pinyin VARCHAR(200) NULL,
ADD INDEX idx_users_username_pinyin (username_pinyin);

由应用层按统一规则生成 username_pinyin,再按它排序:

SELECT id, username
FROM users
ORDER BY username_pinyin, id;

2.4 数据库、表和字段的字符集层级

字符集可以设置在服务器、数据库、表和字段等层级。字段没有显式设置时,会继承表的默认值;表再继承数据库默认值。

查看当前设置:

SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
SHOW CREATE DATABASE shop;
SHOW CREATE TABLE users;
SHOW FULL COLUMNS FROM users;

创建表时明确指定:

CREATE TABLE articles (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  title VARCHAR(200) NOT NULL,
  content TEXT NOT NULL,
  PRIMARY KEY (id)
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

只覆盖一个字段:

CREATE TABLE access_keys (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  key_value VARCHAR(128)
    CHARACTER SET ascii
    COLLATE ascii_bin
    NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uk_access_keys_key_value (key_value)
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

2.5 连接字符集同样重要

即使表是 utf8mb4,客户端与 MySQL 连接使用了错误字符集,中文仍可能乱码。

查看当前连接:

SELECT
  @@character_set_client,
  @@character_set_connection,
  @@character_set_results,
  @@collation_connection;

当前会话统一为 utf8mb4

SET NAMES utf8mb4 COLLATE utf8mb4_0900_ai_ci;

SET NAMES 只影响当前连接。实际 Node.js 项目应在数据库驱动或连接池中统一配置,并保证源码文件、HTTP JSON 和数据库连接都使用 UTF-8。

2.6 修改已有表的字符集

修改表默认值只影响以后新增的字符字段:

ALTER TABLE users
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;

转换现有字符字段和已有数据:

ALTER TABLE users
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;

CONVERT TO CHARACTER SET 可能重建大表,并改变字段能容纳的字节数。生产环境执行前要备份、评估锁表时间,并先在测试环境验证。

修改单个字段:

ALTER TABLE users
MODIFY COLUMN username VARCHAR(50)
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci
  NOT NULL;

使用 MODIFY COLUMN 时要把 NOT NULL、默认值和注释等完整定义重新写出。

2.7 唯一索引与大小写

在不区分大小写的排序规则下,以下邮箱通常会被认为相同:

User@example.com
user@example.com

因此:

email VARCHAR(255)
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci
  NOT NULL,
UNIQUE KEY uk_users_email (email)

会阻止只在大小写上不同的重复邮箱,这通常符合登录业务预期。应用层仍建议在写入前统一规范化,例如去除首尾空格并把邮箱转换为小写。

而区分大小写的编码可以使用二进制排序规则:

code VARCHAR(64)
  CHARACTER SET ascii
  COLLATE ascii_bin
  NOT NULL,
UNIQUE KEY uk_codes_code (code)

2.8 中文模糊搜索的边界

字符集正确只能保证中文能正常保存和比较,不代表普通索引可以完成中文分词搜索。

SELECT id, title
FROM articles
WHERE title LIKE '%数据库%';

这种前置 % 查询通常无法利用普通 B-Tree 索引快速定位。大量中文正文搜索应评估 MySQL FULLTEXT 的分词配置,或使用专门的搜索引擎。业务需要按词语、同义词、拼音或相关度搜索时,不能只依赖字符集设置。

2.9 中文乱码排查

按以下顺序检查:

-- 1. 数据库和表定义
SHOW CREATE DATABASE shop;
SHOW CREATE TABLE users;

-- 2. 字段级定义
SHOW FULL COLUMNS FROM users;

-- 3. 当前连接设置
SELECT
  @@character_set_client,
  @@character_set_connection,
  @@character_set_results;

-- 4. 查看字符及其十六进制字节
SELECT username, HEX(username)
FROM users
WHERE id = 1;

常见原因:

修复前必须先备份。需要先判断是“显示时解码错误”还是“数据库中已经存入错误字节”,不要直接反复执行字符集转换。

3. 高频数据类型

选择类型时优先考虑:

  1. 字段表达的是数值、标识符、文本还是时间?
  2. 取值范围和精度是多少?
  3. 是否经常用于查询、排序、计算或索引?
  4. Node.js 读取后是否存在精度或时区问题?

3.1 整数类型

类型 有符号范围 无符号范围 高频场景
TINYINT -128~127 0~255 布尔状态、小范围枚举、年龄
SMALLINT -32,768~32,767 0~65,535 小型计数、较小编号
MEDIUMINT 约 -838 万~838 万 0~约 1677 万 中等范围计数,使用较少
INT 约 -21 亿~21 亿 0~约 42 亿 库存、数量、中等规模主键
BIGINT 约 -922 京~922 京 0~约 1844 京 大型系统主键、流水 ID

真实场景:

id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
age TINYINT UNSIGNED NULL,
stock INT UNSIGNED NOT NULL DEFAULT 0,
sales_count BIGINT UNSIGNED NOT NULL DEFAULT 0

Node.js 注意事项:MySQL BIGINT 可能超过 JavaScript number 的安全整数范围 2^53 - 1。大型 ID 推荐按字符串读取和传输,避免精度丢失。

3.2 金额和精确小数

金额使用 DECIMAL(M, D)

price DECIMAL(10, 2) NOT NULL,
discount_rate DECIMAL(5, 4) NOT NULL DEFAULT 0.0000,
account_balance DECIMAL(18, 2) NOT NULL DEFAULT 0.00

FLOATDOUBLE 更适合允许误差的测量数据:

temperature DOUBLE NULL,
longitude DECIMAL(10, 7) NULL,
latitude DECIMAL(10, 7) NULL

经纬度如果需要稳定精度,可以使用 DECIMAL;如果进行专业空间查询,应考虑 MySQL 空间数据类型和空间索引。

3.3 字符串类型

类型 特点 高频场景
CHAR(n) 固定长度 国家码、固定编码
VARCHAR(n) 可变长度 用户名、邮箱、订单号、标题
TEXT 最多约 64 KB 文章正文、普通备注
MEDIUMTEXT 最多约 16 MB 长篇富文本
LONGTEXT 最多约 4 GB 极大文本,使用前应评估必要性

真实场景:

country_code CHAR(2) NOT NULL,
username VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL,
order_no VARCHAR(32) NOT NULL,
product_name VARCHAR(200) NOT NULL,
description TEXT NULL

选型原则:

手机号使用字符串的原因:

phone VARCHAR(20) NOT NULL

3.4 日期和时间类型

类型 内容 高频场景
DATE 日期 生日、账单日期
TIME 时间或持续时长 营业时间、耗时
DATETIME 日期和时间 创建时间、预约时间
TIMESTAMP 按会话时区转换的时间戳 更新时间、跨时区时间点
YEAR 年份 年度字段,使用较少

真实场景:

birthday DATE NULL,
paid_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
  ON UPDATE CURRENT_TIMESTAMP

DATETIMETIMESTAMP

项目应统一数据库、Node.js 进程和业务时区规则,避免依赖不同环境的默认设置。

3.5 布尔值和状态

MySQL 的 BOOLEANBOOL 实际是 TINYINT(1) 的同义写法:

is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
is_deleted BOOLEAN NOT NULL DEFAULT FALSE

只有两种状态时适合使用布尔值。订单状态有多种取值,可以使用 VARCHAR 配合 CHECK

status VARCHAR(20) NOT NULL DEFAULT 'pending',
CONSTRAINT chk_orders_status
  CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))

也可以使用 ENUM

status ENUM('pending', 'paid', 'shipped', 'cancelled')
  NOT NULL DEFAULT 'pending'

3.6 JSON 类型

适合保存结构可能变化的非核心扩展属性:

preferences JSON NULL,
product_attributes JSON NULL

示例值:

{
  "theme": "dark",
  "language": "zh-CN"
}

邮箱、金额、用户 ID 等核心数据不应塞进 JSON,否则难以建立清晰的约束、索引和关联关系。

经常查询某个 JSON 属性时,可以创建生成列并建立索引:

ALTER TABLE products
ADD COLUMN color VARCHAR(30)
  GENERATED ALWAYS AS (
    JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.color'))
  ) STORED,
ADD INDEX idx_products_color (color);

3.7 二进制类型

类型 高频场景
BINARY(n) 固定长度字节值
VARBINARY(n) 可变长度二进制标识、加密结果
BLOB 二进制文件或对象

大型图片和视频通常放在对象存储中,数据库保存元数据:

file_url VARCHAR(2048) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL,
mime_type VARCHAR(100) NOT NULL

直接保存大 BLOB 会增加数据库备份、恢复和复制成本。

3.8 NULL、默认值和约束

username VARCHAR(50) NOT NULL,
age TINYINT UNSIGNED NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending'

字段选型速查:

业务数据 推荐类型
大型自增主键 BIGINT UNSIGNED
年龄 TINYINT UNSIGNED
库存 INT UNSIGNED
金额 DECIMAL(10, 2)
手机号 VARCHAR(20)
邮箱 VARCHAR(255)
文章正文 TEXT
生日 DATE
创建时间 DATETIME
两态开关 BOOLEAN
扩展属性 JSON

4. 创建真实业务表

4.1 用户表

CREATE TABLE users (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(255) NOT NULL,
  phone VARCHAR(20) NULL,
  status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1=正常,0=禁用',
  deleted_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),
  UNIQUE KEY uk_users_email (email),
  UNIQUE KEY uk_users_phone (phone),
  KEY idx_users_status_created_at (status, created_at)
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

4.2 商品表

CREATE TABLE products (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  sku VARCHAR(50) NOT NULL,
  name VARCHAR(200) NOT NULL,
  price DECIMAL(10, 2) NOT NULL,
  stock INT UNSIGNED NOT NULL DEFAULT 0,
  attributes JSON NULL,
  status TINYINT UNSIGNED NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_products_sku (sku),
  KEY idx_products_status_created_at (status, created_at),
  CONSTRAINT chk_products_price CHECK (price >= 0)
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

4.3 订单表

CREATE TABLE orders (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  order_no VARCHAR(32) NOT NULL,
  user_id BIGINT UNSIGNED NOT NULL,
  total_amount DECIMAL(12, 2) NOT NULL,
  status VARCHAR(20) NOT NULL DEFAULT 'pending',
  paid_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),
  UNIQUE KEY uk_orders_order_no (order_no),
  KEY idx_orders_user_created_at (user_id, created_at),
  KEY idx_orders_status_created_at (status, created_at),
  CONSTRAINT fk_orders_user_id
    FOREIGN KEY (user_id) REFERENCES users (id),
  CONSTRAINT chk_orders_amount CHECK (total_amount >= 0),
  CONSTRAINT chk_orders_status
    CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

4.4 订单明细表

CREATE TABLE order_items (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  order_id BIGINT UNSIGNED NOT NULL,
  product_id BIGINT UNSIGNED NOT NULL,
  product_name VARCHAR(200) NOT NULL,
  unit_price DECIMAL(10, 2) NOT NULL,
  quantity INT UNSIGNED NOT NULL,
  subtotal DECIMAL(12, 2) NOT NULL,
  PRIMARY KEY (id),
  KEY idx_order_items_order_id (order_id),
  KEY idx_order_items_product_id (product_id),
  CONSTRAINT fk_order_items_order_id
    FOREIGN KEY (order_id) REFERENCES orders (id),
  CONSTRAINT fk_order_items_product_id
    FOREIGN KEY (product_id) REFERENCES products (id),
  CONSTRAINT chk_order_items_quantity CHECK (quantity > 0),
  CONSTRAINT chk_order_items_subtotal CHECK (subtotal >= 0)
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

订单明细保留 product_nameunit_price 快照,是因为商品以后可能改名或调价,历史订单仍应展示下单时的信息。

5. 查看表结构

SHOW TABLES;
DESCRIBE users;
SHOW FULL COLUMNS FROM users;
SHOW CREATE TABLE users;
SHOW INDEX FROM users;

6. 修改表结构

6.1 添加字段

ALTER TABLE users
ADD COLUMN last_login_at DATETIME NULL AFTER updated_at;

字段顺序主要影响查看体验,业务代码不应依赖 SELECT * 的字段顺序。

6.2 修改字段类型或约束

ALTER TABLE users
MODIFY COLUMN username VARCHAR(100) NOT NULL;

MODIFY COLUMN 需要重新写出完整字段定义,否则可能意外丢失 NOT NULL、默认值或注释。

6.3 重命名字段

ALTER TABLE users
RENAME COLUMN username TO display_name;

6.4 删除字段

ALTER TABLE users
DROP COLUMN last_login_at;

删除字段前应确认应用代码、报表、索引和数据库对象是否仍在使用它。

6.5 添加和删除索引

CREATE INDEX idx_orders_paid_at ON orders (paid_at);

CREATE UNIQUE INDEX uk_products_name ON products (name);

DROP INDEX idx_orders_paid_at ON orders;

联合索引:

CREATE INDEX idx_orders_user_status_created_at
ON orders (user_id, status, created_at);

联合索引通常从最左字段开始匹配。索引顺序应根据真实的 WHEREORDER BYGROUP BY 设计,而不是机械地把所有字段放进去。

6.6 添加和删除外键

ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users (id);
ALTER TABLE orders
DROP FOREIGN KEY fk_orders_user_id;

外键两侧字段的类型、是否无符号等属性应保持一致。删除外键不会自动删除相关普通索引,应使用 SHOW INDEX 检查。

6.7 添加检查约束

ALTER TABLE products
ADD CONSTRAINT chk_products_stock CHECK (stock >= 0);
ALTER TABLE products
DROP CHECK chk_products_stock;

7. 表重命名、复制和清空

7.1 重命名表

RENAME TABLE users TO app_users;

7.2 复制表结构

CREATE TABLE users_backup LIKE users;

复制结构和数据需要再执行:

INSERT INTO users_backup
SELECT * FROM users;

CREATE TABLE ... AS SELECT ... 虽然方便,但不会完整复制原表的主键、索引、外键和自增等定义:

CREATE TABLE active_users AS
SELECT id, username, email
FROM users
WHERE status = 1;

7.3 清空表

TRUNCATE TABLE users_backup;

TRUNCATE

7.4 删除表

DROP TABLE IF EXISTS users_backup;

8. DELETETRUNCATEDROP 对比

命令 删除内容 支持 WHERE 保留表结构 自增值通常重置
DELETE 数据行
TRUNCATE 全部数据
DROP TABLE 数据和表结构 表已不存在

9. DDL 执行前检查清单

10. 一页式 DDL 模板

-- 创建数据库
CREATE DATABASE IF NOT EXISTS shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

-- 创建表
CREATE TABLE example (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
  status TINYINT UNSIGNED NOT NULL DEFAULT 1,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_example_name (name),
  KEY idx_example_status_created_at (status, created_at)
) ENGINE = InnoDB
  DEFAULT CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

-- 查看结构
SHOW CREATE TABLE example;
DESCRIBE example;
SHOW INDEX FROM example;

-- 添加、修改、重命名、删除字段
ALTER TABLE example ADD COLUMN remark VARCHAR(255) NULL;
ALTER TABLE example MODIFY COLUMN remark TEXT NULL;
ALTER TABLE example RENAME COLUMN remark TO description;
ALTER TABLE example DROP COLUMN description;

-- 索引
CREATE INDEX idx_example_created_at ON example (created_at);
DROP INDEX idx_example_created_at ON example;

-- 重命名、清空、删除表
RENAME TABLE example TO examples;
TRUNCATE TABLE examples;
DROP TABLE IF EXISTS examples;