MySQL 初级篇全攻略:从零到掌握核心操作

目标:学完本教程,你将具备独立完成单库增删改查、设计规范表结构、管理用户权限、理解事务与索引雏形的能力。
适合读者:完全零基础,或刚接触 SQL 希望系统巩固的开发者。
全文示例:基于 MySQL 8.0,所有代码均可直接运行。


第一章:走进 MySQL —— 发展历程、安装配置与用户权限管理

1.1 MySQL 发展历程(为什么它如此流行)

  • 1995 年:MySQL AB 公司推出 MySQL 1.0,最初以快速、轻量著称。
  • 2000 年:发布 3.23 版本,引入 MyISAM 存储引擎,支持全文索引。
  • 2001 年:4.0 版本加入 InnoDB 存储引擎(当时作为第三方插件),支持事务和行级锁。
  • 2005 年:5.0 版本加入存储过程、触发器、视图、游标等企业级特性。
  • 2008 年:Sun 公司收购 MySQL AB。
  • 2010 年:Oracle 收购 Sun,自此 MySQL 归属 Oracle 管理。
  • 2013 年:5.6 版本大幅改进 InnoDB 性能,支持全文搜索和在线 DDL。
  • 2018 年:8.0 版本成为里程碑:移除查询缓存(弊大于利)、支持窗口函数、CTE、角色管理、原子 DDL 等。

核心版本选择建议

  • 生产环境首选 MySQL 8.0(性能、安全、功能全面领先)。
  • 遗留系统可能仍用 5.7(2023 年已结束生命周期,建议升级)。

1.2 MySQL 安装与配置(Windows / macOS / Linux)

1.2.1 Windows 安装

  1. 下载 MySQL Installer for Windows(从 dev.mysql.com 选择 mysql-installer-web-community)。
  2. 运行安装程序,选择 Developer Default(包含 MySQL Server、Workbench、Shell 等)。
  3. 设置 root 密码(务必记住)。
  4. 配置 MySQL 为 Windows 服务(默认开机自启)。

环境变量(使命令行能直接调用 mysql):

  • 右键“此电脑” → 属性 → 高级系统设置 → 环境变量 → 系统变量 Path → 添加 C:\Program Files\MySQL\MySQL Server 8.0\bin

1.2.2 macOS 安装

  • Homebrew 方式(推荐):
    brew install mysql
    brew services start mysql # 启动服务
    mysql_secure_installation # 安全配置(设置 root 密码、移除匿名用户等)
  • DMG 安装包:从官网下载 .dmg 双击安装。

1.2.3 Linux(Ubuntu)安装

sudo apt update
sudo apt install mysql-server
sudo mysql_secure_installation # 设置密码策略、 root 密码等
sudo systemctl status mysql # 检查运行状态

验证安装

mysql -u root -p
Enter password: ********
mysql> SELECT VERSION();
+-----------+
| 8.0.36 |
+-----------+

1.3 客户端工具选型

工具 特点 优点 缺点
MySQL Workbench 官方免费,跨平台 功能全面(ER 建模、迁移、管理)、支持可视化执行计划 较臃肿,偶尔卡顿
TablePlus 轻量、现代 UI(macOS / Windows) 支持多数据库、快捷键丰富、启动快 免费版有会话限制(但够用)
DBeaver 开源,基于 Eclipse 社区版免费、跨平台、支持几乎所有数据库 初次配置稍复杂
DataGrip JetBrains 出品 智能提示极强、代码格式化优秀 收费(学生可免费)

入门推荐:先使用 MySQL Workbench,熟悉后再尝试 TablePlus 或 DataGrip。

1.4 用户与权限管理(安全基石)

MySQL 的用户信息存储在 mysql.user 表中。用户由 用户名 + 主机名 唯一标识(例如 'alice'@'localhost''alice'@'%' 是两个不同用户)。

1.4.1 创建用户

-- 基础语法:CREATE USER 'username'@'host' IDENTIFIED BY 'password';

-- 允许本地登录
CREATE USER 'alice'@'localhost' IDENTIFIED BY 'Alice123!';

-- 允许任意 IP 登录(开发环境慎用)
CREATE USER 'bob'@'%' IDENTIFIED BY 'Bob456!';

-- 允许指定网段(192.168.1.%)
CREATE USER 'carol'@'192.168.1.%' IDENTIFIED BY 'Carol789!';

-- 创建用户时指定密码过期(强制首次登录修改)
CREATE USER 'dave'@'localhost' IDENTIFIED BY 'Dave123!' PASSWORD EXPIRE;

