MySQL数据库盲区补充

SoloWalker Lv3

MySQL数据库盲区补充

字符集

字符集的作用和字符编码基本可以当作一回事。

字符集就是一个字典,规定了“A 编号多少?‘中’字编号多少?”比如在 utf8mb4 这本字典里,“中”字的编号是0xE4B8AD。如果甲用 utf8mb4 编码,乙却拿 gbk 字典去解码,就会看到乱码。

utf8 与 utf8mb4

这是 MySQL 历史上最著名的“坑”之一,一定要记住:

MySQL 的 utf8 不是真正的 UTF-8,它最多只支持 3 字节的字符。

真正的 UTF-8 标准字符有 1~4 个字节。所以,MySQL 的 utf8 根本无法存储 emoji 表情、部分罕见的汉字、辅助字符

  • 当你存一个 emoji 表情 😂 时,它会报错或从插入位置被截断,后面的内容一并丢掉。
  • 这也是为什么现在数据库里真正要用 UTF-8 的时候,看到的都是 utf8mb4,它的全称是 UTF-8 Multibyte 4,就是支持 4 字节的完整 UTF-8 实现

字符集的三个层级

MySQL 的字符集设置非常灵活,可以在不同层面生效,从上到下依次是:

  1. 服务器层
    影响创建新库时的默认字符集。

    1
    SHOW VARIABLES LIKE 'character_set_server'
  2. 数据库层
    新建库时若不特别指定,就继承服务器设置。建库时可以明确声明:

    1
    CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  3. 表与列层
    建表时继承库设置,但你也可以为各列设置不同的字符集,这也是最灵活的一层。

    1
    2
    3
    4
    CREATE TABLE users (
    name VARCHAR(50) CHARACTER SET utf8mb4,
    bio TEXT
    ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

列类型

列类型是建表时最重要的设计决策之一,它不仅决定数据怎么存,还直接影响性能、存储空间和查询正确性。

为什么要重视列类型?

  1. 省空间:一个TINYINT占1字节,INT占4字节。亿级数据量时,差出几个G。
  2. 保正确:年龄用INT能存”999岁”,用TINYINT上限127,天然挡掉离谱数据。
  3. 快查询:建索引的列,类型越小,索引文件越小,查询越快。

数值类型

整数

类型 字节 有符号范围 无符号范围
TINYINT 1 -128 ~ 127 0 ~ 255
SMALLINT 2 -32768 ~ 32767 0 ~ 65535
MEDIUMINT 3 -838万 ~ 838万 0 ~ 1677万
INT 4 -21亿 ~ 21亿 0 ~ 42亿
BIGINT 8 -9百京 ~ 9百京 0 ~ 18百京
1
2
3
4
5
-- 年龄不可能为负,用无符号
age TINYINT UNSIGNED

-- 自增主键,通常用 INT 或 BIGINT
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY

小数:FLOAT/DOUBLE vs DECIMAL

  • FLOAT / DOUBLE:近似值,有精度损失,绝对不能用于钱
  • DECIMAL(M, D):定点精确小数,专为金额设计。
1
2
3
4
5
-- 存储金额:总长10位,小数点后2位
price DECIMAL(102)

-- 别这样
price FLOAT -- 0.1 + 0.2 可能不等于 0.3

字符串类型:CHAR vs VARCHAR vs TEXT

这是最容易选错的一类。

类型 特点 适用场景
CHAR(N) 定长。永远占 N 个字符宽,不够用空格补。最大255字符。 长度固定的短字符串:手机号、身份证、MD5哈希值、状态码。
VARCHAR(N) 变长。用多少占多少,额外1-2字节记录长度。最大65535字节(注意是字节,不是字符)。 绝大多数变长字符串:姓名、地址、邮箱、标题。
TEXT 存长文本,最大65535字节。 文章内容、商品描述、JSON字符串。

重点说明 VARCHAR 的两个特点:

  1. N 是字符数,不是字节数VARCHAR(10)utf8mb4 下实际最大占40字节,一个 emoji 就能占4字节。
  2. 行大小限制。MySQL 一行最大 65535 字节,所以 VARCHAR(N) 的 N 不能无限大。超过建议用 TEXT。
1
2
3
4
5
-- 选型示例
mobile CHAR(11) -- 手机号,固定11位
username VARCHAR(50) -- 用户名,变长
password_hash CHAR(32) -- MD5,固定32位
content TEXT -- 文章正文

其他类型不一一列举。

列属性

常用的列属性一览

1. PRIMARY KEY(主键)

  • 唯一标识每一行,不能为空,不能重复,一张表只能有一个。
  • 物理上按主键排序存储,查询最快。
1
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY

2. AUTO_INCREMENT(自增)

  • 整数类型的“自动编号器”,每次插新记录自动+1。
  • 必须配合索引使用(通常是主键)。
1
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY

3. NOT NULL(非空)

  • 该列必须有值,不允许 NULL。
1
name VARCHAR(50) NOT NULL

补充:NULL 是什么意思?
NULL 表示“未知”或“不存在”,它不等于空字符串或数字 0。NULL 参与任何运算,结果都是 NULL(比如 NULL + 10 还是 NULL)。这在写条件查询时容易踩坑。

4. DEFAULT(默认值)

  • 插入时没提供该列的值,就用默认值填充。
1
2
status TINYINT DEFAULT 1
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

5. UNIQUE(唯一约束)

  • 保证该列的值在整个表中不重复,但允许多个 NULL(因为 NULL 不算重复)。
1
email VARCHAR(100) UNIQUE

6. COMMENT(注释)

  • 给列加文字说明,方便后续维护。
1
gender TINYINT COMMENT '0:未知, 1:男, 2:女'

表关系

表关系是关系型数据库的灵魂,它让多张独立的表通过“键”关联成一个整体,既能避免数据冗余,又能保证数据一致性。

三种表关系

一对一关系

定义:A 表中的一条记录,最多对应 B 表中的一条记录,反之亦然。

实现方式:在任意一方的表中,添加外键并设为 UNIQUE。

一对多关系(最常见)

定义:A 表中的一条记录,可以对应 B 表中的多条记录;但 B 的一条记录只能对应 A 的一条记录。

实现方式:在“多”的那一方,添加外键指向“一”的那一方的主键。

多对多关系

定义:A 表中的一条记录,可以对应 B 表中的多条记录;B 的一条记录也可以对应 A 的多条记录。

实现方式:必须引入第三张中间表,至少包含两个外键,分别指向 A 和 B 的主键。

高级数据操作

多表联合查询

数据存在不同表中,但业务往往需要一次查出包含多表字段的结果。联合查询就是”从多张表里一起拿数据”。

JOIN 连接查询

最常用的是 INNER JOIN(内连接),找出两张表里能匹配上的记录

1
2
3
SELECT 列名...
FROM1
INNER JOIN2 ON1.=2.列;

还有左连接和右连接,用来处理可能没有匹配记录的情况:

  • LEFT JOIN:左表所有记录都在,右表没有匹配则填 NULL。例如查所有用户(包括没订单的)的订单数。
  • RIGHT JOIN:和左连接反过来,右表记录全在。实际场景中很少用它(用 LEFT JOIN 调换表顺序就行)。

子查询

子查询就是查询里套查询,把内层查询的结果当作外层查询的条件或临时表。

1. 作为条件值(用在 WHERE 里)

1
2
3
4
-- 查询"张三"所在班级的所有同学
SELECT name FROM students
WHERE class_id = (SELECT class_id FROM students WHERE name = '张三')
AND name != '张三';

内层查询必须只返回单个值(单行单列),用 = 比较才不报错。

2. 配合 IN / EXISTS 操作符

  • IN:检查值是否在子查询返回的一列结果集合中。
  • EXISTS:检查子查询是否有记录存在。通常比 IN 效率更高,尤其是匹配大数据集时。
1
2
3
4
5
6
7
-- IN 示例:查有订单记录的用户
SELECT name FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);

