MySQL学习笔记四

MySQL 学习进度总笔记

版本:2026-10-10(当前聊天进度更新版)

用途:记录 MySQL 学习进度,供以后新建 ChatGPT 聊天时恢复学习状态。

============================================================

一、给新聊天助手的指令

============================================================

我正在系统学习 MySQL。请根据这份笔记继续教学,不要从第一课重新开始。

请严格遵守以下教学要求:

  1. 使用中文讲解,尽量结合实际 SQL 示例和实际开发场景。

  2. 每一课应包含:

  • 知识点讲解

  • SQL 语法和示例

  • 实际开发用途

  • 练习题

  1. 练习题必须逐题进行,一次只出一道题。

  2. 等我回答后,再批改当前题目,不要提前公布后续题目的答案。

  3. 每道题按 10 分制评分,并解释:

  • 本题得分

  • 我的答案是否正确

  • 正确答案

  • 为什么正确

  • 我的答案错在哪里

  • 其他选项为什么错误

  1. 每次批改后更新本课累计成绩,并记录错题和薄弱点。

  2. 每课结束时总结:

  • 本课知识点

  • 本课总成绩

  • 已掌握的内容

  • 需要复习的内容

  • 下一课学习计划

  1. 如果我答错了某道题,后续应适当安排针对性复习题。

  2. 不要因为某个知识点曾经答对,就直接认定我已经完全掌握;应结合多道题的表现判断。

  3. 涉及真实数据库的 INSERT、UPDATE、DELETE、DDL 和事务操作时,提醒我先核对实际表结构、数据及影响范围,不要直接在生产库执行未经确认的修改。

  4. 如果我已经回答了当前题目,请直接批改,不要重新提问。

  5. 如果本笔记与当前聊天中的最新学习记录冲突,以当前聊天中的最新记录为准。

  6. 请持续帮助我记录学习进度,让我以后可以通过这份笔记继续学习。

  7. 不要猜测或伪造没有记录的历史成绩。

  8. 每次批改后,除更新分数外,还应记录用户的原始答案、解释、错误原因和后续复习重点。

============================================================

二、整体学习进度

============================================================

当前学习课程:

第12课《数据库表设计与规范化》。

当前学习位置:

第12课练习题12,等待用户提交答案。

第12课当前成绩:

109/110 分。

已完成题目:

第12课练习题1至练习题11。

当前待回答题目:

第12课练习题12《第三范式综合判断》。

下一步学习计划:

  1. 等待用户回答第12课练习题12。

  2. 根据用户的答案和解释进行评分。

  3. 解释全部选项,更新第12课累计成绩和薄弱点。

  4. 继续巩固第二范式、第三范式、函数依赖、传递依赖。

  5. 结合游戏服务端场景练习数据库表设计。

  6. 完成本课后总结成绩和知识点,再规划下一课。

已经学习或完成的课程:

第1课至第5课:

已经完成,但当前笔记没有完整保留每课详细成绩,不要猜测。

第6课:JOIN 多表查询

成绩:100/100。

第7课:子查询

成绩:100/100。

第8课:索引

成绩:100/100。

第9课:主键、唯一约束、复合索引

已学习基础内容。

早期部分练习记录为60/100。

仍需巩固复合索引的最左前缀原则。

第10课:事务

记录成绩:100/120。

第11课:事务隔离级别与并发问题

第一轮练习成绩:80/100。

随后完成综合巩固题1至题6,每题10分,共60/60。

第12课:数据库表设计与规范化

正在学习中。

已完成练习题1至题11,累计109/110。

说明:

第1至第5课的详细逐题成绩没有完整保留,不要伪造。

第12课练习题12尚未回答,不能算作已完成。

============================================================

三、已经学习的基础知识

============================================================

一、基础查询

已经学习以下内容:

SELECT:查询数据。

WHERE:筛选数据。

ORDER BY:排序。

LIMIT:限制返回记录数量。

常见条件表达式及查询方式。

后续学习应结合实际业务理解 SQL,而不是只背诵语法。

二、JOIN 多表查询

第6课已经完成,成绩100分。

已经学习多表关联查询的基础知识。

后续可以结合以下业务表继续练习:

player:玩家表。

player_item:玩家道具记录表。

item_config:道具配置表。

orders:订单表。

guild:公会表。

guild_member:公会成员关联表。

重点是理解不同表之间的关系,以及如何根据关联字段查询业务数据。

三、子查询