-- 若用户已存在,使用 IF NOT EXISTS(MySQL 8.0+)
CREATE USER IF NOT EXISTS 'alice'@'localhost' IDENTIFIED BY 'NewPass!';

1.4.2 查看用户

-- 列出所有用户
SELECT User, Host, authentication_string FROM mysql.user;

-- 查看当前登录用户
SELECT CURRENT_USER();

1.4.3 修改密码

-- 方法1:ALTER USER(推荐,MySQL 8.0+)
ALTER USER 'alice'@'localhost' IDENTIFIED BY 'NewStrongPass123!';

-- 方法2:SET PASSWORD(已废弃,但仍可用)
SET PASSWORD FOR 'bob'@'%' = 'BobNewPass!';

-- 修改当前登录用户自己的密码
ALTER USER USER() IDENTIFIED BY 'MyOwnNewPass!';

1.4.4 删除用户

DROP USER 'alice'@'localhost';
DROP USER 'bob'@'%';
-- 防错写法
DROP USER IF EXISTS 'carol'@'192.168.1.%';

1.4.5 权限授予(GRANT)

MySQL 权限分为多个层级:

  • 全局权限*.*):影响所有数据库。
  • 数据库级权限db_name.*):影响指定数据库的所有对象。
  • 表级权限db_name.table_name):影响指定表。
  • 列级权限db_name.table_name(column)):影响指定列(较少用)。
  • 存储过程/函数权限

常用权限列表:

  • ALL PRIVILEGES:除 GRANT OPTION 外的所有权限(不代表超级用户权限)。
  • SELECTINSERTUPDATEDELETECREATEDROPINDEXALTERCREATE VIEWEXECUTE 等。
-- 基本语法:GRANT privilege1, privilege2 ON object TO 'user'@'host' WITH GRANT OPTION;

-- 授予 alice 对 myblog 库所有表的全部权限(但无法再授权给别人)
GRANT ALL PRIVILEGES ON myblog.* TO 'alice'@'localhost';

-- 授予 bob 只读权限(全局)
GRANT SELECT ON *.* TO 'bob'@'%';

-- 授予 carol 对 employees 表的 INSERT 和 UPDATE 权限
GRANT INSERT, UPDATE ON company.employees TO 'carol'@'192.168.1.%';

-- 允许用户将自己拥有的权限授予他人(WITH GRANT OPTION)
GRANT SELECT ON myblog.* TO 'dave'@'localhost' WITH GRANT OPTION;

-- 创建角色(MySQL 8.0+)并授予角色权限
CREATE ROLE 'app_readonly';
GRANT SELECT ON myblog.* TO 'app_readonly';
GRANT 'app_readonly' TO 'alice'@'localhost';

1.4.6 查看权限

-- 查看当前用户的权限
SHOW GRANTS;

-- 查看指定用户的权限
SHOW GRANTS FOR 'alice'@'localhost';

1.4.7 撤销权限(REVOKE)

-- 撤销 alice 对 myblog 库的所有权限
REVOKE ALL PRIVILEGES ON myblog.* FROM 'alice'@'localhost';

-- 撤销 bob 的全局 SELECT 权限
REVOKE SELECT ON *.* FROM 'bob'@'%';

-- 注意:REVOKE 不会自动删除用户,用户仍可登录。

1.4.8 权限生效时机

  • 使用 GRANTREVOKE 后,执行 FLUSH PRIVILEGES; 强制重新加载权限表(通常已自动生效)。
  • 用户重新登录后,新权限才会完全应用。

第二章:数据定义语言(DDL)基石 —— 数据类型与约束精讲

在创建表之前,必须深刻理解每列可用的数据类型和约束规则。

2.1 数值类型(整数、定点数、浮点数)

类型 字节数 范围(有符号) 范围(无符号 UNSIGNED) 典型用途
TINYINT 1 -128 ~ 127 0 ~ 255 年龄、状态(0/1)
SMALLINT 2 -32,768 ~ 32,767 0 ~ 65,535 小范围计数(例如商品库存)
MEDIUMINT 3 -8,388,608 ~ 8,388,607 0 ~ 16,777,215 中等数量(博客阅读量)
INT 4 -2.1e9 ~ 2.1e9 0 ~ 4.2e9 用户ID、订单号
BIGINT 8 -9.2e18 ~ 9.2e18 0 ~ 1.8e19 超大流水号、时间戳(毫秒)
DECIMAL(M,D) 变长 取决于 M 同上 金额、精确小数( M 总位数,D 小数位数)
FLOAT 4 ±1.2e-38 ~ ±3.4e38 同上 科学计算,不推荐金额
DOUBLE 8 精度更高 同上 需要高精度浮点运算