-- EXISTS 示例:查至少下过一单的用户(更高效)
SELECT name FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);

3. 作为临时表(用在 FROM 后面)

把子查询结果直接当一张表参与查询:

1
2
3
4
5
6
7
SELECT avg_age.class_id, avg_age.avg_age
FROM (
SELECT class_id, AVG(age) AS avg_age
FROM students
GROUP BY class_id
HAVING avg_age > 20
) AS avg_age;

聚合统计与分组筛选

数人头、求平均数这类操作就靠聚合函数。

常用聚合函数COUNT, SUM, AVG, MAX, MIN

关键在于分清 WHERE 和 HAVING

  • WHERE:对原始行进行筛选(分组前)。
  • HAVING:对聚合后的结果进行筛选(分组后)。
1
2
3
4
5
6
-- 统计每个班级中年龄大于18岁的学生人数,且只显示人数超过5人的班级
SELECT class_id, COUNT(*) AS student_count
FROM students
WHERE age > 18 -- 先筛掉 18 岁及以下的
GROUP BY class_id -- 按班级分组
HAVING COUNT(*) > 5; -- 只要人数超过 5 人的组

视图基本操作

视图是什么?

  • 不存数据:视图本身不存储实际数据,数据仍然在原始表中。
  • 存的是查询逻辑:每次查视图时,数据库会执行它背后那条 SELECT 语句,实时返回最新结果。
  • 本质:一个虚拟表,给复杂的查询起个名字,简化后续使用。