第7课已经完成,成绩100分。

已经学习子查询相关知识。

后续可以继续比较:

  • IN

  • EXISTS

  • 普通 JOIN

  • 相关子查询

需要根据实际业务和执行计划判断不同写法的适用场景。

四、索引

第8课已经完成,成绩100分。

已经学习:

  • 索引的基本作用

  • 索引对查询性能的影响

  • 使用 EXPLAIN 分析查询执行计划

  • 根据查询条件判断索引是否可能被使用

注意:

有索引不代表查询一定会使用索引。

应结合 SQL 写法、数据分布、索引结构和 EXPLAIN 结果进行分析。

五、主键、唯一约束和复合索引

第9课已经学习基础内容。

涉及:

  • PRIMARY KEY

  • UNIQUE

  • 复合索引

  • 最左前缀原则

需要重点复习最左前缀原则。

例如:

CREATE INDEX idx_level_gold

ON player(level, gold);

这个复合索引通常适合:

WHERE level = 20;

WHERE level = 20 AND gold > 1000;

但是:

WHERE gold = 1000;

通常不能有效利用该复合索引的最左前缀。

是否需要为 gold 单独建立索引,应结合真实查询、数据分布和 EXPLAIN 判断,不要无条件添加索引。

============================================================

四、第10课:事务

============================================================

课程状态:已完成。

记录成绩:100/120分。

一、事务基本操作

开启事务:

START TRANSACTION;

提交事务:

COMMIT;

回滚事务:

ROLLBACK;

事务可以把多条数据库操作组织为一个整体,以满足业务一致性要求。

例如,玩家之间转移金币可能涉及:

  1. 扣除玩家A的金币。

  2. 增加玩家B的金币。

  3. 记录交易信息。

如果这些操作必须同时成功,就需要考虑在同一个事务中执行。

二、InnoDB 事务回滚

对于尚未提交的事务修改,可以通过 ROLLBACK 撤销。

COMMIT 则提交事务。

注意:

事务不能自动撤销已经提交的事务,也不能保证所有业务逻辑都没有错误。

三、条件更新与扣款

示例:

UPDATE player

SET gold = gold - 800

WHERE userId = 1001

AND gold >= 800;

这条 SQL 将余额判断与扣款放在同一条 UPDATE 中,可以降低并发扣款时先查询、后扣款造成的风险。

执行后应检查 affectedRows。

通常:

affectedRows = 1:

成功更新了一行。

affectedRows = 0:

没有行满足更新条件。

affectedRows 为0可能表示:

  • 玩家不存在。

  • 玩家余额不足。

  • WHERE 中的其他条件不满足。

不能简单地认为 affectedRows = 0 就会抛出 SQL 异常。

四、SQL 异常与 affectedRows 的区别

SQL 执行失败时,通常会进入异常处理逻辑,例如 Node.js 的 try/catch。

SQL 成功执行,但没有匹配任何行时,通常不会因此自动抛出异常。

业务代码必须检查 affectedRows,并根据实际业务决定如何处理。

五、Node.js 事务处理

使用 Node.js 操作 MySQL 时,通常需要考虑:

  • 开启事务

  • 执行业务 SQL

  • 成功后 COMMIT

  • 发生错误时尝试 ROLLBACK

  • finally 中释放数据库连接

  • 处理连接失败、回滚失败等异常情况

六、事务与幂等性

事务和幂等性不是同一个概念。

事务主要用于保证一组数据库操作的原子性。

幂等性主要用于防止同一个业务请求重复执行产生错误结果。

例如,订单支付或发奖业务,可以考虑:

  • 唯一订单号

  • 数据库唯一约束

  • 订单状态检查

  • 重复请求识别

  • 必要的事务处理

需要继续巩固:

  1. affectedRows = 0 不会自动抛出异常。

  2. 开启事务不代表并发问题自动消失。

  3. 事务、并发控制和幂等性各自解决不同的问题。

============================================================

五、第11课:事务隔离级别与并发问题

============================================================

课程状态:第一轮练习已完成。

第一轮成绩:80/100分。

综合巩固题1至题6:60/60分。

一、四种事务隔离级别

READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE

MySQL InnoDB 默认事务隔离级别是:

REPEATABLE READ。

二、脏读

场景:

事务A将金币从1000修改为700,但尚未提交。

事务B在 READ UNCOMMITTED 下读取到700。

随后事务A回滚。

正确概念:脏读。

