MySQL数据库盲区补充
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
SHOW VARIABLES LIKE 'character_set_server';
数据库层
新建库时若不特别指定,就继承服务器设置。建库时可以明确声明:1
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
表与列层
建表时继承库设置,但你也可以为各列设置不同的字符集,这也是最灵活的一层。1
2
3
4CREATE TABLE users (
name VARCHAR(50) CHARACTER SET utf8mb4,
bio TEXT
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
列类型
列类型是建表时最重要的设计决策之一,它不仅决定数据怎么存,还直接影响性能、存储空间和查询正确性。
为什么要重视列类型?
- 省空间:一个
TINYINT占1字节,INT占4字节。亿级数据量时,差出几个G。 - 保正确:年龄用
INT能存”999岁”,用TINYINT上限127,天然挡掉离谱数据。 - 快查询:建索引的列,类型越小,索引文件越小,查询越快。
数值类型
整数
| 类型 | 字节 | 有符号范围 | 无符号范围 |
|---|---|---|---|
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 | -- 年龄不可能为负,用无符号 |
小数:FLOAT/DOUBLE vs DECIMAL
- FLOAT / DOUBLE:近似值,有精度损失,绝对不能用于钱。
- DECIMAL(M, D):定点精确小数,专为金额设计。
1 | -- 存储金额:总长10位,小数点后2位 |
字符串类型:CHAR vs VARCHAR vs TEXT
这是最容易选错的一类。
| 类型 | 特点 | 适用场景 |
|---|---|---|
CHAR(N) |
定长。永远占 N 个字符宽,不够用空格补。最大255字符。 | 长度固定的短字符串:手机号、身份证、MD5哈希值、状态码。 |
VARCHAR(N) |
变长。用多少占多少,额外1-2字节记录长度。最大65535字节(注意是字节,不是字符)。 | 绝大多数变长字符串:姓名、地址、邮箱、标题。 |
TEXT |
存长文本,最大65535字节。 | 文章内容、商品描述、JSON字符串。 |
重点说明 VARCHAR 的两个特点:
- N 是字符数,不是字节数。
VARCHAR(10)在utf8mb4下实际最大占40字节,一个 emoji 就能占4字节。 - 行大小限制。MySQL 一行最大 65535 字节,所以
VARCHAR(N)的 N 不能无限大。超过建议用 TEXT。
1 | -- 选型示例 |
其他类型不一一列举。
列属性
常用的列属性一览
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 | status TINYINT DEFAULT 1, |
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 | SELECT 列名... |
还有左连接和右连接,用来处理可能没有匹配记录的情况:
- LEFT JOIN:左表所有记录都在,右表没有匹配则填 NULL。例如查所有用户(包括没订单的)的订单数。
- RIGHT JOIN:和左连接反过来,右表记录全在。实际场景中很少用它(用 LEFT JOIN 调换表顺序就行)。
子查询
子查询就是查询里套查询,把内层查询的结果当作外层查询的条件或临时表。
1. 作为条件值(用在 WHERE 里)
1 | -- 查询"张三"所在班级的所有同学 |
内层查询必须只返回单个值(单行单列),用
=比较才不报错。
2. 配合 IN / EXISTS 操作符
- IN:检查值是否在子查询返回的一列结果集合中。
- EXISTS:检查子查询是否有记录存在。通常比 IN 效率更高,尤其是匹配大数据集时。
1 | -- IN 示例:查有订单记录的用户 |
3. 作为临时表(用在 FROM 后面)
把子查询结果直接当一张表参与查询:
1 | SELECT avg_age.class_id, avg_age.avg_age |
聚合统计与分组筛选
数人头、求平均数这类操作就靠聚合函数。
常用聚合函数:COUNT, SUM, AVG, MAX, MIN。
关键在于分清 WHERE 和 HAVING:
- WHERE:对原始行进行筛选(分组前)。
- HAVING:对聚合后的结果进行筛选(分组后)。
1 | -- 统计每个班级中年龄大于18岁的学生人数,且只显示人数超过5人的班级 |
视图基本操作
视图是什么?
- 不存数据:视图本身不存储实际数据,数据仍然在原始表中。
- 存的是查询逻辑:每次查视图时,数据库会执行它背后那条 SELECT 语句,实时返回最新结果。
- 本质:一个虚拟表,给复杂的查询起个名字,简化后续使用。
类比:原表是仓库里的实际货架,视图就像仓库墙上挂的一张“取货清单”,按这张清单去货架上拿东西,清单本身不存货。
创建视图
1 | CREATE VIEW 视图名 AS |
举个例子,假设经常要查“学生姓名 + 班级名称”,每次都要写 JOIN:
1 | CREATE VIEW student_class_view AS |
以后就不用再写 JOIN 了,直接:
1 | SELECT * FROM student_class_view; |
查看视图
1 | -- 查看当前库所有视图(和表一起列出来) |
视图和表在
SHOW TABLES里会一起列出来,可以用SHOW FULL TABLES WHERE table_type = 'VIEW';只列视图。
使用视图
当成普通表来查就行,也支持 WHERE、ORDER BY、GROUP BY:
1 | SELECT * FROM student_class_view |
视图的查询结果会实时反映原表的数据变化。原表数据改了,视图查出来的结果也跟着变。
修改视图
覆盖式修改(最常用,不存在则创建,存在则替换):
1 | CREATE OR REPLACE VIEW student_class_view AS |
直接修改(视图已存在时):
1 | ALTER VIEW student_class_view AS |
删除视图
1 | DROP VIEW student_class_view; |
可同时删多个:
1 | DROP VIEW view1, view2; |
注意:有些视图之间可能存在依赖关系。删掉一个被其他视图引用的视图,不会报错,但再查那些依赖它的视图就会失败。
用户权限管理
用户权限管理是数据库安全的核心。MySQL 通过“用户 + 权限”两层机制,精确控制谁能从哪登录、能对哪些库表做什么操作。
权限体系概览
1 | 用户账号@主机 → 全局/库/表/列级权限 → 具体操作(SELECT, INSERT, DELETE...) |
- 用户:格式为
'用户名'@'主机',表示谁、从哪台机器连进来。 - 权限:可授予不同级别(全局 → 库 → 表 → 列 → 存储过程),越向下越精细。
- 生效方式:修改权限后需要
FLUSH PRIVILEGES;刷新,或重启 MySQL。
查看用户
MySQL 的用户信息存在系统库 mysql 的 user 表中。
1 | -- 切换到系统库 |
host 列表示该账号能从哪台机器连进来:
localhost:只能本机连%:任意远程主机- 具体 IP:只允许那台机器
创建用户
1 | CREATE USER '用户名'@'主机' IDENTIFIED BY '密码'; |
常见场景:
1 | -- 本机可登录的用户 |
授权
1 | GRANT 权限列表 ON 数据库.表 TO '用户名'@'主机'; |
常用权限:
| 权限 | 说明 |
|---|---|
ALL PRIVILEGES |
所有权限(慎用) |
SELECT |
可查询 |
INSERT |
可插入 |
UPDATE |
可修改 |
DELETE |
可删除 |
CREATE |
可建库/表 |
DROP |
可删库/表(极危险) |
ALTER |
可修改表结构 |
常见授权场景:
1 | -- 1. 授予某个库的所有权限 |
*.*表示所有库所有表,mydb.*表示 mydb 库的所有表
查看已有权限
1 | -- 查看某个用户的权限 |
输出示例:
1 | GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'dev'@'localhost' |
回收权限
1 | REVOKE 权限列表 ON 数据库.表 FROM '用户名'@'主机'; |
1 | -- 回收 dev 用户对 mydb 库的 DELETE 权限 |
修改密码
自己改自己的密码:
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 | # 示例:备份 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 | # 前提:目标库 mydb 已经存在 |
登录后再导入
1 | -- 在 mysql> 命令行里 |
路径用
/或\\,别用单个\。
远程备份与还原
备份远程服务器上的数据库,加 -h 和 -P 参数:
1 | # 备份远程数据库 |
备份脚本化(太重要了)
生产中通常会写个脚本,配合 Linux 的定时任务 crontab 或 Windows 的”任务计划程序”,实现每日自动备份。
一个 Linux 备份脚本雏形:
1 |
|
生产环境中密码建议写在配置文件中或用
--defaults-extra-file,避免命令行暴露。
物理备份:直接复制数据文件
适用于停服维护时间宽裕的场景,或者数据量特别大的全量迁移:
- 先停掉 MySQL 服务。
- 直接复制整个数据目录(通常叫
data,里面有你的数据库文件夹)。 - 在新服务器上,覆盖新安装 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 | -- 方式1:建表时声明 |
语法拆解:
FOREIGN KEY (从表字段):声明哪个字段是外键。REFERENCES 主表(主表字段):指定它指向哪张表的哪个字段。- 主表被引用的字段必须是主键或有唯一约束。
外键的约束效果
一旦建了外键约束,数据库就会自动做两件事:
1. 插入/更新保护
向 students 表插数据时,class_id 必须是 classes 表里已存在的 id 值,否则直接报错拒绝:
1 | -- classes 里只有 id=1 和 id=2 |
2. 删除/更新联动
你要删除或修改被引用的主表记录时,外键约束定义了怎么处理:
1 | FOREIGN KEY (class_id) REFERENCES classes(id) |
事务安全
事务安全是数据库保证你数据不出错的最后一道保险。它靠 ACID 四大特性和一套隔离机制,让并发操作下数据依然逻辑正确。
什么是事务?
事务是一组要么全部成功、要么全部失败的 SQL 操作,像一个原子一样不可拆分。
最经典的例子——转账:
1 | -- A 账户扣 500 元 |
如果不包在事务里,执行完第一条后系统崩溃,A 的钱凭空消失,B 也没收到。事务就是用来防止这种情况的。
ACID 四大特性
| 特性 | 含义 | 一句话 |
|---|---|---|
| A 原子性 | 同生共死。一组操作要么全执行,要么全回滚。 | 扣钱和加钱必须一起生效。 |
| C 一致性 | 数据永远符合预设规则。 | 转账前 AB 总额 1000,转账后还是 1000。 |
| I 隔离性 | 多个事务同时跑,彼此看不见对方的中间状态。 | 不能同时看到别人扣钱前和加钱后的中间态。 |
| D 持久性 | 一旦提交,数据就永久保存,断电也不丢。 | 转账成功立马拔电源,重启后数据还在。 |
如何手动控制事务
MySQL 默认每条语句自动提交,要手动管理,必须先关掉自动提交。
1 | -- 1. 开始一个新事务(顺便关闭自动提交) |
特别注意:必须先 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 | -- 查看当前会话的隔离级别 |
隔离级别选型建议
| 场景 | 推荐级别 |
|---|---|
| 交易类系统(支付、转账) | REPEATABLE READ(MySQL 默认) |
| 高并发读多写少(内容、统计) | READ COMMITTED |
| 批量对账、报表快照 | REPEATABLE READ 或更高 |
绝大多数 MySQL 应用保持默认 REPEATABLE READ 即可。
实战:事务安全操作流程
结合之前讲过的外键,一个完整的安全操作示例:
1 | -- 第一步:开启事务 |
事务安全的常见陷阱
| 陷阱 | 说明 |
|---|---|
| 没开事务就互改 | 自动提交下,两个 UPDATE 各行其是,ROLLBACK 失效。 |
| 长事务不提交 | 导致行锁一直不释放,其他操作被阻塞,甚至引发大面积不可用。 |
| 以为隔离级别是万能药 | 靠高隔离级别防并发不如乐观锁/悲观锁 + 合理业务设计来得高效。 |
一句话总结
事务强大的地方在于它能保证一组连续操作像一个原子一样,要么全部执行,要么全部回滚,同时靠隔离级别在并发和正确性之间做平衡。掌握 START TRANSACTION、COMMIT、ROLLBACK,以及四种隔离级别解决的不同问题,就抓住了事务安全的核心。
- 标题: 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 进行许可。