一、核心基础
1. 数据库 & 表 是什么?
- MySQL 是关系型数据库 :数据都存在「表」里,表是行 + 列的二维结构(Excel 表格一模一样)
- 一个数据库(database)里可以有多个表(table),表和表之间可以有关联关系
- 核心元素:列 = 字段 (比如姓名、年龄、手机号),行 = 记录(比如一条学生信息)
2. 数据类型
- 数值型:
int整数(年龄、id、数量)、double小数(价格、分数) - 字符串:
varchar(长度)可变字符串(姓名、手机号、地址,比如varchar(20)、varchar(50)),最常用 - 日期型:
datetime年月日时分秒(比如注册时间、订单时间),date只存年月日 - 特殊:主键字段一般用
int即可,搭配自增
3. 约束
约束 = 给字段加的「规则」,保证数据的正确性,建表时必须写的内容,核心 5 个:
primary key (主键):唯一标识一行数据 ,一张表只能有 1 个主键 ,主键的值非空 + 唯一(比如学生 id、用户 id)not null (非空):字段值不能为空(比如姓名、手机号必须填)unique (唯一):字段值不能重复(比如手机号、邮箱,允许为空)default (默认值):字段没赋值时,用默认值(比如default 0、default '未知')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 NULL、IS 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 后面只能写「分组字段」和「聚合函数」,其他字段不能写
四、多表查询
前提:多表之间必须有关联字段 (比如学生表
student有class_id,班级表class有id,通过这两个字段关联)
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 必须带约束,按重要性排序:
primary key(主键):唯一标识一行数据,一张表只能有 1 个主键 ,值「非空 + 唯一」,搭配int使用最多not null(非空):字段值不能为空,如姓名、手机号unique(唯一):字段值不能重复,允许为空,如手机号、邮箱auto_increment(自增):搭配主键int使用,主键值自动从 1 开始递增,插入时主键写null即可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. 字段类型「隐式转换」
例:phone是varchar(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 有两个核心存储引擎:
InnoDB和MyISAM; 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:多表关联视图(简化复杂查询)
需求:创建「用户 + 订单」视图,关联 user 和 order 表,直接查看用户的订单信息。
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. 更新视图(插入 / 修改 / 删除数据)
视图本身不存储数据,但满足条件时 ,可以通过视图更新原表数据。
允许更新视图的条件
- 视图的查询语句中没有聚合函数 (
COUNT/SUM/AVG等); - 没有
GROUP BY/DISTINCT/UNION等关键字; - 视图基于单表创建,且包含原表的主键字段。
案例:通过视图修改原表数据
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;
三、视图的优缺点
优点
- 简化复杂查询:封装多表关联、聚合逻辑,调用方无需关注底层实现;
- 数据安全:隐藏敏感字段,只开放必要数据,降低数据泄露风险;
- 解耦代码与表结构:原表结构变更时,只需修改视图逻辑,外部代码无需改动;
- 清晰的业务逻辑:视图可以按业务模块划分(如订单视图、用户视图),更符合业务思维。
缺点
- 性能问题 :视图查询时,会实时执行底层
SELECT语句,如果底层查询复杂(多表关联 + 聚合),视图查询性能会变差; - 更新限制多:只有满足特定条件的视图才能更新,复杂视图(多表关联、带聚合)无法更新;
- 增加维护成本:视图过多时,会增加数据库的管理成本,需要同步维护视图和原表的关系。
四、视图的使用场景
-
适合用视图的场景
- 复杂查询的封装(比如报表统计的多表关联 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 的顺序 ≠ 执行顺序,记住核心的即可:WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
3. 注释写法
sql
-- 单行注释(两个减号+空格,最常用)
/* 多行注释 */