选择建议

  • 整型:够用就好,例如年龄用 TINYINT UNSIGNED,主键用 INTBIGINT
  • 小数金额:必须用 DECIMAL,例如 DECIMAL(10,2)(总长 10 位,小数点后 2 位)。
-- 示例
CREATE TABLE products (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
price DECIMAL(10,2) NOT NULL, -- 价格精确到分
stock SMALLINT UNSIGNED DEFAULT 0
);

2.2 字符串类型

类型 最大长度 说明 存储特点 场景
CHAR(n) 255 字符 定长字符串 总是占用 n 个字符,尾部空格填充 固定长度代码(手机号、身份证号、MD5)
VARCHAR(n) 65,535 字节(受行大小限制) 变长字符串 实际长度 + 1~2 字节 名字、标题、地址
TINYTEXT 255 字节 短文本 变长 很短的说明
TEXT 65,535 字节(约 64KB) 普通文本 变长,有额外指针 文章摘要、评论
MEDIUMTEXT 16,777,215 字节(16MB) 中等长度文本 同上 新闻正文
LONGTEXT 4GB 超大文本 同上 日志、长文档
BINARY(n) 255 字节 定长二进制 填充 \0 哈希值、UUID(不区分大小写场景)
VARBINARY(n) 65,535 字节 变长二进制 实际 + 长度字节 加密数据、图片指纹
ENUM 最多 65,535 个元素 枚举 存储索引(1~2 字节) 性别、状态(固定几个选项)
SET 最多 64 个元素 集合 位图存储(1/2/3/4/8 字节) 多项选择(爱好、权限组合)

重点对比

  • CHAR vs VARCHARCHAR(10)'abc' 占 10 字节;VARCHAR(10)'abc' 占 4 字节(3+1)。查询时 CHAR 会去掉尾部空格。
  • TEXT vs VARCHAR:TEXT 不能有默认值(MySQL 8.0 之前),且索引时需指定前缀长度;VARCHAR 上限受行格式影响(默认 64KB)。
  • ENUM 陷阱:不建议用 ENUM 存储动态值(修改枚举需 ALTER TABLE),且排序按索引值而非字符串。
-- 示例
CREATE TABLE users (
username VARCHAR(50) NOT NULL,
gender ENUM('male','female','other') DEFAULT 'other',
hobbies SET('reading','music','sports')
);

2.3 日期时间类型

类型 字节 范围 格式 自动更新 场景
DATE 3 ‘1000-01-01’ ~ ‘9999-12-31’ YYYY-MM-DD 生日、入职日期
TIME 3 ‘-838:59:59’ ~ ‘838:59:59’ HH:MM:SS 时间段、持续时间
DATETIME 5(8.0 之前)→ 8 字节 ‘1000-01-01 00:00:00’ ~ ‘9999-12-31 23:59:59’ YYYY-MM-DD HH:MM:SS 可设置 创建时间(不受时区影响)
TIMESTAMP 4 ‘1970-01-01 00:00:01’ UTC ~ ‘2038-01-19 03:14:07’ UTC YYYY-MM-DD HH:MM:SS 自动(第一列 TIMESTAMP 默认) 最后修改时间(受时区影响)
YEAR 1 1901 ~ 2155 YYYY 酒年份、车型年份

重要区别

  • DATETIME 存储字面值,与时区无关;TIMESTAMP 存储 UTC 值,查询时转为会话时区。
  • 超过 2038-01-19 的数据不能用 TIMESTAMP
  • DATETIME 在 MySQL 8.0 中支持 DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP
CREATE TABLE posts (
id INT PRIMARY KEY,
title VARCHAR(200),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

2.4 约束类型(保证数据完整性)

约束是施加在表列上的规则,防止无效数据进入数据库。

约束 作用 语法 特点
NOT NULL 禁止空值 col_name type NOT NULL 确保列总有值
UNIQUE 值唯一(可多个 NULL) UNIQUE (col1, col2) 可组合唯一键
PRIMARY KEY 唯一标识一行(自动 NOT NULL + UNIQUE) PRIMARY KEY (col)id INT PRIMARY KEY 每表最多一个
FOREIGN KEY 引用另一表的主键/唯一键 FOREIGN KEY (col) REFERENCES other(col) 保证引用完整性
CHECK 自定义条件 CHECK (age >= 0) 行级校验(MySQL 8.0 开始充分支持)
DEFAULT 默认值 status VARCHAR(10) DEFAULT 'active' 未指定值时使用

2.4.1 各约束详细用法

NOT NULL

CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL, -- 必须提供姓名
email VARCHAR(100) -- 可以为 NULL
);

