MySQL 核心知识完全指南
前言
MySQL 是目前最流行的开源关系型数据库管理系统之一。本文将系统性地梳理MySQL的核心知识,从基础操作到高级概念,帮助读者构建完整的知识体系。
1. 数据库基础操作
1.1 库级操作
库是数据库的顶层容器,管理所有表。
sql
-- 创建数据库
CREATE DATABASE mydb;
-- 查看所有数据库
SHOW DATABASES;
-- 选择数据库
USE mydb;
-- 删除数据库(慎用!)
DROP DATABASE mydb;
1.2 表级操作
表是数据的存储单元,由行和列组成。
常用数据类型
| 分类 | 类型 | 说明 |
|---|---|---|
| 数值 | INT, BIGINT |
整数 |
DECIMAL(M, D) |
精确小数,适合金额 | |
DOUBLE |
浮点数,不精确 | |
| 字符串 | CHAR(N) |
定长,效率高,但浪费空间 |
VARCHAR(N) |
变长,节省空间,最常用 | |
TEXT |
长文本 | |
| 日期时间 | DATE |
仅日期 |
DATETIME |
日期和时间 | |
TIMESTAMP |
时间戳,受时区影响 |
表结构管理
sql
-- 建表
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
salary DECIMAL(10, 2),
hire_date DATE
);
-- 查看表结构
DESC employee;
-- 查看建表语句
SHOW CREATE TABLE employee \G;
-- 修改表结构
ALTER TABLE employee ADD COLUMN email VARCHAR(100); -- 添加列
ALTER TABLE employee MODIFY COLUMN salary DECIMAL(12, 2); -- 修改列类型
ALTER TABLE employee CHANGE COLUMN name username VARCHAR(100); -- 重命名列
ALTER TABLE employee DROP COLUMN email; -- 删除列
-- 重命名表
RENAME TABLE employee TO staff;
-- 清空表(删除所有数据,但保留结构)
TRUNCATE TABLE staff;
-- 删除表
DROP TABLE staff;
2. 数据操作语言 (DML)
2.1 插入数据 (INSERT)
sql
-- 1. 插入完整行(值的顺序必须与表字段顺序一致)
INSERT INTO employee VALUES (1, '张三', 5000.00, '2023-01-01');
-- 2. 插入指定字段
INSERT INTO employee (name, salary) VALUES ('李四', 6000.00);
-- 3. 批量插入
INSERT INTO employee (name, salary) VALUES ('王五', 5500.00), ('赵六', 7000.00);
-- 4. 从其他表插入数据(备份或复制)
CREATE TABLE emp_bak AS SELECT * FROM employee; -- 备份表(结构和数据)
INSERT INTO emp_bak SELECT * FROM employee; -- 向已存在的备份表插入数据
2.2 更新数据 (UPDATE)
sql
-- 基本更新
UPDATE employee SET salary = 8000.00 WHERE name = '张三';
-- 相关更新:将emp_bak的工资更新为原表emp中的工资
UPDATE emp_bak eb
SET eb.salary = (SELECT e.salary FROM employee e WHERE e.id = eb.id);
2.3 删除数据 (DELETE)
sql
-- 删除满足条件的行
DELETE FROM employee WHERE name = '李四';
-- 删除所有行(但速度慢,可回滚)
DELETE FROM employee;
3. 核心查询 (SELECT)
查询是数据库最核心的操作,其执行逻辑可以抽象为以下流程:
原始数据
FROM/JOIN: 确定数据源并关联表
WHERE: 行过滤
GROUP BY: 分组
HAVING: 组过滤
SELECT: 选择列并计算
ORDER BY: 排序
LIMIT: 限制结果
最终结果
3.1 基本查询与过滤
sql
-- 查询特定字段
SELECT name, salary FROM employee;
-- 条件过滤
SELECT * FROM employee WHERE salary > 5000 AND (name LIKE '张%' OR name LIKE '王%');
-- 范围查询
SELECT * FROM employee WHERE id IN (1, 3, 5);
SELECT * FROM employee WHERE salary BETWEEN 5000 AND 8000;
3.2 排序 (ORDER BY)
sql
-- 默认升序(ASC),降序(DESC)
SELECT * FROM employee ORDER BY salary DESC, name ASC;
3.3 聚合函数与分组 (GROUP BY ... HAVING)
聚合函数用于对一组值进行计算,返回单个值。
| 函数 | 作用 |
|---|---|
COUNT() |
计数 |
SUM() |
求和 |
AVG() |
平均值 |
MAX() |
最大值 |
MIN() |
最小值 |
sql
-- 计算每个部门的平均工资
SELECT dept_id, AVG(salary) AS avg_salary
FROM employee
GROUP BY dept_id;
-- 过滤出平均工资大于6000的部门
SELECT dept_id, AVG(salary) AS avg_salary
FROM employee
GROUP BY dept_id
HAVING AVG(salary) > 6000;
4. 约束 (Constraints)
约束是数据库层面保证数据完整性和一致性的规则。
| 约束类型 | 说明 | 示例 |
|---|---|---|
| PRIMARY KEY | 主键,唯一标识一行,非空且唯一。 | id INT PRIMARY KEY |
| FOREIGN KEY | 外键,维护表间关系,确保引用完整性。 | dept_id INT REFERENCES dept(id) |
| UNIQUE | 唯一约束,列值不能重复,但可有多个NULL。 |
email VARCHAR(100) UNIQUE |
| NOT NULL | 非空约束,列值不能为空。 | name VARCHAR(100) NOT NULL |
| DEFAULT | 默认值。 | status INT DEFAULT 1 |
| CHECK | 检查约束,确保列值满足特定条件。 | age INT CHECK (age >= 18) |
关于外键的思考 :虽然外键能保证数据一致性,但在高并发、分布式系统中,它会带来性能瓶颈和维护成本。因此,很多企业会选择在应用层来维护数据关系,而非在数据库层使用外键。
5. 表关系与设计
数据库设计的关键在于识别实体间的关系。
5.1 关系类型及设计模式
一对一
一对多
多对多
多对多
1 1 1 1 1 * * * 用户
+string 用户名
+int 用户ID(PK)
身份证
+string 号码
+int 身份证ID(PK)
+int 用户ID(FK)
部门
+string 部门名
+int 部门ID(PK)
员工
+string 姓名
+int 员工ID(PK)
+int 部门ID(FK)
学生
+string 姓名
+int 学生ID(PK)
课程
+string 课程名
+int 课程ID(PK)
选课记录
+int 学生ID(FK, PK)
+int 课程ID(FK, PK)
一对一 (1:1)
- 设计:主键关联 或 在外键上增加唯一约束。
- 示例:用户表与用户扩展信息表。
一对多 (1:N)
- 设计:在"多"的一方(从表)添加一个外键,指向"一"的一方(主表)的主键。
- 示例:一个部门有多个员工。
多对多 (M:N)
- 设计:创建一张中间表(关联表),包含两个外键,分别指向两张主表的主键。这两个外键通常组成联合主键。
- 示例:学生选课,一个学生可选多门课,一门课可被多个学生选。
自关联
- 应用:用于存储树形或层级结构数据,如菜单、组织架构、行政区划。
- 设计 :表中包含一个指向自身主键的外键(如
parent_id)。
sql
CREATE TABLE category (
id INT PRIMARY KEY,
name VARCHAR(60),
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES category(id)
);
6. 连接查询 (JOIN)
连接查询是将多张表的数据基于关联条件合并到一起。其本质是笛卡尔积加上过滤条件。
内连接
左连接
右连接
表A
笛卡尔积
表B
JOIN 条件
两表都匹配的记录
左表所有记录 + 右表匹配记录
右表所有记录 + 左表匹配记录
6.1 内连接 (INNER JOIN)
只返回两个表中匹配的行。
sql
-- 显示内连接
SELECT e.name, d.name AS dept_name
FROM employee e
INNER JOIN dept d ON e.dept_id = d.id;
-- 隐式内连接(老式写法)
SELECT e.name, d.name
FROM employee e, dept d
WHERE e.dept_id = d.id;
6.2 外连接 (OUTER JOIN)
以一个表为基准,返回其所有行,另一个表不匹配的行用 NULL 填充。
sql
-- 左连接:以左表(employee)为准
SELECT e.name, d.name
FROM employee e
LEFT JOIN dept d ON e.dept_id = d.id;
-- 右连接:以右表(dept)为准
SELECT e.name, d.name
FROM employee e
RIGHT JOIN dept d ON e.dept_id = d.id;
7. 索引 (Index)
索引是提升查询性能的"加速器",其核心原理类似于书的目录。
7.1 索引的数据结构 (B+ Tree)
MySQL InnoDB 引擎使用 B+ Tree 作为索引结构。
特点
- 所有数据都在叶子节点 2. 叶子节点间有双向指针,支持范围查询 3. 树的高度低,IO次数少 B+树结构
根节点: 5, 15
叶子节点: 1,3,5
叶子节点: 7,9,15
数据页/行指针
数据页/行指针
7.2 索引类型
| 类型 | 说明 | 特点 |
|---|---|---|
| 主键索引 | 基于主键自动创建 | 唯一,非空,聚簇索引(数据按主键顺序存放) |
| 唯一索引 | UNIQUE 约束创建 |
唯一,允许 NULL |
| 普通索引 | 手动创建 | 无唯一性要求,用于加速查询 |
| 复合索引 | 多个列的组合 | 遵循最左前缀原则 |
7.3 最左前缀原则
对于复合索引 (a, b, c),查询条件必须包含 a,索引才会生效。例如:
WHERE a = 1✅ 生效WHERE a = 1 AND b = 2✅ 生效WHERE b = 2 AND c = 3❌ 不生效WHERE a = 1 AND c = 3✅ 仅a生效
7.4 索引的使用建议
- 该建索引的列 :
WHERE、ORDER BY、GROUP BY中频繁使用的列。 - 不该建索引的列:数据量小、更新频繁、区分度低的列(如性别)。
- 代价:增、删、改操作变慢,占用额外存储空间。
8. 事务 (Transaction)
事务是保证数据一致性的逻辑单元,确保一组操作要么全部成功,要么全部失败。
8.1 ACID 特性
| 特性 | 描述 |
|---|---|
| 原子性 | 事务中的操作不可分割,要么都做,要么都不做。 |
| 一致性 | 事务执行前后,数据库的完整性约束未被破坏。 |
| 隔离性 | 并发事务之间互不干扰。 |
| 持久性 | 事务提交后,数据修改是永久性的。 |
8.2 事务操作
sql
START TRANSACTION; -- 或 BEGIN
-- 执行一系列SQL
UPDATE account SET money = money - 1000 WHERE id = 1;
UPDATE account SET money = money + 1000 WHERE id = 2;
-- 提交(使变更永久生效)
COMMIT;
-- 回滚(撤销事务中的所有变更)
ROLLBACK;
8.3 并发事务问题与隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 |
|---|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 | 最高 |
| 读已提交 | 避免 | 可能 | 可能 | 高 |
| 可重复读 (MySQL默认) | 避免 | 避免 | 可能 | 中 |
| 串行化 | 避免 | 避免 | 避免 | 最低 |
问题解释:
- 脏读:读到其他事务未提交的数据。
- 不可重复读:同一事务内,两次读取同一数据的结果不同(因其他事务更新并提交)。
- 幻读:同一事务内,两次查询的记录数不同(因其他事务插入并提交)。
9. JDBC 基础
JDBC (Java Database Connectivity) 是 Java 程序访问数据库的标准 API。
9.1 JDBC 与 MySQL 事务
在 Java 代码中,我们可以通过 Connection 对象来控制事务边界。
java
Connection conn = null;
try {
// 1. 加载驱动,获取连接
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
// 2. 开启事务(关闭自动提交)
conn.setAutoCommit(false); // 对应 SQL: START TRANSACTION
// 3. 执行操作
PreparedStatement ps1 = conn.prepareStatement("UPDATE account SET money = money - ? WHERE id = ?");
ps1.setInt(1, 1000);
ps1.setInt(2, 1);
ps1.executeUpdate();
PreparedStatement ps2 = conn.prepareStatement("UPDATE account SET money = money + ? WHERE id = ?");
ps2.setInt(1, 1000);
ps2.setInt(2, 2);
ps2.executeUpdate();
// 4. 提交事务
conn.commit(); // 对应 SQL: COMMIT
System.out.println("转账成功!");
} catch (Exception e) {
// 5. 发生异常,回滚事务
if (conn != null) {
try {
conn.rollback(); // 对应 SQL: ROLLBACK
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 6. 关闭连接等资源
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}