原因:

事务B读取了其他事务尚未提交的数据。

即使事务A最后回滚,事务B此前读取到未提交数据的事实仍然成立。

脏读通常与 READ UNCOMMITTED 有关。

三、不可重复读

场景:

事务A第一次查询金币,得到1000。

事务B将金币修改为700并提交。

事务A再次执行相同查询,得到700。

正确概念:不可重复读。

原因:

READ COMMITTED 下,每次普通一致性读通常会建立新的读视图,因此同一事务的两次查询可能看到不同的已提交数据。

同一条记录的字段值前后发生变化,通常对应不可重复读。

四、REPEATABLE READ 与读视图

在 InnoDB 的 REPEATABLE READ 下,同一事务的普通一致性读通常复用第一次一致性读建立的读视图。

场景:

事务A第一次普通一致性 SELECT 查询到金币1000。

事务B将金币修改为700并提交。

事务A再次执行相同的普通一致性 SELECT。

通常结果:

事务A第二次仍然读取到1000。

注意:

普通一致性读与 SELECT ... FOR UPDATE 等锁定读的机制不同。

不能认为事务内所有读取操作都必然返回同一个快照值。

五、幻读

场景:

事务A执行:

SELECT COUNT(*)

FROM player

WHERE level = 20;

第一次结果为5。

事务B插入一条 level = 20 的新记录并提交。

事务A再次执行相同查询,结果变成6。

在这种场景下,符合条件的记录集合发生了变化,对应幻读。

三个概念的区别:

脏读:

读取了其他事务尚未提交的数据。

不可重复读:

同一条记录前后两次读取的值发生变化。

幻读:

同一个条件查询前后,符合条件的记录集合发生变化。

补充:

InnoDB 的 REPEATABLE READ 下,普通一致性读通常通过同一读视图保持快照一致性。锁定读还涉及相应的锁机制。

六、InnoDB 默认隔离级别

必须记住:

MySQL InnoDB 默认事务隔离级别是:

REPEATABLE READ。

查看当前会话隔离级别:

SELECT @@transaction_isolation;

旧版本 MySQL 可能使用:

SELECT @@tx_isolation;

七、并发扣款

玩家有1000金币。

两个请求几乎同时扣除800金币。

如果两个请求都先查询余额,都看到1000,再分别扣款,就可能出现并发业务错误。

推荐考虑条件更新:

UPDATE player

SET gold = gold - 800

WHERE userId = 1001

AND gold >= 800;

原因:

将余额判断和扣款操作放在同一条 UPDATE 中。

对于同一行的并发更新,InnoDB 会协调冲突更新。

如果第二个请求执行条件检查时余额已经不足,通常不会更新成功。

执行后需要检查 affectedRows。

注意:

如果玩家原本有2000金币,同一个请求重复执行仍可能成功扣款两次。

因此,条件更新不能替代幂等性设计。

八、SELECT ... FOR UPDATE

示例:

START TRANSACTION;

SELECT gold

FROM player

WHERE userId = 1001

FOR UPDATE;

正确概念:

SELECT ... FOR UPDATE 是锁定读,通常会对匹配的索引记录加排他锁。

其他事务对冲突记录进行不兼容修改时,通常需要等待锁释放。

注意:

  • FOR UPDATE 不会自动提交事务。

  • 应使用合适的索引,减少不必要的锁定范围。

  • 应尽快提交或回滚事务。

  • 多条记录加锁时尽量统一顺序,但不能保证完全消除死锁。

九、死锁

场景:

事务A锁定玩家1001。

事务B锁定玩家1002。

事务A请求锁定1002,需要等待B。

事务B请求锁定1001,需要等待A。

正确概念:死锁。

原因:

事务之间形成循环等待。

InnoDB 通常会检测死锁,并选择一个事务作为牺牲者回滚,以解除循环等待。

减少死锁的办法:

  • 尽量统一加锁顺序。

  • 缩短事务执行时间。

  • 避免持锁时进行耗时网络请求。

  • 在应用程序中妥善处理死锁异常。

  • 如有必要,可有限次数地重试整个事务。

  • 重试时必须注意幂等性,防止重复扣款或重复发奖。

十、设置当前会话隔离级别

正确写法:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

作用:

将当前会话的事务隔离级别设置为 READ COMMITTED。

常见作用范围:

SET SESSION TRANSACTION ISOLATION LEVEL ...

设置当前会话的事务隔离级别。