UNIQUE(单列/复合):

CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) UNIQUE, -- 不允许重复用户名
email VARCHAR(100),
CONSTRAINT uc_email UNIQUE (email) -- 命名约束
);

-- 复合唯一:一个用户对一本书只能评价一次
CREATE TABLE reviews (
user_id INT,
book_id INT,
rating TINYINT,
UNIQUE KEY uk_user_book (user_id, book_id)
);

PRIMARY KEY

-- 方式1:列级
CREATE TABLE orders (order_id INT PRIMARY KEY, ...);

-- 方式2:表级(用于复合主键)
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);

FOREIGN KEY(外键约束):

CREATE TABLE categories (
id INT PRIMARY KEY,
name VARCHAR(50)
);

CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
cat_id INT,
FOREIGN KEY (cat_id) REFERENCES categories(id)
ON DELETE RESTRICT -- 禁止删除被引用的分类
ON UPDATE CASCADE -- 分类 ID 更新时,自动更新产品的 cat_id
);

外键级联操作选项:

  • CASCADE:父表删除/更新时,子表自动删除/更新。
  • SET NULL:父表删除/更新时,子表外键列设为 NULL。
  • RESTRICT(默认):阻止父表删除/更新被引用的行。
  • NO ACTION:与 RESTRICT 相同(MySQL 里)。

CHECK(MySQL 8.0.16+ 完整支持):

CREATE TABLE employees (
id INT PRIMARY KEY,
age TINYINT,
salary DECIMAL(10,2),
CONSTRAINT chk_age CHECK (age >= 18),
CONSTRAINT chk_salary CHECK (salary > 0)
);

DEFAULT

CREATE TABLE logs (
id INT PRIMARY KEY,
log_time DATETIME DEFAULT CURRENT_TIMESTAMP,
level VARCHAR(10) DEFAULT 'INFO'
);

2.5 库与表的详细操作(DDL)

2.5.1 数据库操作

-- 创建数据库(指定字符集和排序规则)
CREATE DATABASE myblog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 查看所有数据库
SHOW DATABASES;

-- 切换数据库
USE myblog;

-- 修改数据库字符集
ALTER DATABASE myblog CHARACTER SET utf8mb4;

-- 删除数据库(谨慎!)
DROP DATABASE myblog;
DROP DATABASE IF EXISTS myblog;

2.5.2 创建表(完整语法)