类比:原表是仓库里的实际货架,视图就像仓库墙上挂的一张“取货清单”,按这张清单去货架上拿东西,清单本身不存货。

创建视图

1
2
CREATE VIEW 视图名 AS
SELECT 语句;

举个例子,假设经常要查“学生姓名 + 班级名称”,每次都要写 JOIN:

1
2
3
4
CREATE VIEW student_class_view AS
SELECT s.id, s.name AS student_name, c.name AS class_name
FROM students s
LEFT JOIN classes c ON s.class_id = c.id;

以后就不用再写 JOIN 了,直接:

1
SELECT * FROM student_class_view;

查看视图

1
2
3
4
5
6
7
8
-- 查看当前库所有视图(和表一起列出来)
SHOW TABLES;

-- 查看视图结构
DESC student_class_view;

-- 查看视图创建语句
SHOW CREATE VIEW student_class_view;

视图和表在 SHOW TABLES 里会一起列出来,可以用 SHOW FULL TABLES WHERE table_type = 'VIEW'; 只列视图。

使用视图

当成普通表来查就行,也支持 WHERE、ORDER BY、GROUP BY:

1
2
3
SELECT * FROM student_class_view
WHERE class_name = '火箭班'
ORDER BY student_name;

视图的查询结果会实时反映原表的数据变化。原表数据改了,视图查出来的结果也跟着变。

修改视图

覆盖式修改(最常用,不存在则创建,存在则替换):

1
2
3
4
CREATE OR REPLACE VIEW student_class_view AS
SELECT s.id, s.name AS student_name, c.name AS class_name
FROM students s
JOIN classes c ON s.class_id = c.id;

直接修改(视图已存在时):

1
2
3
4
ALTER VIEW student_class_view AS
SELECT s.id, s.name, c.name AS class_name
FROM students s
JOIN classes c ON s.class_id = c.id;

删除视图

1
DROP VIEW student_class_view;

可同时删多个:

1
DROP VIEW view1, view2;

注意:有些视图之间可能存在依赖关系。删掉一个被其他视图引用的视图,不会报错,但再查那些依赖它的视图就会失败。

用户权限管理

用户权限管理是数据库安全的核心。MySQL 通过“用户 + 权限”两层机制,精确控制谁能从哪登录、能对哪些库表做什么操作。

权限体系概览

1
用户账号@主机  →  全局/库/表/列级权限  →  具体操作(SELECT, INSERT, DELETE...)
  • 用户:格式为 '用户名'@'主机',表示谁、从哪台机器连进来。
  • 权限:可授予不同级别(全局 → 库 → 表 → 列 → 存储过程),越向下越精细。
  • 生效方式:修改权限后需要 FLUSH PRIVILEGES; 刷新,或重启 MySQL。

查看用户

MySQL 的用户信息存在系统库 mysqluser 表中。

1
2
3
4
5
-- 切换到系统库
USE mysql;

-- 查看所有用户和允许的主机
SELECT user, host FROM user;

host 列表示该账号能从哪台机器连进来:

  • localhost:只能本机连
  • %:任意远程主机
  • 具体 IP:只允许那台机器

创建用户

1
CREATE USER '用户名'@'主机' IDENTIFIED BY '密码';

常见场景:

1
2
3
4
5
6
7
8
-- 本机可登录的用户
CREATE USER 'dev'@'localhost' IDENTIFIED BY '123456';

-- 任意远程主机可登录
CREATE USER 'dev'@'%' IDENTIFIED BY '123456';