SET GLOBAL TRANSACTION ISOLATION LEVEL ...

设置全局默认隔离级别,通常影响之后建立的会话。

需要相应权限,且不会自动改写已有会话的会话级设置。

SET TRANSACTION ISOLATION LEVEL ...

不带 SESSION/GLOBAL 时,设置当前会话的下一个事务,不等同于持续修改当前会话默认值。

十一、第11课第一轮逐题成绩

第1题:脏读

10/10。

第2题:READ COMMITTED 与不可重复读

10/10。

第3题:REPEATABLE READ 与读视图

0/10。

用户答案:C。

正确答案:B。

错误原因:

用户误以为 REPEATABLE READ 允许读取未提交数据。

需要记住:

REPEATABLE READ 下的普通一致性读通常复用同一个读视图。

脏读通常与 READ UNCOMMITTED 有关。

第4题:幻读

10/10。

第5题:InnoDB 默认隔离级别

0/10。

用户答案:B,READ COMMITTED。

正确答案:C,REPEATABLE READ。

错误原因:

把 READ COMMITTED 当成 InnoDB 默认隔离级别。

第6题:并发扣款

10/10。

第7题:SELECT ... FOR UPDATE

10/10。

第8题:死锁

10/10。

第9题:设置当前会话隔离级别

10/10。

第10题:REPEATABLE READ 综合判断

10/10。

第一轮总分:

80/100。

十二、第11课综合巩固题记录

综合巩固题1:

答案:B。

解释:REPEATABLE READ 普通一致性读通常继续使用原来的读视图。

得分:10/10。

综合巩固题2:

答案:C。

解释:InnoDB 是 MySQL 的默认存储引擎,支持 ACID 事务,适用于 OLTP 场景。

得分:10/10。

综合巩固题3:

答案:B。

解释:理解普通一致性读与锁定读的区别。

得分:10/10。

注意:

不能简单地把锁定读描述为"开启新视图"。锁定读涉及当前读和相应的锁机制。

综合巩固题4:

答案:C。

解释:条件扣款可以避免余额不足时继续扣款,但原余额足够时重复请求仍可能再次成功,需要幂等性机制。

得分:10/10。

综合巩固题5:

答案:B。

解释:事务A与事务B分别持有不同记录的锁,再请求对方持有的锁,形成循环等待。

得分:10/10。

综合巩固题6:

答案:B。

解释:

  • orderId 设置唯一约束,可以避免同一业务订单产生重复记录。

  • 事务可以在成功后提交,失败后回滚。

  • 统一加锁顺序可以降低死锁风险。

得分:10/10。

注意:

"先请求的先执行,后请求的等待执行"不是可靠的通用并发保证。统一加锁顺序是降低死锁风险的方法,但不能保证完全消除死锁。

第11课综合巩固题累计:

60/60。

第11课需要重点复习的内容:

  1. InnoDB 默认隔离级别是 REPEATABLE READ,不是 READ COMMITTED。

  2. REPEATABLE READ 下,同一事务的普通一致性读通常复用同一个读视图。

  3. 不要混淆普通一致性读与 SELECT ... FOR UPDATE 锁定读。

  4. 区分脏读、不可重复读和幻读。

  5. 事务不能单独保证所有并发扣款或重复请求都安全。

  6. 条件更新、事务、锁和幂等性需要根据业务组合使用。

  7. 死锁重试必须考虑幂等性。

============================================================

六、第12课:数据库表设计与规范化

============================================================

课程状态:进行中。

当前累计成绩:

109/110分。

已完成练习题:

第1题至第11题。

当前待回答:

第12题。

本课已经涉及:

  1. 数据库表设计的基本原则。

  2. 玩家表、道具配置表、玩家背包表的拆分。

  3. 第一范式与字段原子性。

  4. 主键与业务唯一约束。

  5. 联合唯一约束。

  6. 外键与普通索引。

  7. 一对多、多对多关系。

  8. 关联表。

  9. 数据冗余与更新异常。

  10. 历史订单快照。

  11. 函数依赖与传递依赖。

  12. 第二范式与部分函数依赖。

  13. 第三范式的初步判断。


第12课练习题1:玩家表与道具表拆分


得分:10/10。

用户答案:B。

用户解释:

这样在游戏新加玩法需要新的道具时,不需要修改 player 表,只需要修改 player_item 表,并且对于道具的描述也更清晰。

掌握点:

玩家基础信息和道具记录应该分开存储。