CREATE TABLE [IF NOT EXISTS] table_name (
column1 data_type [NOT NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE] [PRIMARY KEY] [COMMENT '注释'],
column2 ...,
[CONSTRAINT constraint_name] PRIMARY KEY (col1, col2),
[CONSTRAINT constraint_name] FOREIGN KEY (col) REFERENCES other(col),
[CONSTRAINT constraint_name] CHECK (condition),
INDEX index_name (col),
UNIQUE INDEX unique_name (col)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

示例

CREATE TABLE IF NOT EXISTS posts (
id INT UNSIGNED AUTO_INCREMENT COMMENT '主键ID',
title VARCHAR(200) NOT NULL COMMENT '标题',
content TEXT COMMENT '内容',
author_id INT UNSIGNED NOT NULL,
view_count INT UNSIGNED DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
INDEX idx_author (author_id),
FULLTEXT KEY ft_title (title) -- 全文索引(MyISAM 或 InnoDB 5.6+)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

2.5.3 修改表(ALTER TABLE)

-- 添加列
ALTER TABLE posts ADD COLUMN is_published BOOLEAN DEFAULT FALSE;

-- 添加列在特定位置
ALTER TABLE posts ADD COLUMN summary VARCHAR(500) AFTER title;

-- 修改列的数据类型或属性
ALTER TABLE posts MODIFY COLUMN content MEDIUMTEXT;

-- 重命名列
ALTER TABLE posts CHANGE COLUMN view_count views INT UNSIGNED DEFAULT 0;

-- 删除列
ALTER TABLE posts DROP COLUMN summary;

-- 添加索引
ALTER TABLE posts ADD INDEX idx_created (created_at);

-- 删除索引
ALTER TABLE posts DROP INDEX idx_author;

-- 添加外键
ALTER TABLE posts ADD CONSTRAINT fk_author FOREIGN KEY (author_id) REFERENCES users(id);

-- 删除外键
ALTER TABLE posts DROP FOREIGN KEY fk_author;

-- 重命名表
RENAME TABLE posts TO articles;
-- 或者
ALTER TABLE articles RENAME TO posts;

2.5.4 删除表与清空表

-- 删除表(结构和数据全部丢失)
DROP TABLE posts;
DROP TABLE IF EXISTS posts;

-- 清空表(保留结构,数据不可回滚)
TRUNCATE TABLE posts; -- 相当于 DELETE 无 WHERE + 重置自增

-- 对比 DELETE
DELETE FROM posts; -- 逐行删除,可回滚,自增值不重置

第三章:数据操作语言(DML)—— 增删改查全面进阶

3.1 INSERT 的七种写法

3.1.1 标准 INSERT(指定列)

INSERT INTO users (username, age, email) VALUES ('alice', 25, '[email protected]');

3.1.2 省略列名(必须按顺序提供所有非自增/非默认列)

INSERT INTO users VALUES (DEFAULT, 'bob', 30, '[email protected]'); -- id 自增

3.1.3 批量插入(效率最高)

INSERT INTO users (username, age) VALUES 
('carol', 28),
('dave', 32),
('eve', 27);

3.1.4 INSERT … SET 语法(适用于部分列)

INSERT INTO users SET username = 'frank', age = 29, email = '[email protected]';

3.1.5 INSERT … SELECT(从其他表复制数据)

INSERT INTO archive_users (username, age) 
SELECT username, age FROM users WHERE age > 30;

3.1.6 INSERT IGNORE(忽略重复键错误)

-- 若 username 有唯一索引,重复时忽略插入,不报错
INSERT IGNORE INTO users (username, age) VALUES ('alice', 26);

3.1.7 ON DUPLICATE KEY UPDATE(重复时更新)

-- 若 username 冲突,则更新 age 为 26
INSERT INTO users (username, age) VALUES ('alice', 26)
ON DUPLICATE KEY UPDATE age = VALUES(age);

3.2 UPDATE 的多种用法

3.2.1 单表更新(带 WHERE)

UPDATE users SET age = 26 WHERE username = 'alice';

3.2.2 多列同时更新

UPDATE users SET age = age + 1, email = CONCAT(username, '@new.com') WHERE id = 1;

3.2.3 带 LIMIT 限制更新行数

UPDATE users SET status = 'inactive' WHERE last_login < '2023-01-01' LIMIT 100;

3.2.4 多表关联更新(更新一个表基于另一个表的值)

-- 将 orders 表中的订单状态更新为对应支付记录的状态
UPDATE orders o
JOIN payments p ON o.payment_id = p.id
SET o.status = p.status
WHERE p.paid_at > '2024-01-01';

3.2.5 使用子查询更新

UPDATE products SET price = price * 1.1 
WHERE category_id = (SELECT id FROM categories WHERE name = 'Electronics');

3.2.6 ORDER BY 配合 UPDATE(按顺序更新)

-- 将年龄最小的 3 个用户标记为 young
UPDATE users SET tag = 'young' ORDER BY age ASC LIMIT 3;

3.3 DELETE 与 TRUNCATE 对比

3.3.1 单表删除(带 WHERE)

DELETE FROM users WHERE age > 60;

3.3.2 带 LIMIT 限制删除行数

DELETE FROM logs WHERE created_at < '2023-01-01' LIMIT 1000;

3.3.3 多表删除(删除一个或多个表的数据)

-- 删除没有订单的用户(从 users 表删除)
DELETE u FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL;

-- 同时删除用户及其订单中的记录(两个表都要删)
DELETE u, o FROM users u JOIN orders o ON u.id = o.user_id WHERE u.inactive = 1;

3.3.4 使用子查询删除

DELETE FROM products WHERE category_id IN (SELECT id FROM categories WHERE disabled = 1);

3.3.5 TRUNCATE 的深入认识

TRUNCATE TABLE temp_table;
-- 等价于:
-- 1. 删除原表并重建(自动递增计数器重置)
-- 2. 不可回滚(DDL 语句)
-- 3. 不触发 DELETE 触发器
-- 4. 不记录单行删除日志,速度极快

对比表

特性 DELETE TRUNCATE
条件过滤 (WHERE) ✅ 支持 ❌ 不支持
事务回滚 ✅ 支持(InnoDB) ❌ 不支持(DDL)
触发器触发 ✅ 行级删除触发器 ❌ 不触发
自增计数器 保持当前值 重置为初始值
磁盘空间释放 不释放(仅标记为可复用) 释放并回收到系统
速度(百万行) 慢(秒~分钟) 极快(毫秒级)

第四章:数据查询语言(DQL)—— 筛选、排序与去重

4.1 WHERE 子句详解(支持所有运算符)

4.1.1 比较运算符

SELECT * FROM users WHERE age >= 18;
SELECT * FROM users WHERE username = 'alice';
SELECT * FROM users WHERE age <> 25; -- 不等于

4.1.2 逻辑运算符(AND / OR / NOT)

SELECT * FROM users WHERE age BETWEEN 20 AND 30 AND city = 'Beijing';
SELECT * FROM users WHERE age < 18 OR age > 60;
SELECT * FROM users WHERE NOT (status = 'banned');

4.1.3 BETWEEN(包含边界)

SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';

4.1.4 IN(多个值匹配)

SELECT * FROM users WHERE id IN (1, 3, 5, 7);
SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE active = 1);

4.1.5 LIKE 与通配符(% 任意多个,_ 单个)

SELECT * FROM users WHERE username LIKE 'a%';     -- 以 a 开头
SELECT * FROM users WHERE username LIKE '%son'; -- 以 son 结尾
SELECT * FROM users WHERE username LIKE '_a%'; -- 第二个字符是 a
-- 转义通配符:使用 ESCAPE
SELECT * FROM files WHERE filename LIKE '100\%%' ESCAPE '\'; -- 查找以 100% 开头的

4.1.6 REGEXP / RLIKE(正则表达式,MySQL 8.0 更强大)

SELECT * FROM users WHERE username REGEXP '^[a-z]{3}$';  -- 三个小写字母
SELECT * FROM products WHERE name REGEXP 'iPhone|iPad';

4.1.7 NULL 值处理(必须用 IS NULL / IS NOT NULL)

SELECT * FROM users WHERE email IS NULL;
SELECT * FROM users WHERE email IS NOT NULL;
-- 错误写法:WHERE email = NULL(永远返回空)

4.2 ORDER BY 排序(单列、多列、表达式)

4.2.1 基本排序

SELECT * FROM products ORDER BY price DESC;           -- 降序
SELECT * FROM products ORDER BY price ASC, name ASC; -- 多列

4.2.2 按表达式排序

-- 按打折后的价格排序
SELECT name, price, price * 0.8 AS discounted FROM products ORDER BY discounted DESC;

4.2.3 按函数结果排序

SELECT username, created_at FROM users ORDER BY YEAR(created_at), MONTH(created_at);

4.2.4 NULL 值的排序位置(MySQL 默认 NULL 为最小值)

-- 将 NULL 排到最后
SELECT * FROM users ORDER BY email IS NULL, email;
-- 或者使用 IFNULL
SELECT * FROM users ORDER BY IFNULL(email, 'zzzz');

4.3 DISTINCT 去重

4.3.1 单列去重

SELECT DISTINCT city FROM users;

4.3.2 多列组合去重(所有列组合完全相同时去重)

SELECT DISTINCT city, age FROM users;

4.3.3 DISTINCT 与 GROUP BY 的区别

  • DISTINCT 是去重,不包含聚合。
  • GROUP BY 用于分组聚合(后续会讲)。
-- 去重年龄(等价于下面的 GROUP BY)
SELECT DISTINCT age FROM users;
SELECT age FROM users GROUP BY age; -- 结果相同,但 GROUP BY 通常用于 COUNT 等

4.3.4 性能注意点

DISTINCT 会隐式排序(除非与 ORDER BY NULL 一起用),对大数据量较慢。若只判断是否存在,用 EXISTS 替代。


第五章:聚合统计与分组

5.1 聚合函数(COUNT, SUM, AVG, MAX, MIN)

5.1.1 COUNT 的三种形式

-- COUNT(*) : 统计行数(包括 NULL)
SELECT COUNT(*) FROM users;

-- COUNT(列) : 统计该列非 NULL 的行数
SELECT COUNT(email) FROM users;

-- COUNT(DISTINCT 列) : 统计唯一非 NULL 值的数量
SELECT COUNT(DISTINCT city) FROM users;

5.1.2 SUM 和 AVG(忽略 NULL)

SELECT SUM(amount), AVG(amount) FROM orders;
-- 注意:AVG 不包含 NULL 行,若希望 NULL 作为 0 参与计算,需用 IFNULL
SELECT AVG(IFNULL(amount, 0)) FROM orders;

5.1.3 MAX 和 MIN

SELECT MAX(price), MIN(price) FROM products;

5.1.4 组合使用

SELECT 
COUNT(*) AS total_users,
AVG(age) AS avg_age,
MAX(age) AS oldest,
MIN(age) AS youngest
FROM users;

5.2 GROUP BY 分组

5.2.1 单列分组

SELECT city, COUNT(*) AS user_count FROM users GROUP BY city;

5.2.2 多列分组

SELECT city, age, COUNT(*) FROM users GROUP BY city, age;

5.2.3 GROUP BY 与聚合函数

-- 每个城市的用户平均年龄
SELECT city, AVG(age) FROM users GROUP BY city;

5.2.4 常见错误:SELECT 中出现了非分组列且非聚合

-- 错误:username 不在 GROUP BY 中,且不是聚合函数
SELECT city, username, COUNT(*) FROM users GROUP BY city;

5.2.5 GROUP_CONCAT(将分组内的值连接成字符串)

SELECT city, GROUP_CONCAT(username ORDER BY username SEPARATOR ', ') AS users_list
FROM users GROUP BY city;

5.2.6 WITH ROLLUP(额外增加一行汇总)

SELECT city, SUM(amount) FROM orders GROUP BY city WITH ROLLUP;
-- 结果中会多一行 city = NULL, 表示所有城市的总和

5.3 HAVING 与 WHERE 的区别

  • WHERE:在分组前过滤行,不能使用聚合函数。
  • HAVING:在分组后过滤组,可以使用聚合函数。
-- 查找平均年龄大于 30 的城市
SELECT city, AVG(age) AS avg_age
FROM users
GROUP BY city
HAVING AVG(age) > 30;

-- WHERE 和 HAVING 并用(先过滤再分组再筛选组)
SELECT city, AVG(age) AS avg_age
FROM users
WHERE status = 'active' -- 过滤掉非活跃用户
GROUP BY city
HAVING AVG(age) > 25;

第六章:多表查询 —— 连接与子查询

6.1 内连接(INNER JOIN)

返回两表完全匹配的行。

-- 标准语法
SELECT u.username, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- 多表连接
SELECT u.username, p.name, oi.quantity
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id;

-- 自连接(同一张表连接自己,比如查找员工和上级)
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

6.2 外连接(LEFT / RIGHT / FULL)

-- 左连接:保留左表所有行,右表无匹配则为 NULL
SELECT u.username, o.order_id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

-- 右连接:保留右表所有行(可用 LEFT 改写)
SELECT u.username, o.order_id
FROM orders o
RIGHT JOIN users u ON u.id = o.user_id;

-- FULL OUTER JOIN(MySQL 不直接支持,可通过 UNION 模拟)
SELECT u.name, o.order_id FROM users u LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT u.name, o.order_id FROM users u RIGHT JOIN orders o ON u.id = o.user_id;

6.3 交叉连接(CROSS JOIN)—— 笛卡尔积

-- 两种写法
SELECT * FROM colors CROSS JOIN sizes;
SELECT * FROM colors, sizes; -- 逗号隐式交叉连接
-- 结果:3种颜色 * 3种尺寸 = 9行

6.4 UNION 与 UNION ALL

-- 合并两个查询结果,自动去重(有排序开销)
SELECT name FROM employees
UNION
SELECT name FROM customers;

-- 不去重,效率更高
SELECT name FROM employees
UNION ALL
SELECT name FROM customers;

注意:两个结果集的列数必须相同,对应列数据类型应兼容。

6.5 子查询

6.5.1 标量子查询(返回单个值)

-- 查询年龄大于平均年龄的用户
SELECT username, age FROM users
WHERE age > (SELECT AVG(age) FROM users);

6.5.2 行子查询(返回一行多列)

-- 查询与 alice 同城市且同龄的用户
SELECT * FROM users
WHERE (city, age) = (SELECT city, age FROM users WHERE username = 'alice');

6.5.3 列子查询(返回一列多个值)

-- 查询在订单表中出现过的用户
SELECT username FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);