-- 只允许特定 IP 登录
CREATE USER 'dev'@'192.168.1.100' IDENTIFIED BY '123456';

授权

1
GRANT 权限列表 ON 数据库.表 TO '用户名'@'主机';

常用权限:

权限 说明
ALL PRIVILEGES 所有权限(慎用)
SELECT 可查询
INSERT 可插入
UPDATE 可修改
DELETE 可删除
CREATE 可建库/表
DROP 可删库/表(极危险)
ALTER 可修改表结构

常见授权场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 1. 授予某个库的所有权限
GRANT ALL PRIVILEGES ON mydb.* TO 'dev'@'localhost';

-- 2. 授予某个库的只读权限
GRANT SELECT ON mydb.* TO 'readonly'@'%';

-- 3. 精细到一张表
GRANT SELECT, INSERT, UPDATE ON mydb.users TO 'dev'@'localhost';

-- 4. 精细到一列(只允许更新 email 列)
GRANT UPDATE (email) ON mydb.users TO 'dev'@'localhost';

-- 5. 全局权限(对所有库表都生效)
GRANT SELECT ON *.* TO 'readonly'@'%';

*.* 表示所有库所有表,mydb.* 表示 mydb 库的所有表


查看已有权限

1
2
3
4
5
-- 查看某个用户的权限
SHOW GRANTS FOR 'dev'@'localhost';

-- 查看自己当前登录用户的权限
SHOW GRANTS FOR CURRENT_USER;

输出示例:

1
GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'dev'@'localhost'

回收权限

1
REVOKE 权限列表 ON 数据库.表 FROM '用户名'@'主机';
1
2
3
4
5
-- 回收 dev 用户对 mydb 库的 DELETE 权限
REVOKE DELETE ON mydb.* FROM 'dev'@'localhost';

-- 回收所有权限
REVOKE ALL PRIVILEGES ON mydb.* FROM 'dev'@'localhost';

修改密码

自己改自己的密码:

1
ALTER USER CURRENT_USER IDENTIFIED BY '新密码';

管理员改别人密码:

1
ALTER USER 'dev'@'localhost' IDENTIFIED BY '新密码';

删除用户

1
DROP USER 'dev'@'localhost';

修改权限后立即生效

1
FLUSH PRIVILEGES;

多数 GRANT/REVOKE 会立即生效,但有些情况(如直接操作 mysql.user 表)需要手动刷新。

整库数据备份和还原

数据备份和还原是数据库管理的最后一道防线,能让你在误删数据、服务器宕机时”起死回生”。

备份类型速览

维度 说明
按范围 整库备份、单表备份、只备结构不备数据
按方式 逻辑备份(导 SQL 语句)、物理备份(复制数据文件)

日常最常用的是 逻辑备份,通过 MySQL 自带的 mysqldump 工具完成。


备份:mysqldump

注意mysqldump 不是在 mysql> 命令行里执行的,而是在操作系统的终端(CMD / Shell)里执行

备份整个数据库

1
mysqldump -u 用户名 -p 数据库名 > 备份文件.sql
1
2
# 示例:备份 mydb 库,生成 mydb_backup.sql 文件
mysqldump -u root -p mydb > mydb_backup.sql

回车后输入密码,当前目录下就多了 mydb_backup.sql,里面是所有建表 + 插数据的 SQL 语句。

备份多个数据库

1
mysqldump -u root -p --databases db1 db2 > backup.sql

备份所有数据库

1
mysqldump -u root -p --all-databases > all_backup.sql

只备份表结构,不要数据

-d 参数,适合复制表结构到新环境:

1
mysqldump -u root -p -d mydb > mydb_structure.sql

只备份数据,不包含建表语句

-t 参数,适合向已有表中导入数据:

1
mysqldump -u root -p -t mydb > mydb_data.sql

备份单张表

1
mysqldump -u root -p mydb 表名 > table_backup.sql

还原:恢复数据

在终端里导入(最常用)

1
mysql -u root -p 数据库名 < 备份文件.sql
1
2
# 前提:目标库 mydb 已经存在
mysql -u root -p mydb < mydb_backup.sql

登录后再导入

1
2
3
-- 在 mysql> 命令行里
USE mydb;
SOURCE D:/backup/mydb_backup.sql;

路径用 /\\,别用单个 \


远程备份与还原