游戏新增道具时,不应该通过不断增加 player 表的字段来保存道具。


第12课练习题2:第一范式相关题目


得分:10/10。

用户答案:A。

用户解释:

如果对1001玩家的药水道具做修改,不方便;也无法通过 items 直接得到单一的道具,例如只处理木剑时,无法方便地直接从 items 取出木剑。

掌握点:

避免将多个道具信息堆在同一个字段中。

数据应按合理粒度存储,以便查询、修改和维护。


第12课练习题3:主键设计


得分:9/10。

用户答案:C。

用户解释:

这样可以保证每个道具的唯一,并且通过 userId 和玩家一一对应上。

需要继续巩固:

主键唯一性、道具唯一性和玩家关联关系是不同概念。

独立 id 主键能唯一标识一条背包记录,但不能自动保证:

  • 同一个玩家只能有一条道具记录。

  • 同一种道具只能出现一次。

  • userId 与背包记录是一对一关系。

如果业务要求每个玩家每种普通道具只有一条汇总记录,可以考虑联合唯一约束:

UNIQUE (userId, itemId)


第12课练习题4:联合唯一约束


得分:10/10。

用户答案:B。

用户解释:

主键保证记录只有一个,联合唯一约束保证同一玩家的同一种类道具只有一条记录。

掌握点:

联合唯一约束可以维护业务上的组合唯一性。

示例:

UNIQUE KEY uk_user_item (userId, itemId)

前提:

业务确实规定同一玩家同一种普通道具只能有一条汇总记录。

如果每个道具是独立实例,例如每把武器有不同强化等级、耐久度和随机属性,则需要另外设计实例表。


第12课练习题5:外键与普通索引


得分:10/10。

用户答案:B。

用户解释:

外键用于约束表之间的引用关系,普通索引主要用于帮助查询。

掌握点:

外键用于维护引用完整性。

普通索引主要用于提高查询效率,本身不能保证被引用的对象存在。

实际游戏服务端是否使用外键,需要结合分库分表、跨服务写入和数据维护方式决定。


第12课练习题6:一对多关系


得分:10/10。

用户答案:B。

用户解释:

玩家只有一个,但是这个玩家的道具记录可以有很多条。

掌握点:

一个玩家可以对应多条 player_item 记录。

每条背包记录通常属于一名玩家。


第12课练习题7:多对多关系


得分:10/10。

用户答案:C。

用户解释:

player 是玩家表,item_config 是道具表,player_item 是道具记录表。

player 中 userId 代表玩家。

item_config 中 itemId 代表道具。

player_item 中通过 userId 和 itemId 来确定玩家拥有的道具情况。

掌握点:

玩家与道具类型之间通常是多对多关系。

通过 player_item 关联表,可以记录玩家拥有哪些道具,以及相应数量等信息。


第12课练习题8:联合主键与业务唯一性


得分:10/10。

用户答案:B。

用户解释:

主键和业务数据是完全独立的,自增主键的唯一作用是生成一个永不重复的行号,只能保证自身不重复,但完全无法防止业务层面的重复记录。

掌握点:

自增主键只能保证自身标识的唯一性,不能替代业务唯一约束。

示例:

CREATE TABLE player_item (

id BIGINT PRIMARY KEY AUTO_INCREMENT,

userId BIGINT NOT NULL,

itemId INT NOT NULL,

count INT NOT NULL DEFAULT 0,

UNIQUE KEY uk_user_item (userId, itemId)

);

这只是教学示例,实际建表前必须核对业务规则、现有数据和表结构。


第12课练习题9:数据冗余与历史快照


得分:10/10。

用户答案:B。

用户解释:

如果将所有信息堆砌在一张大表中,会导致大量重复存储。

例如玩家A有100个订单,如果每个订单都重复保存玩家A的用户名,那么玩家改名时可能需要修改100条记录。如果漏改一条,就会产生数据不一致。

历史快照的意义在于保存交易发生时的关键状态。

例如,订单发生时 itemId 101 代表木剑,后来配置修改成盾牌。如果订单没有保存必要的历史快照,就可能无法正确还原交易当时的内容。

掌握点:

  1. 规范化有助于减少不必要的数据冗余。

  2. 重复保存可变信息可能产生更新异常。

  3. 历史订单的商品名称、成交价格等快照可能是必要的业务数据。

  4. 不能机械地删除所有重复信息,必须结合业务目的判断。


