MySQL 学过但是全忘了?15min帮你快速复习

一、核心基础

1. 数据库 & 表 是什么?
  • MySQL 是关系型数据库 :数据都存在「表」里,表是行 + 列的二维结构(Excel 表格一模一样)
  • 一个数据库(database)里可以有多个表(table),表和表之间可以有关联关系
  • 核心元素:列 = 字段 (比如姓名、年龄、手机号),行 = 记录(比如一条学生信息)
2. 数据类型
  • 数值型:int 整数(年龄、id、数量)、double 小数(价格、分数)
  • 字符串:varchar(长度) 可变字符串(姓名、手机号、地址,比如varchar(20)varchar(50)),最常用
  • 日期型:datetime 年月日时分秒(比如注册时间、订单时间),date 只存年月日
  • 特殊:主键字段一般用 int 即可,搭配自增
3. 约束

约束 = 给字段加的「规则」,保证数据的正确性,建表时必须写的内容,核心 5 个:

  1. primary key (主键)唯一标识一行数据 ,一张表只能有 1 个主键 ,主键的值非空 + 唯一(比如学生 id、用户 id)
  2. not null (非空):字段值不能为空(比如姓名、手机号必须填)
  3. unique (唯一):字段值不能重复(比如手机号、邮箱,允许为空)
  4. default (默认值):字段没赋值时,用默认值(比如default 0default '未知'
  5. auto_increment主键自增 ,搭配int主键使用,插入数据时不用手动填主键值,会自动 + 1(比如 id 从 1、2、3... 一直自增,最常用)
4. SQL 语句的 3 大分类

DDL 数据定义语言 :操作「库、表」的结构(创建 / 删除 / 修改库、表),不操作表里的数据

DML 数据操作语言:操作「表中的数据」(增、删、改),重中之重

DQL 数据查询语言 :查询表中的数据(查),MySQL 用的最多的语法,占 80%,核心中的核心

DCL 数据 控制语言 :管理数据库的用户、权限

二、核心语法

语法规则:MySQL 中 关键字大小写不敏感 (比如 SELECT=select,CREATE=create),但建议关键字大写、表名 / 字段名小写 ,可读性高;语句必须以分号; 结尾!

第一类:DDL 数据定义语言(操作库、表)
1. 操作「数据库」
sql 复制代码
-- 1. 创建数据库 (判断不存在再创建,避免报错,必加)
CREATE DATABASE IF NOT EXISTS 数据库名;

-- 2. 删除数据库 (谨慎用!删了就没了)
DROP DATABASE IF EXISTS 数据库名;

-- 3. 使用/切换数据库 (重中之重!执行所有表操作前,必须先执行这句)
USE 数据库名;
2. 操作「数据表」

(1)创建表

sql 复制代码
CREATE TABLE IF NOT EXISTS 表名(
    字段名1 数据类型 约束1 约束2,
    字段名2 数据类型 约束,
    字段名3 数据类型 约束,
    ...
);

示例(学生表,最经典)

sql 复制代码
CREATE TABLE IF NOT EXISTS student(
    id INT PRIMARY KEY AUTO_INCREMENT, -- 主键+自增,核心写法
    name VARCHAR(20) NOT NULL,        -- 姓名非空
    age INT DEFAULT 0,                -- 年龄默认0
    phone VARCHAR(11) UNIQUE,         -- 手机号唯一
    create_time DATETIME              -- 注册时间
);

(2)删除表

sql 复制代码
DROP TABLE IF EXISTS 表名;

(3)查看表结构

sql 复制代码
DESC 表名;  -- 简写,最常用
-- 或者完整写法
DESCRIBE 表名;
第二类:DML 数据操作语言

只操作数据,不改变表结构,表里的内容增、删、改.

注意:所有字符串 / 日期类型的值 ,必须用 单引号 '' 包裹!数值类型不用!

1. 新增数据(INSERT 插入)
sql 复制代码
-- 写法1:指定字段插入(推荐,灵活,字段顺序可换)
INSERT INTO 表名(字段1,字段2,字段3) VALUES(值1,值2,值3);

-- 写法2:不指定字段,按表的字段顺序全部插入(字段少的时候用)
INSERT INTO 表名 VALUES(值1,值2,值3,...);

-- 示例
INSERT INTO student(name,age,phone) VALUES('张三',20,'13800138000');
INSERT INTO student VALUES(null,'李四',21,'13900139000',NOW()); -- id自增,传null即可,NOW()是当前时间
2. 修改数据(UPDATE 更新)
sql 复制代码
-- 语法
UPDATE 表名 SET 字段1=新值, 字段2=新值 WHERE 条件;

-- 示例:把id=1的学生年龄改成22
UPDATE student SET age=22 WHERE id=1;

UPDATE语句千万不能省略 WHERE 条件 !省略后,表里所有行的这个字段都会被修改!(比如把所有学生的年龄都改成 22)

3. 删除数据(DELETE 删除)
sql 复制代码
-- 语法
DELETE FROM 表名 WHERE 条件;

-- 示例:删除id=2的学生记录
DELETE FROM student WHERE id=2;

省略 WHERE 条件,会删除表里的所有数据!不可逆!

第三类:DQL 数据查询语言

这是 MySQL 最核心的能力,所有开发中用的最多的就是查询.

核心原则:查询不会修改任何数据,放心写,查错了也没事

基础查询
sql 复制代码
-- 1. 查询表中「全部字段」(*代表所有,测试/简单场景用)
SELECT * FROM 表名;

-- 2. 查询表中「指定字段」(推荐,效率高,字段名用逗号分隔)
SELECT 字段1,字段2,字段3 FROM 表名;

-- 示例
SELECT * FROM student; -- 查所有学生的所有信息
SELECT name,age FROM student; -- 只查学生的姓名和年龄
条件查询

根据条件筛选数据,语法:SELECT 字段 FROM 表名 WHERE 条件

条件运算符

  • 等于:= (MySQL 中没有==,只有=
  • 不等于:!=<>
  • 大于 / 小于:><>=<=
  • 区间:BETWEEN 最小值 AND 最大值(包含边界,比如年龄 18-25)
  • 枚举:IN (值1,值2,值3)(比如 id 是 1、3、5 的学生)
  • 非空 / 空:IS NOT NULLIS NULL注意:null 值不能用 = 判断,必须用 IS

逻辑运算符

  • 并且:AND (所有条件都满足)
  • 或者:OR (满足任意一个条件)
  • 取反:NOT (不满足条件)

示例

sql 复制代码
-- 查年龄大于20的学生
SELECT * FROM student WHERE age>20;
-- 查年龄在18到25之间的学生
SELECT * FROM student WHERE age BETWEEN 18 AND 25;
-- 查姓名是张三或李四的学生
SELECT * FROM student WHERE name='张三' OR name='李四';
-- 查手机号不为空、年龄>=20的学生
SELECT * FROM student WHERE phone IS NOT NULL AND age>=20;
排序查询

语法:SELECT 字段 FROM 表名 [WHERE条件] ORDER BY 排序字段 排序规则

  • 排序规则:ASC 升序(从小到大,默认值,可省略),DESC 降序(从大到小)
  • 支持多字段排序:先按第一个字段排,第一个字段相同则按第二个字段排
sql 复制代码
-- 查所有学生,按年龄降序排列(年龄大的在前)
SELECT * FROM student ORDER BY age DESC;
-- 查年龄>=18的学生,先按年龄升序,再按id降序
SELECT * FROM student WHERE age>=18 ORDER BY age ASC, id DESC;
模糊查询

语法:SELECT 字段 FROM 表名 WHERE 字段 LIKE '匹配规则'

  • %:匹配任意 0 / 多个字符(如%张%:包含张,138%:以 138 开头)
  • _:匹配1 个 任意字符(如__:匹配两个字的姓名)
分页查询

当表中数据很多时,只查指定行数的数据(比如一页显示 10 条),语法:SELECT 字段 FROM 表名 LIMIT 起始索引,每页条数

  • 核心规则:起始索引从 0 开始!(第一页的索引是 0,第二页是 10,以此类推)
  • 简化写法:LIMIT n → 等价于 LIMIT 0, n (查前 n 条数据)
sql 复制代码
-- 查前5条学生数据(简化写法)
SELECT * FROM student LIMIT 5;
-- 查第6-10条数据(起始索引5,查5条),也就是第二页
SELECT * FROM student LIMIT 5,5;
去重查询

查询结果中,去掉重复的字段值,语法:SELECT DISTINCT 字段 FROM 表名

sql 复制代码
-- 查所有学生的年龄,去掉重复的年龄(比如只看有哪些年龄)
SELECT DISTINCT age FROM student;
别名

给「字段名」或「表名」起别名,简化 SQL 语句,AS 可以省略,直接空格写别名即可

sql 复制代码
-- 给字段起别名:姓名→name,年龄→age_num
SELECT name AS 姓名, age 年龄_num FROM student;
子查询

嵌套在 SELECT/INSERT/UPDATE/DELETE 中的查询语句,分为标量子查询 (返回单个值)、列子查询 (返回一列值)、行子查询(返回一行值)。

sql 复制代码
-- 标量子查询:查年龄大于平均年龄的学生
SELECT * FROM student WHERE age > (SELECT AVG(age) FROM student);
-- 列子查询:返回一列值,搭配IN使用(如查属于一班、二班的学生)
SELECT * FROM student WHERE class_id IN (SELECT id FROM class WHERE name IN('一班','二班'));
EXISTS 关键字

判断子查询是否有结果,返回 true/false,适合大表查询 (比 IN 效率高)。

UNION/UNION ALL

合并多个 SELECT 结果集,UNION 去重,UNION ALL 不去重(效率更高)。

第四类:DCL 数据控制语言

作用:管理数据库的用户、权限,语法不用手写,记概念就行

核心关键字:CREATE USER(创建用户)、GRANT(授权)、REVOKE(回收权限)、FLUSH PRIVILEGES(刷新权限)

考点:数据库最小权限原则 → 给业务账号只分配需要的权限(如 SELECT/INSERT),不分配 DROP/DELETE 等高权限。

三、聚合函数 + 分组查询

1. 聚合函数

聚合函数 = 对一列数据 进行「统计计算」,返回一个结果值,比如求总数、平均值、最大值。

  • COUNT(字段/*):统计行数(比如统计学生总数)
  • SUM(字段):求和(比如统计所有学生的总分)
  • AVG(字段):求平均值(比如统计学生的平均年龄)
  • MAX(字段):求最大值(比如最大年龄)
  • MIN(字段):求最小值(比如最小年龄)

小技巧:统计总行数,用 COUNT(*) 最方便,不受字段是否为空的影响

sql 复制代码
-- 统计学生总数
SELECT COUNT(*) FROM student;
-- 统计所有学生的平均年龄
SELECT AVG(age) FROM student;
-- 统计年龄>=20的学生的最大年龄和最小年龄
SELECT MAX(age), MIN(age) FROM student WHERE age>=20;
2. 分组查询(GROUP BY + HAVING)

作用 :把数据按「某个字段分组」,然后对每组进行聚合统计(比如按性别分组,统计男生 / 女生的人数)

  • 语法:SELECT 分组字段, 聚合函数 FROM 表名 [WHERE条件] GROUP BY 分组字段 [HAVING 分组后的条件]
  • 核心区别:① 执行时机不同:WHERE分组前 筛选数据,HAVING分组后 筛选分组结果;② 作用对象不同:WHERE筛选行,HAVING筛选分组;③ 语法限制不同:WHERE不能使用聚合函数,HAVING可以使用聚合函数(如 HAVING COUNT (*)>=2)。
sql 复制代码
-- 按性别分组,统计每组的人数(假设表中有gender字段:男/女)
SELECT gender, COUNT(*) FROM student GROUP BY gender;
-- 按性别分组,统计每组的平均年龄,且只显示平均年龄>=20的组
SELECT gender, AVG(age) FROM student GROUP BY gender HAVING AVG(age)>=20;

分组后,SELECT 后面只能写「分组字段」和「聚合函数」,其他字段不能写

四、多表查询

前提:多表之间必须有关联字段 (比如学生表studentclass_id,班级表classid,通过这两个字段关联)

1. 内连接(INNER JOIN)

作用 :查询「两张表中匹配关联条件」的数据,只查交集(比如查学生 + 对应的班级名称,只查有班级的学生)

sql 复制代码
SELECT 字段 FROM 表1
INNER JOIN 表2
ON 表1.关联字段 = 表2.关联字段;

简写:INNER JOIN 可以直接写 JOIN,效果一样

2. 左连接(LEFT JOIN)

作用 :查询「左表的所有数据」,即使右表没有匹配的关联数据,也会显示左表数据,右表字段值为null(比如查所有学生 + 对应的班级名称,没有班级的学生也会显示)

sql 复制代码
SELECT 字段 FROM 表1
LEFT JOIN 表2
ON 表1.关联字段 = 表2.关联字段;

示例:

sql 复制代码
-- 查学生姓名、年龄、对应的班级名称(学生表student,班级表class)
SELECT s.name, s.age, c.name FROM student s
LEFT JOIN class c
ON s.class_id = c.id;

小技巧:多表查询时,给表起别名(s、c),能极大简化 SQL 书写!

3. 右连接(RIGHT JOIN)
  • 作用:和左连接相反,查询右表的所有数据 ,左表无匹配的字段值补 null
  • 语法:SELECT 字段 FROM 表1 RIGHT JOIN 表2 ON 表1.关联字段 = 表2.关联字段;
4. 全连接(FULL JOIN)
  • 作用:查询两张表的所有数据 ,左右表无匹配的字段都补 null
  • 注意:MySQL 不直接支持 FULL JOIN,需用 UNION 联合左连接和右连接实现。
5. 交叉连接(CROSS JOIN)

作用:两张表做笛卡尔积(表 1 的每一行和表 2 的每一行组合),一般很少直接用,需搭配条件过滤。

五、约束

约束 = 给字段加的规则,保证数据正确性,创建表的核心内容,面试手写建表 SQL 必须带约束,按重要性排序:

  1. primary key(主键):唯一标识一行数据,一张表只能有 1 个主键 ,值「非空 + 唯一」,搭配int使用最多
  2. not null(非空):字段值不能为空,如姓名、手机号
  3. unique(唯一):字段值不能重复,允许为空,如手机号、邮箱
  4. auto_increment(自增):搭配主键int使用,主键值自动从 1 开始递增,插入时主键写null即可
  5. default(默认值):字段未赋值时用默认值,如age INT DEFAULT 0
sql 复制代码
CREATE TABLE student(
    id INT PRIMARY KEY AUTO_INCREMENT, -- 主键+自增 核心组合
    name VARCHAR(20) NOT NULL,         -- 姓名非空
    age INT DEFAULT 0,                 -- 年龄默认0
    phone VARCHAR(11) UNIQUE,          -- 手机号唯一
    create_time DATETIME
);
1. 外键约束(FOREIGN KEY)
  • 作用:保证两张表的关联关系 (比如学生表的 class_id 必须是班级表的 id 存在的值),实现数据的参照完整性
  • 语法:建表时 FOREIGN KEY (子表字段) REFERENCES 父表(父表主键) [ON DELETE 动作] [ON UPDATE 动作]
  • 注意:MySQL 中只有 InnoDB 引擎支持外键,且会一定程度影响性能,高性能场景(如你接触的 muduo 服务器)有时会舍弃外键,改由业务代码保证关联完整性。
2. 检查约束(CHECK)
  • 作用:限制字段值的范围(比如年龄必须大于 0)。
  • 注意:MySQL 5.x 版本 CHECK语法支持但不生效,MySQL 8.0 才真正支持。

六、索引

1. 索引的【核心定义】

索引 :是 MySQL 在数据表上建立的一种 特殊的、排好序的快速查找数据结构 (MySQL 底层默认是 B+树 结构),它不改变原表数据,只是给表的字段建立一个「目录」。

核心作用:避免全表扫描,把查询效率从「毫秒 / 秒级」提升到「微秒级」,比如百万级数据,无索引查询要 1 秒,有索引查询仅需 0.001 秒。

核心代价:空间换时间 ------ ① 索引会额外占用磁盘空间;② 对表执行 INSERT/UPDATE/DELETE 时,MySQL 不仅要修改数据,还要维护索引的排序结构,会降低「增删改」的执行效率。

2. 索引的本质 & 底层结构

MySQL 的索引底层默认是 B + 树,不用二叉树 / 红黑树的原因:

  • 二叉树:数据量大时会退化成链表,查询效率暴跌;
  • 红黑树:树的高度过高,磁盘 IO 次数多(数据库数据存在磁盘,IO 是性能瓶颈);
  • B + 树:层数少、高度低,所有数据都存在叶子节点,叶子节点之间是双向链表,既适合精准查询,也适合范围查询,磁盘 IO 最少,完美适配数据库查询场景。
3. 索引的分类
1. 主键索引 (PRIMARY KEY)
  • 特点:一张表只能有 1 个主键索引自动创建 (给字段加primary key约束时,MySQL 自动创建);
  • 核心规则:主键索引的字段值,必须满足 非空 + 唯一,绝对不能重复、不能为空;
  • 常用搭配:主键字段一般是int类型,搭配auto_increment自增,比如id INT PRIMARY KEY AUTO_INCREMENT,这是开发 / 面试的标配写法;
  • 核心作用:唯一标识一行数据,查询效率最高,因为是数据库的核心索引。
sql 复制代码
-- 建表时写了 id INT PRIMARY KEY AUTO_INCREMENT,就自动创建了主键索引
-- 直接用即可,无需手动创建
SELECT * FROM user WHERE id = 2; -- 自动走主键索引,速度极快
2. 唯一索引 (UNIQUE)
  • 特点:一张表可以有多个唯一索引自动创建 (给字段加unique约束时,MySQL 自动创建);
  • 核心规则:字段值 必须唯一 ,但允许为 NULL(和主键的核心区别);
  • 适用场景:手机号、邮箱、身份证号这类「不能重复,但可能为空」的字段,比如phone VARCHAR(11) UNIQUE
sql 复制代码
-- 建表时写了 phone VARCHAR(11) UNIQUE NOT NULL,自动创建唯一索引
SELECT * FROM user WHERE phone = '13800138000'; -- 自动走唯一索引

主键索引 vs 唯一索引的区别

① 数量:主键索引一张表只能有 1 个,唯一索引可以有多个;

② 空值:主键索引字段不允许 NULL ,唯一索引字段允许 NULL

③ 约束:主键是数据库层面的核心约束,唯一索引只是字段唯一性约束。

3. 普通索引 (INDEX)
  • 特点:一张表可以有多个普通索引手动创建(不会自动生成,必须写 SQL 创建),无任何约束;
  • 核心规则:字段值可以重复、可以为空,没有任何限制;
  • 适用场景:最常用的索引类型,所有「查询频繁、更新较少」的字段都适合建普通索引,比如查询学生姓名、商品名称等;
sql 复制代码
-- 创建普通索引
CREATE INDEX 索引名 ON 表名(字段名);
-- 示例:给student表的name字段建普通索引
CREATE INDEX idx_student_name ON student(name);
-- 删除索引
DROP INDEX 索引名 ON 表名;
DROP INDEX idx_student_name ON student;

-- 需求:经常根据用户名查询用户信息,给 username 字段创建普通索引
CREATE INDEX idx_user_username ON user(username);

-- 创建后,直接查询,MySQL自动使用该索引
SELECT * FROM user WHERE username = '张三'; -- 走普通索引,查询提速
4. 复合索引 (联合索引)
  • 定义:基于多个字段联合创建 的索引,比如给student表的name+age字段创建复合索引,是面试最高频考点,没有之一;

  • 核心规则:一张表可以有多个复合索引,手动创建,无约束;

sql 复制代码
-- 创建复合索引:字段顺序非常重要!!!
CREATE INDEX 索引名 ON 表名(字段1,字段2,字段3);
-- 示例:给student表创建 name+age 的复合索引
CREATE INDEX idx_name_age ON student(name,age);

-- 场景1:查询条件包含【最左字段username】→ 索引生效(推荐写法)
SELECT * FROM user WHERE username = '张三';
SELECT * FROM user WHERE username = '张三' AND age = 22;

-- 场景2:跳过最左字段,只查age → 索引失效(经典坑)
SELECT * FROM user WHERE age = 22;

-- 场景3:索引字段用函数 → 索引失效
SELECT * FROM user WHERE LEFT(username,1) = '张'; -- 对username做函数处理

-- 场景4:模糊查询以%开头 → 索引失效
SELECT * FROM user WHERE username LIKE '%三';

-- 场景5:模糊查询以字符开头 → 索引生效
SELECT * FROM user WHERE username LIKE '张%';
复合索引的「最左前缀原则」

概念:复合索引的查询条件,必须包含索引的「最左侧第一个字段」 ,索引才会生效;如果跳过最左字段,直接查后面的字段,索引完全失效,会变成全表扫描。

举例:创建了(name,age)的复合索引

  • 生效场景:where name='张三'where name='张三' and age=20 → 包含最左字段 name,索引生效;
  • 失效场景:where age=20 → 跳过了最左字段 name,索引直接失效!

复合索引的字段顺序怎么定?

查询频率高的字段放左边,重复率低的字段放左边

索引的优缺点
  • 核心优点:极大提升查询效率,避免全表扫描,尤其是百万 / 千万级大表,查询速度提升百倍以上;
  • 辅助优点:索引是排好序的,能加速ORDER BY排序查询,不用额外排序。
  • 空间代价:索引是独立的数据结构,会额外占用磁盘空间,索引越多,占用空间越大;
  • 效率代价:对表执行INSERT/UPDATE/DELETE时,MySQL 不仅要修改原表数据,还要维护索引的排序结构 ,会降低增删改的执行效率
索引的【创建原则】

核心原则:合适的字段建索引,不合适的坚决不建

  • 适合建索引:查询频繁、更新较少的字段(比如查询用户昵称、商品分类);
  • 适合建索引:重复率低的字段(比如手机号,重复率 0,建索引效果最好);
  • 不适合建索引:更新频繁的字段(比如订单状态,频繁修改,建索引会拖慢更新速度);
  • 不适合建索引:重复率高的字段(比如性别、状态,只有男 / 女 / 0/1,建索引几乎无效果);
  • 不适合建索引:小表(数据量少于 1 万行,全表扫描比查索引更快,没必要建)。
索引失效的场景
1. WHERE 子句中对索引字段做「函数操作」

例:SELECT * FROM student WHERE DATE(create_time) = '2026-01-01'

原因:MySQL 无法使用索引的排序结构,只能全表扫描。

2. 模糊查询以 % 开头

例:SELECT * FROM student WHERE name LIKE '%张'(失效);SELECT * FROM student WHERE name LIKE '张%'(生效)

  • 原因:%开头表示任意字符在前,索引的排序结构无法匹配,只能全表扫描。
3. 复合索引不遵循「最左前缀原则」

例:索引(name,age),查询where age=20,跳过最左字段 name,索引失效。

4. 字段类型「隐式转换」

例:phonevarchar(11)字符串类型,查询where phone=13800138000(用数值匹配),索引失效;正确写法where phone='13800138000'

原因:MySQL 会自动转换字段类型,导致索引无法匹配。

5. WHERE 子句中用 !=<> 判断索引字段

例:SELECT * FROM student WHERE age != 20,索引失效,全表扫描。

6. WHERE 子句中用 OR 连接非索引字段

例:name是索引字段,address不是,查询where name='张三' OR address='北京',索引失效。

怎么判断 SQL 是否用到了索引 / 验证索引生效?

EXPLAIN 关键字,放在 SQL 前,查看执行计划中的key列,有值表示用到了索引,NULL表示没用到。例:EXPLAIN SELECT * FROM student WHERE name='张三';

sql 复制代码
-- 验证生效:key列显示 idx_user_name_age,说明用到了复合索引
EXPLAIN SELECT * FROM user WHERE username = '张三';

-- 验证失效:key列显示 NULL,说明索引失效,走全表扫描
EXPLAIN SELECT * FROM user WHERE age = 22;
主键为什么推荐用自增 int,而不是 UUID?

自增 int 是有序的,插入时 B + 树不用调整结构,效率高;UUID 是无序的,插入时会频繁调整索引结构,效率低,还占用更多空间。

七、事务

1. 事务的【核心定义】

事务 :是数据库中一组 不可分割的 SQL 操作序列 ,这组操作要么全部执行成功 ,要么全部执行失败回滚,不存在「部分成功、部分失败」的情况。

核心理解:事务就是「要么全做,要么全不做」,是数据库保证数据一致性的核心机制。

经典应用场景:转账业务 (A 账户扣钱,B 账户加钱,必须同时成功 / 失败)、订单提交 (生成订单 + 扣减库存,必须同时成功 / 失败)、用户注册(插入用户 + 初始化积分,必须同时成功 / 失败)。

2. 事务的四大特性 - ACID

事务的四大特性:原子性、一致性、隔离性、持久性 ,简称 ACID

1. 原子性 (Atomicity) - 核心是「不可分割」

定义:事务是一个不可拆分的最小执行单位,事务中的所有 SQL 操作,是一个整体。

效果:事务执行时,要么里面的 SQL 全部执行成功,只要有任何一条 SQL 执行失败,整个事务的所有操作都会被撤销(回滚),数据库回到事务执行前的状态。

2. 一致性 (Consistency) - 核心是「数据完整」

定义:事务执行的前、后 ,数据库中的数据完整性约束保持不变,业务逻辑的规则保持不变。

效果:事务执行后,数据不会出现「逻辑错误」,比如转账的总金额不变、库存扣减后不能为负数、订单金额和商品总价一致。

举例:A 有 1000 元,B 有 500 元,转账 200 元 → 事务执行前总金额 1500,执行后 A800+B700,总金额还是 1500,数据一致。

一致性是事务的最终目标,原子性、隔离性、持久性都是为了保证一致性!

3. 隔离性 (Isolation) - 核心是「互不干扰」

定义:数据库允许多个事务并发执行 (同一时间多个操作),隔离性保证:多个并发执行的事务之间,互相独立、互不干扰,每个事务感觉不到其他事务的存在。

核心问题:如果没有隔离性,多个事务并发执行时,会产生脏读、不可重复读、幻读三大问题(下面详细讲),这也是事务隔离级别的由来。

4. ④ 持久性 (Durability) - 核心是「永久生效」

定义:当事务执行完成并成功提交(COMMIT) 后,事务对数据库的所有修改,会被永久保存到磁盘中,不会因为任何原因(数据库重启、服务器宕机、断电)丢失。

效果:提交后的修改,是不可逆的,数据库怎么重启,数据都是修改后的状态。

补充:如果事务还没提交就宕机,重启后会自动回滚,数据恢复原状,这也是持久性的体现。

原子性 → 保证事务是整体,不拆分;隔离性 → 保证并发事务互不干扰;持久性 → 保证修改永久生效;三者共同保障最终的 一致性

3. 事务的【核心操作语法】

事务的语法超级简单,只有 3 个核心关键字

sql 复制代码
-- 1. 开启事务:执行所有业务SQL前,必须先开启事务
START TRANSACTION;  -- 等价写法:BEGIN;  两个都可以,推荐写START TRANSACTION

-- 2. 执行事务内的业务操作:所有增删改SQL,比如转账的扣钱+加钱
UPDATE user SET money = money - 200 WHERE id=1; -- A扣钱
UPDATE user SET money = money + 200 WHERE id=2; -- B加钱

-- 3. 两种结局:二选一,绝对不会同时执行
COMMIT;     -- 提交事务:所有操作执行成功,修改永久生效(最终状态)
ROLLBACK;   -- 回滚事务:任意操作失败,撤销所有修改,回到事务前状态(补救措施)

-- 查看自动提交状态:ON=开启,OFF=关闭
SELECT @@autocommit;

-- 手动关闭自动提交(全局生效,开发中一般不用)
SET autocommit = OFF;

MySQL 中,默认是「自动提交事务」(autocommit=ON),即每一条 SQL 执行后自动 COMMIT;开启START TRANSACTION后,自动提交会暂时关闭,直到手动 COMMIT/ROLLBACK。

4. 脏读、不可重复读、幻读
1. 脏读 (Dirty Read)

定义:一个事务,读取到了另一个事务「已经修改但还未提交」的数据

核心特点:读到的数据是「临时的、无效的、脏的」,因为另一个事务随时可能回滚,这些数据会消失。

举例:事务 A 修改了 A 的余额为 800,但未提交;事务 B 此时查询到 A 的余额是 800;随后事务 A 执行 ROLLBACK,余额恢复为 1000;事务 B 读到的 800 就是「脏数据」。

2. 不可重复读 (Non-repeatable Read)

定义:同一个事务内多次执行完全相同的查询语句 ,查询到的结果却不一致

核心原因:两次查询之间,有另一个事务修改了该数据并成功提交

举例:事务 A 第一次查询 A 的余额是 1000;事务 B 修改 A 的余额为 800 并提交;事务 A 再次查询,余额变成 800,同一条查询语句,结果不同,就是不可重复读。

3. 幻读 (Phantom Read)

定义:同一个事务内多次执行完全相同的查询语句 ,查询到的结果集条数不一致

核心原因:两次查询之间,有另一个事务插入 / 删除了数据并成功提交

举例:事务 A 查询年龄 > 20 的学生有 5 人;事务 B 插入了 1 个年龄 21 的学生并提交;事务 A 再次查询,结果变成 6 人,就是幻读。

  • 脏读 → 读的是「未提交的脏数据」;
  • 不可重复读 → 同一条数据,内容变了(改 / 删);
  • 幻读 → 同条件查询,条数变了(增 / 删)。
5. 事务的隔离级别
隔离级别的核心作用

为了解决上面的「脏读、不可重复读、幻读」三大问题,MySQL 设计了 4 种事务隔离级别 ,隔离级别从「低」到「高」排序,级别越高,解决的问题越多,但是性能越低(因为限制了并发能力)。

核心规则:隔离级别越高,数据越安全,并发性能越差,这是一个「取舍关系」。

4 种隔离级别
  • 读未提交 (Read Uncommitted) - 最低级别

    • 允许:一个事务读取另一个事务未提交的数据;
    • 存在问题:脏读、不可重复读、幻读 全部存在
    • 特点:性能最高,数据安全性最差,几乎不用
  • 读已提交 (Read Committed) - Oracle 默认级别

    • 解决问题:解决了脏读
    • 存在问题:不可重复读、幻读 依然存在
    • 特点:性能较高,数据安全性一般,是 Oracle、SQL Server 的默认级别。
    • 核心逻辑:只能读到「其他事务已经提交」的数据,未提交的读不到。
  • 可重复读 (Repeatable Read) - MySQL 默认级别

    • 解决问题:解决了脏读、不可重复读
    • 存在问题:理论上存在幻读,MySQL 通过自身机制规避了大部分幻读
    • 特点:性能适中,数据安全性高 ,是 MySQL 的默认隔离级别 ,也是开发中最常用的级别,面试必考,必须记死
    • 核心逻辑:同一个事务内,多次查询的结果完全一致,不受其他事务提交的影响。
  • 串行化 (Serializable) - 最高级别

    • 解决问题:解决了所有问题(脏读、不可重复读、幻读)
    • 核心原理:强制所有事务串行执行(同一时间只能执行一个事务),完全禁止并发;
    • 特点:数据安全性最高 ,但并发性能最差 (几乎无并发),生产环境极少用,只适用于数据一致性要求极高的场景(比如银行对账)。
  • MySQL 的默认事务隔离级别是:可重复读 (Repeatable Read)
  • 该级别解决了:脏读、不可重复读,规避了大部分幻读;
  • 隔离级别从低到高:读未提交 → 读已提交 → 可重复读 → 串行化。
6. MySQL 的存储引擎对事务的支持
  • MySQL 有两个核心存储引擎:InnoDBMyISAM
  • InnoDB支持事务、支持外键、支持行锁,是 MySQL 的默认存储引擎,开发中 100% 使用;
  • MyISAM不支持事务、不支持外键、只支持表锁,性能高,但数据安全性差,仅用于纯查询的场景(比如日志表);
7. 事务的回滚点(SAVEPOINT)

作用:在事务中设置「回滚节点」,可以回滚到指定节点,而不是回滚整个事务,适合复杂业务。

sql 复制代码
START TRANSACTION;
UPDATE user SET money=800 WHERE id=1;
SAVEPOINT sp1; -- 设置回滚点
UPDATE user SET money=700 WHERE id=1;
ROLLBACK TO sp1; -- 回滚到sp1,只撤销第二次修改,第一次修改保留
COMMIT;
8. 为什么生产环境很少用外键?

① 外键会增加数据库的耦合度,不利于分库分表;

② 外键的约束检查会降低增删改的效率;

③ 事务已经能保证数据一致性,配合业务代码校验,完全可以替代外键,既保证数据安全,又提升性能。

九、视图

一、视图的核心概念

1. 定义

视图是一张 虚拟表 ,其数据来源于一条 SELECT 查询语句,这条查询可以关联一张或多张表。

  • 视图不存储任何数据,只存储查询逻辑;
  • 对视图的操作(查询),本质是执行其背后的 SELECT 语句,实时从原表获取数据;
  • 原表数据发生变化时,视图查询结果也会同步更新

2. 核心作用

|----------|-------------------------------------------------------|
| 作用 | 说明 |
| 简化复杂查询 | 将多表关联、聚合等复杂 SQL 封装成视图,后续查询直接用视图,不用重复写复杂语句 |
| 隐藏敏感数据 | 只暴露用户需要的字段,比如用户表隐藏密码、身份证号,只提供 id/username/phone |
| 统一数据访问接口 | 无论原表结构如何变化,只要视图的查询逻辑不变,外部使用视图的代码就无需修改 |

3. 视图 vs 物理表

|------|-------------------------|------------------------------|
| 特性 | 视图 | 物理表 |
| 数据存储 | 不存储数据,只存查询逻辑 | 存储实际数据,占用磁盘空间 |
| 数据更新 | 原表数据变,视图结果同步变 | 需手动执行 INSERT/UPDATE/DELETE |
| 性能 | 查询时实时执行底层 SQL,性能取决于底层查询 | 直接读取数据,性能更高 |
| 使用场景 | 简化查询、权限控制 | 存储原始数据,增删改查的基础 |

二、视图的完整操作
sql 复制代码
-- 先创建订单表(用于多表视图演示)
CREATE TABLE IF NOT EXISTS `order` (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT, -- 关联user表的id
    order_no VARCHAR(30) NOT NULL, -- 订单号
    amount DECIMAL(10,2) NOT NULL, -- 订单金额
    create_time DATETIME DEFAULT NOW(),
    -- 外键关联(演示用,生产环境可省略)
    FOREIGN KEY (user_id) REFERENCES user(id)
);

-- 插入测试订单数据
INSERT INTO `order`(user_id, order_no, amount) VALUES
(1, 'ORDER20260117001', 199.99),
(1, 'ORDER20260117002', 299.99),
(2, 'ORDER20260117003', 99.99);
1. 创建视图
sql 复制代码
CREATE [OR REPLACE] VIEW 视图名 [(视图字段列表)]
AS
SELECT 查询语句
[WITH [CASCADED | LOCAL] CHECK OPTION];
  • OR REPLACE:如果视图已存在,则替换原有视图(避免报错);
  • WITH CHECK OPTION:更新视图时,保证更新的数据符合视图的查询条件(下文演示)。

案例 1:单表视图(隐藏敏感字段)

需求:创建用户视图,只暴露 id/username/phone,隐藏 money/create_time 字段。

sql 复制代码
-- 创建用户视图
CREATE OR REPLACE VIEW v_user AS
SELECT id, username, phone FROM user;

-- 查询视图(和查询普通表完全一样)
SELECT * FROM v_user;

查询结果:只显示 id/username/phone,敏感字段被隐藏

案例 2:多表关联视图(简化复杂查询)

需求:创建「用户 + 订单」视图,关联 userorder 表,直接查看用户的订单信息。

sql 复制代码
-- 创建多表关联视图
CREATE OR REPLACE VIEW v_user_order AS
SELECT 
    u.id AS user_id,
    u.username,
    o.id AS order_id,
    o.order_no,
    o.amount,
    o.create_time
FROM user u
LEFT JOIN `order` o ON u.id = o.user_id;

-- 查询视图(无需再写JOIN语句,直接获取关联数据)
SELECT * FROM v_user_order;

查询结果:直接显示用户和对应的订单信息,简化了多表关联操作

2. 修改视图

方式 1:用 CREATE OR REPLACE 覆盖

sql 复制代码
-- 修改v_user视图,新增age字段
CREATE OR REPLACE VIEW v_user AS
SELECT id, username, phone, age FROM user;

方式 2:用 ALTER VIEW 语句

sql 复制代码
ALTER VIEW v_user AS
SELECT id, username, phone, age, money FROM user;
3. 更新视图(插入 / 修改 / 删除数据)

视图本身不存储数据,但满足条件时 ,可以通过视图更新原表数据

允许更新视图的条件

  1. 视图的查询语句中没有聚合函数COUNT/SUM/AVG 等);
  2. 没有 GROUP BY/DISTINCT/UNION 等关键字;
  3. 视图基于单表创建,且包含原表的主键字段。

案例:通过视图修改原表数据

sql 复制代码
-- 1. 查看v_user视图的原始数据(id=1的用户是张三)
SELECT * FROM v_user WHERE id = 1;

-- 2. 通过视图修改原表的username(张三→张三丰)
UPDATE v_user SET username = '张三丰' WHERE id = 1;

-- 3. 查看原表,数据已同步更新
SELECT * FROM user WHERE id = 1;

WITH CHECK OPTION 约束

作用:通过视图更新数据时,强制要求更新后的数据仍能被视图查询到,避免更新后的数据「脱离视图范围」。

案例演示:

sql 复制代码
-- 创建视图,只包含age≥20的用户,并添加WITH CHECK OPTION
CREATE OR REPLACE VIEW v_user_adult AS
SELECT id, username, age FROM user WHERE age >= 20
WITH CHECK OPTION;

-- 正常更新:把id=1的用户age从22→25(仍≥20,符合条件)
UPDATE v_user_adult SET age = 25 WHERE id = 1;

-- 报错更新:把id=1的用户age从25→18(<20,不符合条件,触发约束)
UPDATE v_user_adult SET age = 18 WHERE id = 1;

报错原因:更新后 age=18 不满足视图的 age≥20 条件,WITH CHECK OPTION 阻止了这次更新。

4. 删除视图

语法:DROP VIEW [IF EXISTS] 视图名1, 视图名2...;

sql 复制代码
-- 删除单个视图
DROP VIEW IF EXISTS v_user;

-- 删除多个视图
DROP VIEW IF EXISTS v_user_order, v_user_adult;
三、视图的优缺点
优点
  1. 简化复杂查询:封装多表关联、聚合逻辑,调用方无需关注底层实现;
  2. 数据安全:隐藏敏感字段,只开放必要数据,降低数据泄露风险;
  3. 解耦代码与表结构:原表结构变更时,只需修改视图逻辑,外部代码无需改动;
  4. 清晰的业务逻辑:视图可以按业务模块划分(如订单视图、用户视图),更符合业务思维。
缺点
  1. 性能问题 :视图查询时,会实时执行底层 SELECT 语句,如果底层查询复杂(多表关联 + 聚合),视图查询性能会变差;
  2. 更新限制多:只有满足特定条件的视图才能更新,复杂视图(多表关联、带聚合)无法更新;
  3. 增加维护成本:视图过多时,会增加数据库的管理成本,需要同步维护视图和原表的关系。
四、视图的使用场景
  • 适合用视图的场景

    • 复杂查询的封装(比如报表统计的多表关联 SQL);
    • 对外提供数据接口时的脱敏处理;
    • 表结构不稳定,但外部查询逻辑需要稳定的场景。
  • 不适合用视图的场景

    • 高频的简单查询(直接查原表性能更高);
    • 需要频繁更新数据的场景(视图更新限制多);
    • 底层查询非常复杂的场景(视图查询性能会很差)。

十、性能优化基础

  • EXPLAIN 关键字 :分析 SQL 语句的执行计划(比如是否走索引、全表扫描、关联方式等),是优化 SQL 的核心工具。
    • 用法:EXPLAIN SELECT * FROM student WHERE age > 20;
  • 慢查询日志:开启后记录执行时间超过阈值的 SQL,用于定位慢查询。
  • 优化原则 :避免 SELECT *、避免 WHERE 子句用函数操作字段(比如 DATE(create_time) = '2024-01-01' 会导致索引失效)、大表分页优化等。

十一、数据库编程

1. 存储过程

封装在数据库中的一组 SQL 语句集合,可以传参,重复调用,减少网络交互(比如批量插入数据)。

sql 复制代码
-- 创建存储过程
CREATE PROCEDURE 过程名(参数列表)
BEGIN
    -- SQL 语句
END;
-- 调用存储过程
CALL 过程名(参数);
2. 函数

和存储过程类似,但必须有返回值 ,可以嵌入到 SELECT 语句中使用。

3. 触发器

当表发生 INSERT/UPDATE/DELETE 操作时,自动触发执行的 SQL 逻辑(比如插入订单后,自动更新商品库存)。

  • 注意:高性能场景下慎用,会增加数据库负担。

十二、数据库管理与运维基础

1. 用户与权限管理

创建用户、授权、回收权限(比如给后端应用账号分配 SELECT/INSERT 权限,不给 DROP 权限)。

sql 复制代码
-- 创建用户
CREATE USER '用户名'@'主机地址' IDENTIFIED BY '密码';
-- 授权(比如给 test 库的 student 表授权查询、插入)
GRANT SELECT,INSERT ON test.student TO '用户名'@'主机地址';
-- 刷新权限
FLUSH PRIVILEGES;
2. 数据备份与恢复
  • 备份:用 mysqldump 工具导出数据库(比如 mysqldump -u root -p school > school_backup.sql)。
  • 恢复:导入备份文件(比如 mysql -u root -p school < school_backup.sql)。
3. 字符集与校对规则

使用 utf8mb4 字符集(支持所有 Unicode 字符,包括 emoji 表情,解决 utf8 不支持部分字符的问题)。

十三、补充知识点

1. 主键自增的补充

主键字段 id INT PRIMARY KEY AUTO_INCREMENT,插入数据时,id 字段可以写null或者不写该字段,MySQL 会自动赋值,从 1 开始,依次 + 1,永不重复。

2. SQL 的执行顺序

写 SQL 的顺序 ≠ 执行顺序,记住核心的即可:WHEREGROUP BYHAVINGORDER BYLIMIT

3. 注释写法
sql 复制代码
-- 单行注释(两个减号+空格,最常用)
/* 多行注释 */
相关推荐
自不量力的A同学10 小时前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
Exquisite.10 小时前
Mysql
数据库·mysql
全栈前端老曹11 小时前
【MongoDB】深入研究副本集与高可用性——Replica Set 架构、故障转移、读写分离
前端·javascript·数据库·mongodb·架构·nosql·副本集
R1nG86311 小时前
CANN资源泄漏检测工具源码深度解读 实战设备内存泄漏排查
数据库·算法·cann
阿钱真强道11 小时前
12 JetLinks MQTT直连设备事件上报实战(继电器场景)
linux·服务器·网络·数据库·网络协议
逍遥德11 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
笨蛋不要掉眼泪11 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
Coder_Boy_12 小时前
基于SpringAI的在线考试系统-整体架构优化设计方案
java·数据库·人工智能·spring boot·架构·ddd
fen_fen20 小时前
Oracle建表语句示例
数据库·oracle
砚边数影1 天前
数据可视化入门:Matplotlib 基础语法与折线图绘制
数据库·信息可视化·matplotlib·数据可视化·kingbase·数据库平替用金仓·金仓数据库