备份远程服务器上的数据库,加 -h-P 参数:

1
2
3
4
5
# 备份远程数据库
mysqldump -h 192.168.1.100 -P 3306 -u root -p mydb > backup.sql

# 还原到远程服务器
mysql -h 192.168.1.100 -P 3306 -u root -p mydb < backup.sql

备份脚本化(太重要了)

生产中通常会写个脚本,配合 Linux 的定时任务 crontab 或 Windows 的”任务计划程序”,实现每日自动备份。

一个 Linux 备份脚本雏形

1
2
3
4
5
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
mysqldump -u root -p'密码' mydb > /backup/mydb_$DATE.sql
# 删除 7 天前的旧备份
find /backup/ -name "mydb_*.sql" -mtime +7 -delete

生产环境中密码建议写在配置文件中或用 --defaults-extra-file,避免命令行暴露。


物理备份:直接复制数据文件

适用于停服维护时间宽裕的场景,或者数据量特别大的全量迁移:

  1. 先停掉 MySQL 服务。
  2. 直接复制整个数据目录(通常叫 data,里面有你的数据库文件夹)。
  3. 在新服务器上,覆盖新安装 MySQL 的 data 目录。

注意事项

  • MySQL 版本必须一致或兼容。
  • 引擎必须是 InnoDB/MyISAM。

一般小团队直接使用 mysqldump 更安全、更方便。


备份与还原快速命令备忘

操作 命令
备份整个库 mysqldump -u root -p 库名 > xxx.sql
备份多个库 mysqldump -u root -p --databases db1 db2 > xxx.sql
只备份结构 mysqldump -u root -p -d 库名 > xxx.sql
终端还原 mysql -u root -p 库名 < xxx.sql
登录后还 SOURCE 路径/xxx.sql;

外键

外键是关系型数据库保证数据引用完整性的核心机制。简单说,它强制要求从表的某个字段值,必须在主表里真实存在。

外键是什么?

定义:从表里的一个或一组字段,它的值必须匹配主表中主键(或唯一键)的某个值。

外键的基本语法

在建表时声明,或者在已有表上添加。

1
2
3
4
5
6
7
8
9
10
11
-- 方式1:建表时声明
CREATE TABLE students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
class_id INT,
FOREIGN KEY (class_id) REFERENCES classes(id)
);

-- 方式2:给已有表添加外键
ALTER TABLE students
ADD FOREIGN KEY (class_id) REFERENCES classes(id);

语法拆解:

  • FOREIGN KEY (从表字段):声明哪个字段是外键。
  • REFERENCES 主表(主表字段):指定它指向哪张表的哪个字段。
  • 主表被引用的字段必须是主键或有唯一约束

外键的约束效果

一旦建了外键约束,数据库就会自动做两件事:

1. 插入/更新保护
向 students 表插数据时,class_id 必须是 classes 表里已存在的 id 值,否则直接报错拒绝:

1
2
3
-- classes 里只有 id=1 和 id=2
INSERT INTO students (name, class_id) VALUES ('张三', 3);
-- 报错:class_id=3 不存在,插入失败

2. 删除/更新联动
你要删除或修改被引用的主表记录时,外键约束定义了怎么处理:

1
2
3
FOREIGN KEY (class_id) REFERENCES classes(id)
ON DELETE CASCADE
ON UPDATE CASCADE

事务安全

事务安全是数据库保证你数据不出错的最后一道保险。它靠 ACID 四大特性和一套隔离机制,让并发操作下数据依然逻辑正确。

什么是事务?

事务是一组要么全部成功、要么全部失败的 SQL 操作,像一个原子一样不可拆分。

最经典的例子——转账:

1
2
3
4
5
-- A 账户扣 500 元
UPDATE accounts SET balance = balance - 500 WHERE user_id = 1;

-- B 账户加 500 元
UPDATE accounts SET balance = balance + 500 WHERE user_id = 2;

如果不包在事务里,执行完第一条后系统崩溃,A 的钱凭空消失,B 也没收到。事务就是用来防止这种情况的。


ACID 四大特性

特性 含义 一句话
A 原子性 同生共死。一组操作要么全执行,要么全回滚。 扣钱和加钱必须一起生效。
C 一致性 数据永远符合预设规则。 转账前 AB 总额 1000,转账后还是 1000。
I 隔离性 多个事务同时跑,彼此看不见对方的中间状态。 不能同时看到别人扣钱前和加钱后的中间态。
D 持久性 一旦提交,数据就永久保存,断电也不丢。 转账成功立马拔电源,重启后数据还在。

