Chapter 02

DDL:创建与管理表结构

数据类型选择、约束设计、ALTER 修改表结构,建立扎实的 Schema 设计能力

2.1 什么是 DDL

DDL(Data Definition Language,数据定义语言) 是用于定义数据库结构的 SQL 语句集合。DDL 操作的对象是"结构"本身,而不是数据内容。

DDL 语句执行后通常会自动提交(Auto-Commit),不需要手动 COMMIT,也无法 ROLLBACK(MySQL 中如此;PostgreSQL 中 DDL 可以在事务中执行并回滚,这是其优势之一)。

语句作用
CREATE创建数据库、表、索引、视图、存储过程等
ALTER修改已有结构(加列、改类型、加约束等)
DROP彻底删除数据库对象(包括数据,不可恢复)
TRUNCATE清空表中所有数据,但保留表结构

2.2 数据库操作

SQL-- 创建数据库(MySQL)
CREATE DATABASE blog_db
  CHARACTER SET utf8mb4        -- 推荐 utf8mb4,完整支持 emoji
  COLLATE utf8mb4_unicode_ci;   -- 排序规则(ci = 不区分大小写)

-- 创建数据库(PostgreSQL)
CREATE DATABASE blog_db
  ENCODING 'UTF8'
  LC_COLLATE 'zh_CN.UTF-8'
  TEMPLATE template0;

-- 选择数据库(MySQL)
USE blog_db;

-- 查看当前使用的数据库
SELECT DATABASE();      -- MySQL
SELECT current_database(); -- PostgreSQL

-- 删除数据库(危险!不可恢复)
DROP DATABASE blog_db;

-- 删除前先检查是否存在(避免报错)
DROP DATABASE IF EXISTS blog_db;

2.3 CREATE TABLE 语法详解

SQLCREATE TABLE 表名 (
  -- 列定义格式:列名  数据类型  [约束...]
  列名  数据类型  NOT NULL,
  列名  数据类型  DEFAULT 默认值,
  列名  数据类型  UNIQUE,

  -- 表级约束(多列约束写在这里)
  PRIMARY KEY (列名),
  FOREIGN KEY (列名) REFERENCES 其他表(列名)
);

2.4 数据类型详解

整数类型

类型字节范围(有符号)范围(无符号)适用场景
TINYINT1-128 ~ 1270 ~ 255状态码、布尔值替代
SMALLINT2-32,768 ~ 32,7670 ~ 65,535年份、小范围编号
INT / INTEGER4-21亿 ~ 21亿0 ~ 42亿最常用的 ID、计数器
BIGINT8-9.2×10¹⁸ ~ 9.2×10¹⁸极大雪花 ID、高并发系统 ID

浮点与精确数值类型

类型精度说明
FLOAT~7 位有效数字单精度浮点,存在精度误差,不适合金融计算
DOUBLE~15 位有效数字双精度浮点,仍有精度问题
DECIMAL(M,D)精确M 总位数,D 小数位数。金融计算必须用此类型
🚨

金融计算必须使用 DECIMAL,绝对不要用 FLOAT/DOUBLE 0.1 + 0.2 在浮点运算中结果是 0.30000000000000004 而非 0.3。存储价格、金额时请用 DECIMAL(10,2)

字符串类型

类型存储方式最大长度适用场景
CHAR(n)定长,不足补空格255 字符MD5 哈希(固定32位)、固定格式编码
VARCHAR(n)变长,实际长度+1~2字节65,535 字节姓名、标题、邮箱(最常用)
TEXT变长,独立存储65,535 字节文章正文、评论内容
MEDIUMTEXT变长16 MB较大文本
LONGTEXT变长4 GB超大文本
JSON验证后存储MySQL 5.7+/PostgreSQL 支持,灵活属性
ℹ️

CHAR vs VARCHAR 选择原则 长度固定(如手机号 11 位、MD5 32 位)用 CHAR,效率稍高;长度不固定(姓名、邮箱)用 VARCHAR,节省空间。现代数据库的 VARCHAR 性能已经很好,绝大多数情况用 VARCHAR 即可。

日期与时间类型

类型格式范围时区说明
DATEYYYY-MM-DD1000-9999年生日、纯日期
TIMEHH:MM:SS-838:59:59 ~ 838:59:59纯时间或时间差
DATETIMEYYYY-MM-DD HH:MM:SS1000-9999年不转换存储为写入时的字面值
TIMESTAMPYYYY-MM-DD HH:MM:SS1970-2038年自动转换UTC创建/更新时间戳(推荐)
💡

created_at 和 updated_at 用什么类型?TIMESTAMP DEFAULT CURRENT_TIMESTAMP 存储记录创建时间;用 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 自动记录最后修改时间。TIMESTAMP 会自动处理时区转换,跨时区部署时更安全。

2.5 约束(Constraints)

约束是数据库在数据写入时自动执行的规则,保证数据的完整性和一致性。

PRIMARY KEY — 主键

主键唯一标识一行数据,隐含了 NOT NULLUNIQUE 两个约束。

SQL-- MySQL:自增主键
CREATE TABLE users (
  id    INT UNSIGNED NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (id)
);

-- PostgreSQL:SERIAL 或 GENERATED AS IDENTITY(推荐)
CREATE TABLE users (
  id    INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);

-- UUID 主键(分布式系统常用,避免 ID 泄露)
CREATE TABLE users (
  id    CHAR(36) DEFAULT (UUID()) PRIMARY KEY  -- MySQL 8.0+
);

NOT NULL、UNIQUE、DEFAULT