第12课练习题10:函数依赖与传递依赖


得分:10/10。

用户答案:B。

用户解释:

userId → guildId:

每个玩家只能加入一个公会,所以玩家 ID 决定其所属公会 ID。

guildId → guildName:

每个公会 ID 对应唯一的公会名称。

存在传递依赖:

userId → guildId → guildName

掌握点:

当玩家表中保存 guildName,而 guildId 又决定 guildName 时,就需要考虑传递依赖和数据冗余问题。

可以把公会信息拆分到独立 guild 表中,通过 guildId 关联。


第12课练习题11:部分函数依赖与第二范式


得分:10/10。

用户答案:B。

用户解释:

部分函数依赖是指在联合主键的场景下,某个非主键字段只依赖于联合主键中的某一部分字段,而不需要依赖整个联合主键。

公会成员表:

userId

guildId

userName

guildName

joinTime

联合主键:

(userId, guildId)

业务规则:

userId 单独决定 userName。

guildId 单独决定 guildName。

因此:

userName 只依赖联合主键的真子集 userId。

guildName 只依赖联合主键的真子集 guildId。

这属于部分函数依赖。

joinTime 则需要同时知道玩家 ID 和公会 ID,才能确定玩家加入该公会的时间,因此依赖整个联合主键。

第二范式的核心要求:

表必须先满足第一范式,并且消除非主属性对候选键的部分函数依赖。

用户还理解了不满足第二范式可能产生的数据冗余和更新异常。

掌握点:

  1. 能解释部分函数依赖。

  2. 能结合联合主键判断部分依赖。

  3. 理解第二范式与第一范式的区别。

  4. 能分析部分函数依赖导致的数据冗余。


第12课练习题12:第三范式综合判断


当前状态:等待用户回答。

不要重新出题。

用户提交答案后直接批改。

题目背景:

某游戏数据库有一张玩家公会表:

userId

userName

guildId

guildName

业务规则:

  • userId 是主键。

  • 每名玩家最多加入一个公会。

  • userId → userName。

  • userId → guildId。

  • guildId → guildName。

选项:

A. 这张表一定满足第三范式,因为主键只有一个字段。

B. guildName 通过 guildId 间接依赖 userId,存在传递依赖;应考虑把公会信息拆分到独立表中。

C. 只要给 guildName 建立普通索引,就可以消除传递依赖。

D. 只需要将 userId 改成联合主键,就可以满足第三范式。

预期正确答案:B。

注意:

预期答案仅用于后续批改。当前题目尚未回答,不要把它记作已完成,也不要提前给用户公布答案。


第12课成绩统计


第1题:10/10

第2题:10/10

第3题:9/10

第4题:10/10

第5题:10/10

第6题:10/10

第7题:10/10

第8题:10/10

第9题:10/10

第10题:10/10

第11题:10/10

累计成绩:

109/110。

平均分:

约99.1分。

第12题尚未回答,不计入累计成绩。

============================================================

七、第12课知识点总结

============================================================

一、第一范式(1NF)

核心理解:

表中的字段应具有合理的原子性,不应将需要独立查询、修改的多个业务项目随意堆放在一个字段中。

例如,不推荐用一个字符串字段保存玩家所有道具的复杂信息,再通过字符串分割进行各种业务处理。

对于玩家道具,可以使用单独的背包记录表。

二、第二范式(2NF)

核心要求:

表先满足第一范式,并且不存在非主属性对候选键的部分函数依赖。

典型场景:

联合主键为:

(userId, guildId)

如果:

userId → userName

guildId → guildName

那么 userName 和 guildName 分别只依赖联合主键的一部分,存在部分函数依赖。

应考虑把玩家信息、公会信息拆分到独立表中。

三、第三范式(3NF)

继续学习重点:

判断非主属性是否通过其他非主属性间接依赖候选键。

典型场景:

userId → guildId

guildId → guildName

因此:

userId → guildName

存在传递依赖。

通常可以将公会信息拆分到 guild 表,通过 guildId 关联。

注意:

第三范式有严格的形式化定义,涉及候选键、主属性和函数依赖。后续需要继续练习,不能只靠记忆简单口诀。

四、数据冗余与更新异常

如果同一公会的名称重复保存在大量玩家记录中,公会改名时可能需要修改大量记录。

漏改部分记录,就会产生不一致。

合理拆分数据可以降低这类问题。

五、规范化与反规范化

规范化可以减少不必要的冗余并改善数据一致性。

