MySQL 学习进度总笔记
版本:2026-10-10(当前聊天进度更新版)
用途:记录 MySQL 学习进度,供以后新建 ChatGPT 聊天时恢复学习状态。
============================================================
一、给新聊天助手的指令
============================================================
我正在系统学习 MySQL。请根据这份笔记继续教学,不要从第一课重新开始。
请严格遵守以下教学要求:
-
使用中文讲解,尽量结合实际 SQL 示例和实际开发场景。
-
每一课应包含:
-
知识点讲解
-
SQL 语法和示例
-
实际开发用途
-
练习题
-
练习题必须逐题进行,一次只出一道题。
-
等我回答后,再批改当前题目,不要提前公布后续题目的答案。
-
每道题按 10 分制评分,并解释:
-
本题得分
-
我的答案是否正确
-
正确答案
-
为什么正确
-
我的答案错在哪里
-
其他选项为什么错误
-
每次批改后更新本课累计成绩,并记录错题和薄弱点。
-
每课结束时总结:
-
本课知识点
-
本课总成绩
-
已掌握的内容
-
需要复习的内容
-
下一课学习计划
-
如果我答错了某道题,后续应适当安排针对性复习题。
-
不要因为某个知识点曾经答对,就直接认定我已经完全掌握;应结合多道题的表现判断。
-
涉及真实数据库的 INSERT、UPDATE、DELETE、DDL 和事务操作时,提醒我先核对实际表结构、数据及影响范围,不要直接在生产库执行未经确认的修改。
-
如果我已经回答了当前题目,请直接批改,不要重新提问。
-
如果本笔记与当前聊天中的最新学习记录冲突,以当前聊天中的最新记录为准。
-
请持续帮助我记录学习进度,让我以后可以通过这份笔记继续学习。
-
不要猜测或伪造没有记录的历史成绩。
-
每次批改后,除更新分数外,还应记录用户的原始答案、解释、错误原因和后续复习重点。
============================================================
二、整体学习进度
============================================================
当前学习课程:
第12课《数据库表设计与规范化》。
当前学习位置:
第12课练习题12,等待用户提交答案。
第12课当前成绩:
109/110 分。
已完成题目:
第12课练习题1至练习题11。
当前待回答题目:
第12课练习题12《第三范式综合判断》。
下一步学习计划:
-
等待用户回答第12课练习题12。
-
根据用户的答案和解释进行评分。
-
解释全部选项,更新第12课累计成绩和薄弱点。
-
继续巩固第二范式、第三范式、函数依赖、传递依赖。
-
结合游戏服务端场景练习数据库表设计。
-
完成本课后总结成绩和知识点,再规划下一课。
已经学习或完成的课程:
第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;
事务可以把多条数据库操作组织为一个整体,以满足业务一致性要求。
例如,玩家之间转移金币可能涉及:
-
扣除玩家A的金币。
-
增加玩家B的金币。
-
记录交易信息。
如果这些操作必须同时成功,就需要考虑在同一个事务中执行。
二、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 中释放数据库连接
-
处理连接失败、回滚失败等异常情况
六、事务与幂等性
事务和幂等性不是同一个概念。
事务主要用于保证一组数据库操作的原子性。
幂等性主要用于防止同一个业务请求重复执行产生错误结果。
例如,订单支付或发奖业务,可以考虑:
-
唯一订单号
-
数据库唯一约束
-
订单状态检查
-
重复请求识别
-
必要的事务处理
需要继续巩固:
-
affectedRows = 0 不会自动抛出异常。
-
开启事务不代表并发问题自动消失。
-
事务、并发控制和幂等性各自解决不同的问题。
============================================================
五、第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课需要重点复习的内容:
-
InnoDB 默认隔离级别是 REPEATABLE READ,不是 READ COMMITTED。
-
REPEATABLE READ 下,同一事务的普通一致性读通常复用同一个读视图。
-
不要混淆普通一致性读与 SELECT ... FOR UPDATE 锁定读。
-
区分脏读、不可重复读和幻读。
-
事务不能单独保证所有并发扣款或重复请求都安全。
-
条件更新、事务、锁和幂等性需要根据业务组合使用。
-
死锁重试必须考虑幂等性。
============================================================
六、第12课:数据库表设计与规范化
============================================================
课程状态:进行中。
当前累计成绩:
109/110分。
已完成练习题:
第1题至第11题。
当前待回答:
第12题。
本课已经涉及:
-
数据库表设计的基本原则。
-
玩家表、道具配置表、玩家背包表的拆分。
-
第一范式与字段原子性。
-
主键与业务唯一约束。
-
联合唯一约束。
-
外键与普通索引。
-
一对多、多对多关系。
-
关联表。
-
数据冗余与更新异常。
-
历史订单快照。
-
函数依赖与传递依赖。
-
第二范式与部分函数依赖。
-
第三范式的初步判断。
第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 代表木剑,后来配置修改成盾牌。如果订单没有保存必要的历史快照,就可能无法正确还原交易当时的内容。
掌握点:
-
规范化有助于减少不必要的数据冗余。
-
重复保存可变信息可能产生更新异常。
-
历史订单的商品名称、成交价格等快照可能是必要的业务数据。
-
不能机械地删除所有重复信息,必须结合业务目的判断。
第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,才能确定玩家加入该公会的时间,因此依赖整个联合主键。
第二范式的核心要求:
表必须先满足第一范式,并且消除非主属性对候选键的部分函数依赖。
用户还理解了不满足第二范式可能产生的数据冗余和更新异常。
掌握点:
-
能解释部分函数依赖。
-
能结合联合主键判断部分依赖。
-
理解第二范式与第一范式的区别。
-
能分析部分函数依赖导致的数据冗余。
第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课剩余练习。
建议覆盖:
-
第三范式综合判断。
-
第二范式与第三范式的区别。
-
函数依赖、部分函数依赖、传递函数依赖。
-
候选键、主键和复合键。
-
玩家、道具、公会、订单等游戏服务端表设计。
-
规范化与适度反规范化。
-
结合业务设计完整表结构。
-
主键、唯一约束、外键、字段类型和索引的选择。
第三步:
第12课结束后,必须总结:
-
本课知识点
-
本课总成绩
-
已掌握内容
-
错题及原因
-
仍需复习的内容
-
下一课学习计划
第四步:
根据实际掌握情况规划下一课,不要跳过当前尚未回答的题目。
============================================================
十一、未来更新进度的规则
============================================================
每完成一课或一组练习,请更新本笔记。
必须记录:
-
课程名称
-
课程完成状态
-
每道题的考查主题
-
用户原始答案
-
用户的解释
-
正确答案
-
本题得分
-
错题和错误原因
-
本课累计成绩
-
已掌握的知识点
-
仍需复习的薄弱点
-
下一步学习计划
注意:
-
不要把未回答的题目当成已完成。
-
不要伪造没有记录的历史成绩。
-
如果用户答错,应在后续安排针对性复习题。
-
每次只出一道练习题。
-
用户已经提交答案时,直接批改。
-
如果新聊天中的最新记录与旧笔记不同,以最新记录为准。
-
涉及真实数据库修改时,提醒先检查表结构、数据和影响范围,不要直接在生产库执行未经确认的 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题。
本笔记结束。