SQLCREATE TABLE users (
  id       INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50)  NOT NULL,              -- 不能为空
  email    VARCHAR(255) NOT NULL UNIQUE,       -- 不能为空且唯一
  role     VARCHAR(20)  NOT NULL DEFAULT 'user', -- 默认值 'user'
  is_active TINYINT(1) NOT NULL DEFAULT 1,     -- 布尔,默认激活
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CHECK — 检查约束

SQLCREATE TABLE products (
  id    INT AUTO_INCREMENT PRIMARY KEY,
  name  VARCHAR(100) NOT NULL,
  price DECIMAL(10,2) NOT NULL CHECK (price >= 0), -- 价格不能为负
  stock INT NOT NULL DEFAULT 0 CHECK (stock >= 0),   -- 库存不能为负
  status ENUM('draft', 'published', 'archived') DEFAULT 'draft'
);

-- CHECK 约束在 MySQL 8.0.16+ 才真正强制执行

FOREIGN KEY — 外键约束

外键确保引用完整性:子表(orders)中的 user_id 必须在父表(users)中存在对应的 id。

SQLCREATE TABLE orders (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  user_id    INT NOT NULL,
  total      DECIMAL(10,2) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  -- 外键约束
  CONSTRAINT fk_orders_user
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE    -- 删除用户时,同时删除其所有订单
    ON UPDATE CASCADE    -- 用户 id 修改时,同步更新订单中的 user_id
);
ON DELETE / ON UPDATE 行为说明
CASCADE同步删除/更新子表对应行(谨慎使用删除级联)
SET NULL将子表外键列设为 NULL(外键列需允许 NULL)
RESTRICT如果子表有关联数据,拒绝删除/更新父表记录(默认)
NO ACTION与 RESTRICT 相同(标准 SQL 名称)

2.6 ALTER TABLE 修改表结构

SQL-- 添加列
ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email;

-- 修改列类型
ALTER TABLE users MODIFY COLUMN phone VARCHAR(30);

-- 重命名列(MySQL 8.0+)
ALTER TABLE users RENAME COLUMN phone TO mobile;

-- 删除列
ALTER TABLE users DROP COLUMN mobile;

-- 添加唯一约束
ALTER TABLE users ADD UNIQUE (email);

-- 添加索引
ALTER TABLE users ADD INDEX idx_created_at (created_at);

-- 重命名表
ALTER TABLE users RENAME TO app_users;

-- PostgreSQL 修改列类型(语法稍有不同)
ALTER TABLE users ALTER COLUMN phone TYPE VARCHAR(30);

2.7 DROP TABLE vs TRUNCATE TABLE

操作删除结构删除数据可回滚重置自增
DROP TABLE
TRUNCATE TABLE是(全部)否(MySQL)/ 是(PostgreSQL)
DELETE FROM是(可条件)
🚨

DROP 和 TRUNCATE 都是高危操作 在生产环境执行前,务必先备份!TRUNCATE 比 DELETE 快(不记录每行删除日志),但不可 WHERE 过滤,也不触发行级触发器。

2.8 实战:创建博客系统数据库

综合运用本章所有知识,创建一个包含用户、文章、评论三张表的博客系统:

SQL-- ① 创建数据库
CREATE DATABASE IF NOT EXISTS blog_db
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;
USE blog_db;

-- ② 用户表
CREATE TABLE users (
  id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  username   VARCHAR(50)  NOT NULL UNIQUE,
  email      VARCHAR(255) NOT NULL UNIQUE,
  password_hash CHAR(60)  NOT NULL,          -- bcrypt 哈希固定 60 位
  bio        TEXT,
  avatar_url VARCHAR(512),
  role       ENUM('user', 'admin') NOT NULL DEFAULT 'user',
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
             ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

-- ③ 文章表
CREATE TABLE posts (
  id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  user_id    INT UNSIGNED NOT NULL,
  title      VARCHAR(200) NOT NULL,
  slug       VARCHAR(200) NOT NULL UNIQUE,   -- URL 友好路径,如 hello-world
  content    LONGTEXT NOT NULL,
  status     ENUM('draft', 'published', 'archived')
             NOT NULL DEFAULT 'draft',
  view_count INT UNSIGNED NOT NULL DEFAULT 0,
  published_at TIMESTAMP NULL DEFAULT NULL,   -- 草稿时为 NULL
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
             ON UPDATE CURRENT_TIMESTAMP,

  CONSTRAINT fk_posts_user
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE,

  INDEX idx_posts_user_id (user_id),
  INDEX idx_posts_status_published (status, published_at)
) ENGINE=InnoDB;

-- ④ 评论表
CREATE TABLE comments (
  id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  post_id    INT UNSIGNED NOT NULL,
  user_id    INT UNSIGNED NOT NULL,
  parent_id  INT UNSIGNED NULL DEFAULT NULL,  -- 回复功能:NULL 表示顶级评论
  content    TEXT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  CONSTRAINT fk_comments_post
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
  CONSTRAINT fk_comments_user
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
  CONSTRAINT fk_comments_parent
    FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE,

  INDEX idx_comments_post_id (post_id),
  INDEX idx_comments_user_id (user_id)
) ENGINE=InnoDB;

表关系图(文字描述)

📊

users(1)— 写 →(N)posts:一个用户可发多篇文章
posts(1)— 含 →(N)comments:一篇文章可有多条评论
users(1)— 写 →(N)comments:一个用户可写多条评论
comments(1)— 回复 →(N)comments:评论可嵌套回复(自引用)