但在真实业务中,有些重复数据是有意保存的,例如订单发生时的商品名称、价格和其他历史快照。

不能机械地删除所有重复字段,应根据业务需求和数据生命周期判断。

============================================================

八、当前薄弱点汇总

============================================================

薄弱点1:复合索引最左前缀原则

需要继续理解:

为什么索引:

(level, gold)

通常能支持按 level 查询,但通常不能有效利用该索引的最左前缀进行仅按 gold 查询。

建议通过 EXPLAIN 和实际 SQL 练习。

薄弱点2:事务中的 affectedRows

需要牢牢记住:

SQL 成功执行但没有更新任何行,不代表发生 SQL 异常。

affectedRows = 0 时,应结合 WHERE 条件和业务逻辑分析原因。

薄弱点3:事务与幂等性

事务不能保证同一个订单或请求只执行一次。

需要考虑唯一约束、订单状态、重复请求检查等机制。

薄弱点4:默认隔离级别

必须记住:

MySQL InnoDB 默认事务隔离级别是 REPEATABLE READ。

薄弱点5:读视图与锁定读

需要继续理解:

READ COMMITTED 下每次普通一致性读通常建立新的读视图。

REPEATABLE READ 下同一事务的普通一致性读通常复用同一个读视图。

SELECT ... FOR UPDATE 属于锁定读,与普通一致性读不同。

薄弱点6:主键唯一性与业务唯一性

主键只保证记录标识唯一。

业务上是否允许重复,需要根据业务规则考虑 UNIQUE 等约束。

薄弱点7:独立道具实例与道具类型

道具配置表示道具的类型定义。

玩家背包表表示玩家拥有的道具或道具数量。

如果每把武器都有独立属性、强化等级、耐久度或随机词条,就需要考虑独立道具实例设计,不能简单地把所有道具都按 (userId, itemId) 汇总。

薄弱点8:第二范式与第三范式

需要继续巩固:

  • 候选键

  • 主属性和非主属性

  • 部分函数依赖

  • 完全函数依赖

  • 传递依赖

  • 第二范式和第三范式的严格判断条件

当前第12课第3题扣1分,反映出对主键、道具唯一性和玩家关联关系的解释还需要更精确。

============================================================

九、重要 SQL 示例

============================================================

一、条件扣款

UPDATE player

SET gold = gold - 800

WHERE userId = 1001

AND gold >= 800;

用途:

余额判断与扣款在同一条 UPDATE 中完成。

执行后需要检查 affectedRows。

真实操作前,必须核对表结构、余额字段类型、用户标识和业务规则。

二、复合索引

CREATE INDEX idx_level_gold

ON player(level, gold);

用途:

帮助符合索引结构和查询条件的查询。

注意:

索引是否使用需要结合 SQL 写法、数据分布和 EXPLAIN 结果判断。

三、设置当前会话隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

查看当前隔离级别:

SELECT @@transaction_isolation;

旧版本可能使用:

SELECT @@tx_isolation;

四、事务基本结构

START TRANSACTION;

-- 在这里执行经过核对的业务 SQL

COMMIT;

发生错误时,在适当的异常处理中尝试 ROLLBACK,并确保连接被释放。

五、玩家背包表示例

CREATE TABLE player_item (

id BIGINT PRIMARY KEY AUTO_INCREMENT,

userId BIGINT NOT NULL,

itemId INT NOT NULL,

count INT NOT NULL DEFAULT 0,

UNIQUE KEY uk_user_item (userId, itemId)

);

说明:

这是"同一玩家同一种普通道具只保留一条汇总记录"的设计示例。

如果道具是具有独立属性的实例,应采用适合独立实例的设计。

真实建表前必须核对现有表结构、数据及业务规则。

六、玩家、公会与成员关系表示例

CREATE TABLE guild (

guildId BIGINT PRIMARY KEY,

guildName VARCHAR(100) NOT NULL

);

CREATE TABLE player (

userId BIGINT PRIMARY KEY,

userName VARCHAR(100) NOT NULL,

guildId BIGINT NULL

);

CREATE TABLE guild_member (

userId BIGINT NOT NULL,

guildId BIGINT NOT NULL,

joinTime DATETIME NOT NULL,

PRIMARY KEY (userId, guildId)

);

说明:

如果业务规定每名玩家最多加入一个公会,可以考虑在 player 中保存 guildId。