6.5.4 表子查询(返回多行多列,用于 FROM 子句)

-- 派生表(必须给别名)
SELECT dept_id, avg_salary
FROM (SELECT dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY dept_id) AS dept_avg
WHERE avg_salary > 50000;

6.5.5 相关子查询(引用外层查询的列)

-- 查询每个分类中价格高于该分类平均价格的产品
SELECT p1.name, p1.price, p1.category_id
FROM products p1
WHERE p1.price > (SELECT AVG(p2.price) FROM products p2 WHERE p2.category_id = p1.category_id);

6.5.6 EXISTS / NOT EXISTS(效率通常优于 IN)

-- 查询下过订单的用户
SELECT username FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

-- 查询从未下过订单的用户
SELECT username FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

第七章:事务与索引入门

7.1 事务 ACID 特性与操作

ACID

  • 原子性:事务内操作要么全做,要么全不做。
  • 一致性:事务前后数据总满足约束(如账户总额不变)。
  • 隔离性:并发事务互不干扰。
  • 持久性:提交后数据永久保存(即使断电)。

7.1.1 事务控制语句

-- 显式开启事务
START TRANSACTION;
-- 或 BEGIN;

-- 执行一系列 DML
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;

-- 提交(永久生效)
COMMIT;

-- 回滚(撤销当前事务所有修改)
ROLLBACK;