如何手动控制事务

MySQL 默认每条语句自动提交,要手动管理,必须先关掉自动提交

1
2
3
4
5
6
7
8
9
10
11
12
-- 1. 开始一个新事务(顺便关闭自动提交)
START TRANSACTION;

-- 2. 执行你想保护的操作
UPDATE accounts SET balance = balance - 500 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 500 WHERE user_id = 2;

-- 3. 检查出问题了?全部撤销
ROLLBACK;

-- 4. 都确认无误?永久生效
COMMIT;

特别注意:必须先 START TRANSACTION,否则 ROLLBACK 和 COMMIT 无意义——每条语句已经自动提交完了,无事务可回滚。

事务安全的核心:隔离级别

多个事务同时操作相同数据时,如果不隔离好,就会出现各种“幻觉”。

1. 四大并发问题

问题 图解 说明
脏读 读到别人未提交的临时数据。 A 转账还没确认,B 就看到了钱变多,A 一回滚,B 看到的就是脏数据。
不可重复读 同一事务内两次读同一行,值变了 开始查余额 1000,事务中另一个事务改了它并提交,再查变成 500。
幻读 同一事务内两次查一组数据,行数变了 开始查有 10 个用户,事务中别人新插入/删除了用户,再查变成 11/9 行。
丢失更新 两个事务同时改同一行,后提交的覆盖了先提交的。 A 给用户加 100,B 同时给用户加 200,最后用户只多了 200,丢失 100。

2. 四种隔离级别与问题对应

隔离级别 脏读 不可重复读 幻读 并发性能
READ UNCOMMITTED ❌ 会 ❌ 会 ❌ 会 最高
READ COMMITTED ✅ 解决 ❌ 会 ❌ 会 较高
REPEATABLE READ(默认) ✅ 解决 ✅ 解决 ⚠️ 部分解决 一般
SERIALIZABLE ✅ 解决 ✅ 解决 ✅ 解决 最低

查看和设置隔离级别

1
2
3
4
5
6
7
8
9
10
11
-- 查看当前会话的隔离级别
SELECT @@transaction_isolation;

-- 查看全局隔离级别
SELECT @@global.transaction_isolation;

-- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局隔离级别(对新建连接生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

隔离级别选型建议

场景 推荐级别
交易类系统(支付、转账) REPEATABLE READ(MySQL 默认)
高并发读多写少(内容、统计) READ COMMITTED
批量对账、报表快照 REPEATABLE READ 或更高

绝大多数 MySQL 应用保持默认 REPEATABLE READ 即可。

实战:事务安全操作流程

结合之前讲过的外键,一个完整的安全操作示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 第一步:开启事务
START TRANSACTION;

-- 第二步:执行核心操作
INSERT INTO orders (...) VALUES (...);
INSERT INTO order_items (...) VALUES (...);
UPDATE inventory SET stock = stock - 1 WHERE product_id = 99;

-- 第三步:检查结果
SELECT * FROM orders WHERE ...;
SELECT stock FROM inventory WHERE product_id = 99;

-- 第四步:决策
-- 如果一切正确
COMMIT;

-- 如果发现问题(比如库存为负)
ROLLBACK;

事务安全的常见陷阱

陷阱 说明
没开事务就互改 自动提交下,两个 UPDATE 各行其是,ROLLBACK 失效。
长事务不提交 导致行锁一直不释放,其他操作被阻塞,甚至引发大面积不可用。
以为隔离级别是万能药 靠高隔离级别防并发不如乐观锁/悲观锁 + 合理业务设计来得高效。

一句话总结

事务强大的地方在于它能保证一组连续操作像一个原子一样,要么全部执行,要么全部回滚,同时靠隔离级别在并发和正确性之间做平衡。掌握 START TRANSACTIONCOMMITROLLBACK,以及四种隔离级别解决的不同问题,就抓住了事务安全的核心。

  • 标题: MySQL数据库盲区补充
  • 作者: SoloWalker
  • 创建于 : 2026-05-07 00:00:00
  • 更新于 : 2026-05-07 20:35:17
  • 链接: https://s0lowalker.github.io/2026/05/07/数据库/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论