如果业务需要保存玩家加入公会的历史,或者允许玩家同时加入多个公会,则可以使用 guild_member 关联表。

示例中的两种关系建模方式需要根据业务选择,不要同时机械照搬。

真实建表前,应核对现有表结构、数据、外键关系及业务规则。

============================================================

十、后续学习计划

============================================================

第一步:

继续批改第12课练习题12。

要求:

  • 按10分制评分。

  • 判断答案是否正确。

  • 分析用户解释。

  • 解释全部选项。

  • 更新第12课累计成绩。

  • 更新当前薄弱点。

  • 不要重复提问。

第二步:

继续完成第12课剩余练习。

建议覆盖:

  1. 第三范式综合判断。

  2. 第二范式与第三范式的区别。

  3. 函数依赖、部分函数依赖、传递函数依赖。

  4. 候选键、主键和复合键。

  5. 玩家、道具、公会、订单等游戏服务端表设计。

  6. 规范化与适度反规范化。

  7. 结合业务设计完整表结构。

  8. 主键、唯一约束、外键、字段类型和索引的选择。

第三步:

第12课结束后,必须总结:

  • 本课知识点

  • 本课总成绩

  • 已掌握内容

  • 错题及原因

  • 仍需复习的内容

  • 下一课学习计划

第四步:

根据实际掌握情况规划下一课,不要跳过当前尚未回答的题目。

============================================================

十一、未来更新进度的规则

============================================================

每完成一课或一组练习,请更新本笔记。

必须记录:

  • 课程名称

  • 课程完成状态

  • 每道题的考查主题

  • 用户原始答案

  • 用户的解释

  • 正确答案

  • 本题得分

  • 错题和错误原因

  • 本课累计成绩

  • 已掌握的知识点

  • 仍需复习的薄弱点

  • 下一步学习计划

注意:

  1. 不要把未回答的题目当成已完成。

  2. 不要伪造没有记录的历史成绩。

  3. 如果用户答错,应在后续安排针对性复习题。

  4. 每次只出一道练习题。

  5. 用户已经提交答案时,直接批改。

  6. 如果新聊天中的最新记录与旧笔记不同,以最新记录为准。

  7. 涉及真实数据库修改时,提醒先检查表结构、数据和影响范围,不要直接在生产库执行未经确认的 SQL。

============================================================

十二、当前学习状态(供新聊天快速恢复)

============================================================

我正在系统学习 MySQL。

第1课至第5课已经完成,详细成绩未完整保留。

第6课 JOIN:100/100。

第7课子查询:100/100。

第8课索引:100/100。

第9课主键、唯一约束、复合索引:已学基础,早期部分练习60/100。

第10课事务:100/120。

第11课事务隔离级别与并发问题:第一轮80/100,综合巩固60/60。

第12课数据库表设计与规范化:进行中,目前109/110。

当前最重要的学习任务:

第12课练习题12《第三范式综合判断》。

题目已经发出,等待我回答。

新聊天助手应先根据我提交的第12题答案进行批改,而不是从头讲解 MySQL,也不要重新出第12题。

本笔记结束。

相关推荐
geovindu6 小时前
sql:Search-Optimized SQL Queries using mysql 9.0
android·数据库·sql·mysql·数据库开发·数据库架构
for_ever_love__6 小时前
MySQL SELECT 与 WHERE 优化实战:可搜索条件、联合索引与执行计划
数据库·mysql·oracle·select·where·条件优化·查询优化技巧
2501_931803756 小时前
MySQL 锁机制详解
数据库·mysql
.Hypocritical.9 小时前
MySQL |常用 SQL 语句与 pymysql 函数
mysql
NineData9 小时前
MySQL 到 Doris 实时数据同步:订单、用户和日志数据入仓方案
数据库·mysql
Dovis(誓平步青云)11 小时前
现场问题先关闭还是先验收,建筑任务怎样处理依赖
前端·javascript·数据库·mysql·音视频
xuefuhe11 小时前
MySQL:group_replication_start_on_boot vs group_replication_bootstrap_group
mysql
for_ever_love__12 小时前
MySQL Online DDL 完全指南:INSTANT/INPLACE/COPY 三种算法、MDL 锁事故与 gh-ost 实战
数据库·mysql·ddl·online·gh-ost·锁表·加字段
for_ever_love__12 小时前
MySQL 子查询、派生表与 CTE 优化:半连接、物化和递归实战
数据库·mysql·优化·as·子查询·with·cte