7.1.2 保存点(SAVEPOINT)

START TRANSACTION;
INSERT INTO logs (msg) VALUES ('step1');
SAVEPOINT sp1;
INSERT INTO logs (msg) VALUES ('step2');
-- 发现 step2 错误,回滚到 sp1
ROLLBACK TO SAVEPOINT sp1;
COMMIT; -- 只提交了 step1 的记录

7.1.3 自动提交模式

-- 查看当前自动提交状态(默认 ON)
SHOW VARIABLES LIKE 'autocommit';

-- 关闭自动提交(当前会话生效)
SET autocommit = 0;
-- 此时每条 DML 不会自动提交,需显式 COMMIT

7.2 索引概念与初步理解

7.2.1 索引的类比

  • 索引就像书的目录:没有目录要翻遍全书(全表扫描);有目录可以快速定位页码(数据行)。
  • 代价:占用额外磁盘,写操作(INSERT/UPDATE/DELETE)变慢(因为要更新索引)。

7.2.2 主键索引与二级索引

  • 主键索引(聚簇索引):叶子节点直接存储整行数据。表必须有且仅有一个。
  • 二级索引(辅助索引):叶子节点存储主键值。查询时需回表(先找到主键,再用主键去聚簇索引找行)。

7.2.3 覆盖索引

如果二级索引已经包含了查询所需要的所有列,则无需回表,直接返回索引中的值,性能极高。

-- 假设在 username 列上建有索引
-- 覆盖索引示例(索引包含了 username 和 age?需建复合索引)
CREATE INDEX idx_user_age ON users(username, age);
-- 查询不需要回表,因为 age 也在索引中
SELECT username, age FROM users WHERE username = 'alice';

7.2.4 创建和删除索引

-- 创建普通索引
CREATE INDEX idx_email ON users(email);

-- 创建唯一索引
CREATE UNIQUE INDEX idx_username ON users(username);

-- 删除索引
DROP INDEX idx_email ON users;

-- 查看表的索引
SHOW INDEX FROM users;

7.2.5 索引使用原则(进阶篇会深入)

  • 对 WHERE、ORDER BY、GROUP BY 中的列创建索引。
  • 避免在索引列上使用函数或运算(会导致索引失效)。
  • 区分度高的列优先索引(如身份证号 > 性别)。

总结与下期预告

恭喜你完成了 MySQL 初级篇的超级扩充版!现在你掌握了:

  • ✅ MySQL 发展历程与安装配置
  • ✅ 用户创建、权限授予与撤销(安全基石)
  • ✅ 所有数据类型(数值、字符串、日期时间)和六大约束
  • ✅ DDL 库表创建、修改、删除的几十种用法
  • ✅ DML 增删改查的七种 INSERT、多表 UPDATE、多表 DELETE
  • ✅ DQL 中 WHERE、ORDER BY、DISTINCT 的详细运算符
  • ✅ GROUP BY 聚合与 HAVING 的精确使用
  • ✅ 多表连接(内外连接、自连接、UNION)和子查询(标量、行、列、相关)
  • ✅ 事务 ACID 和索引基本概念

下一阶段(进阶篇) 我们将深入:

  • B+ 树索引原理与执行计划(Explain)
  • 索引优化实战(最左前缀、索引失效场景)
  • 事务隔离级别(脏读、幻读、MVCC)
  • 锁机制(行锁、间隙锁、死锁分析)
  • 存储过程、触发器、视图的高级应用

练习建议:自己搭建一个 blog 系统数据库,包含用户、文章、评论、标签表,完成至少 10 个不同难度的查询(如:按评论数排序的热门文章、用户权限分级)。并在每张表上合理添加索引与约束。只有亲手敲过,知识才是你的。

保持好奇,进阶篇见!