1. 为什么从单表开始一张小名著人物表能解决所有入门问题1.1 单表操作是所有SQL语句的地基前阵子一位刚开始学数据库的朋友跟我诉苦看教程看到联表查询就彻底懵了。我直接跟他说你急什么多表查询是后面的事MySQL入门阶段最该做的就是把单表操作吃透。单表操作顾名思义就是对一张独立的表做增删改查所有动作都发生在这一个表上。别小看这一步你以后在业务代码里看到的那些看似复杂的SQL剥掉外壳之后核心仍然是对表里的一行行数据做筛选、排序、统计和加工。拿做饭打比方单表操作就是洗菜、切菜、热锅。你连菜都切不利索就别急着研究宴席怎么摆盘。数据库再花哨最终落点都是四件事把想要的行从表里查出来把新行放进去把不对的行改掉把不要的行删掉。SELECT、INSERT、UPDATE、DELETE、WHERE、ORDER BY、LIMIT、GROUP BY这些关键字全部都能在单表场景里先练明白。这四件事做扎实了后面学JOIN、子查询、索引优化都是水到渠成的事。这篇文章就是这个系列的第一篇核心是两件事把SQL语句的基本分类和单表增删改查讲透然后拿一套名著风格的小数据做练习。零基础可以跟着一步步敲用过MySQL但没系统梳理过的人也能从中查漏补缺。1.2 用来练手的人物档案表为了让练习不枯燥我这次自己做了一张迷你名著人物表。虚构了一部架空古典小说《山河拾遗录》把里面出场的主要角色整理成一张人物档案表。用带情境的数据练SQL有个明显好处每个查询都是有意义的。比如找出武力值最高的前五名统计哪个门派人多而不是面对一堆无意义的数字发呆。这张表叫 characters存放12个角色的完整信息字段包括姓名、性别、年龄、门派、称号、武学类别、武力值、生日和建档时间。数据预览如下idnamegenderagesecttitleskill_typepower_levelbirthday1沈青梧女25青梧剑派青梧剑仙剑术881999-03-032沈白水女27青梧剑派白水剑客剑术761997-08-143陆悬河男29悬河书斋悬河先生书法化形821995-07-154陆小泠女23听雨楼小泠姑娘音波功692001-12-055叶惊蝉女22听雨楼惊蝉客暗器792002-05-216燕孤鸿男35北境孤鸿堡孤鸿堡主长枪911990-01-097云渺女27青云观渺渺真人阵法841997-09-018岳凌霜男29凌霜阁凌霜剑客剑术861995-12-029萧听雨男40听雨楼听雨楼主掌法891985-06-1810苏晚照女24悬河书斋晚照书童书法化形582000-08-1111秦不悔男26青云观不悔道人阵法811998-10-1012温如言女21凌霜阁如言琴师音波功632003-11-23先说明一句年龄、生日都是我拍脑袋定的设定值练SQL而已不用拿历法较真。把数据整体扫一遍你会发现几个有意思的点有人同姓比如沈青梧和沈白水有人同门听雨楼一下子就占了三位有人武力值特别高燕孤鸿91分一骑绝尘也有人还是萌新苏晚照只有58分。这些细节都是后面练习题的素材。2. 动手前先分清SQL四大类语句到底各管什么2.1 DDL、DML、DQL、DCL的分工很多人学SQL一上来就背关键字背完就忘因为脑子里没有分类框架。其实SQL语句按功能分四大类搞清楚这个分类你看到一条语句就能判断它属于哪个环节也自然知道我该用哪句。分类中文含义常见语句管的事DDL数据定义语言CREATE、ALTER、DROP定义和修改表结构比如建表、加字段、删表DML数据操纵语言INSERT、UPDATE、DELETE对表里的数据做增、改、删DQL数据查询语言SELECT查询数据日常工作中用得最多DCL数据控制语言GRANT、REVOKE管理用户权限入门期了解即可你可以这么理解DDL是装修房子决定房子有几间屋、窗户开在哪、门朝哪边开DML是往屋里摆家具、换家具、扔家具DQL是看看屋里现在到底有什么DCL则是决定谁能进这间屋。单表入门阶段我们重点操练的是DDL里的CREATE TABLE、DML里的INSERT/UPDATE/DELETE以及DQL里的SELECT。DCL暂时只用眼睛看一遍知道有这回事就行。2.2 连接数据库与建库开始前的小准备在写任何SQL之前要先能进到MySQL环境里。命令行下最常用的连接方式是mysql -u root -p输入密码后进入MySQL交互终端看到mysql提示符就说明连上了。新手记住三个探路命令就够了SHOW DATABASES; -- 看看目前有哪些数据库 SELECT VERSION(); -- 确认当前MySQL版本 SHOW CHARACTER SET; -- 查看支持的字符集练手最好单独建一个库别把练习数据混进工作相关的库。建库语句我习惯这么写CREATE DATABASE IF NOT EXISTS classic_practice DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;IF NOT EXISTS 的意思是如果这个库不存在才创建防止重复执行时报错。字符集直接指定 utf8mb4这一条非常关键。utf8mb4是MySQL当前的中文标配字符集支持完整的中文和各类特殊字符。你如果偷懒不写很可能建出来的库是 latin1 或者旧的 utf8后面插入中文就会出现乱码或者???。这个问题我会在第8章专门展开讲。建好库之后用 USE 切进去USE classic_practice;之后你写的所有建表、插数据、查询语句默认都在这个库里执行。确认一下当前执行环境是哪个库也是很重要的习惯否则你可能在错误的库里建了一堆表后面越查越乱。3. 建表不只是写字段类型和约束决定了你后面能怎么查3.1 完整的建表语句长什么样这是本篇最核心的一段SQL建议对着敲一遍不要直接复制一个个字段打出来印象完全不一样CREATE TABLE characters ( id INT UNSIGNED AUTO_INCREMENT COMMENT 角色编号, name VARCHAR(20) NOT NULL COMMENT 角色姓名, gender ENUM(男,女) NOT NULL COMMENT 性别, age TINYINT UNSIGNED NOT NULL COMMENT 年龄, sect VARCHAR(30) NOT NULL COMMENT 所属门派, title VARCHAR(30) DEFAULT NULL COMMENT 称号, skill_type VARCHAR(20) NOT NULL COMMENT 武学类别, power_level TINYINT UNSIGNED NOT NULL COMMENT 武力值, birthday DATE DEFAULT NULL COMMENT 生日, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 建档时间, PRIMARY KEY (id), UNIQUE KEY uk_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT《山河拾遗录》人物档案表;执行成功之后可以用SHOW CREATE TABLE characters;查看MySQL实际保存的表定义也可以DESC characters;看字段清单。两条命令在排查建表问题时非常有用。3.2 每个字段类型为什么这么选逐段拆解类型选择背后的逻辑这是很多教程不会讲的。id 用 INT UNSIGNED AUTO_INCREMENT。INT是常规整数UNSIGNED表示非负把取值范围全部留给正数。AUTO_INCREMENT是自增由数据库自动编号插入数据时不用管它。为什么不用姓名当主键因为姓名理论上可能重复而且角色一旦改名所有关联引用都得跟着改。一个与业务无关、永远不变的编号才是最稳定的主键。name 用 VARCHAR(20) NOT NULL。VARCHAR是可变长字符串存几个字就占几个字的空间不像CHAR那样固定占满。长度20对中文名完全够用。这里有个新手常犯的毛病无脑写 VARCHAR(255)。字段长度越长行存储和索引的成本越高够用就好。gender 用 ENUM(男,女)。枚举类型会在数据库层面限制这个字段只能填男或女填别的直接报错好处是防止脏数据。坏处是以后如果要加第三个取值得用 ALTER TABLE 修改枚举定义。如果你觉得麻烦用 VARCHAR(2) 也行只是少了这层约束。入门阶段我建议用ENUM顺便把约束这个概念理解掉。age 和 power_level 用 TINYINT UNSIGNED。年龄、武力值这种小范围整数没必要用INT。TINYINT UNSIGNED取值0到255完全够用还省空间。这是初学者最容易忽略的数据库选型里够用且最小是一种习惯。birthday 用 DATE。日期必须用专门的日期类型不要拿字符串硬存。用DATE之后你可以直接比较大小、算日期差、按日期排序用字符串存这些操作都会变得非常别扭还得先转换格式。created_at 用 DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP。记录档案创建时间默认取当前时间插入时无需手动赋值。这类审计字段在很多业务表里都是标配。3.3 主键、唯一键、非空与默认值约束背后的设计逻辑建表时那几行约束纯粹是为了管住数据。PRIMARY KEY(id) 是主键一个表只能有一个主键它唯一标识一行MySQL会自动为主键建索引按主键查询走的是最快路径。UNIQUE KEY uk_name(name) 是唯一键保证姓名不重复从业务上讲两个角色重名确实容易混乱。NOT NULL 表示该字段不能为空一个角色不可能没有年龄DEFAULT NULL 表示允许为空比如称号有些人确实没有称号。DEFAULT CURRENT_TIMESTAMP 则是不填就自动带当前时间。这里有个经验之谈约束越清晰后面的脏数据越少。我见过太多表设计阶段图省事什么约束都不加结果到了数据分析阶段天天花时间清洗垃圾数据。建表时多花两分钟想清楚这个字段允许空吗、能不能重复、取值范围是什么后面省的是成倍的时间。4. 写入数据INSERT的两种写法与五个常见翻车现场4.1 单行写入与多行批量写入表建好之后第一步就是灌数据。最基础的单行插入长这样INSERT INTO characters (name, gender, age, sect, title, skill_type, power_level, birthday) VALUES (沈青梧, 女, 25, 青梧剑派, 青梧剑仙, 剑术, 88, 1999-03-03);注意两点。第一字段列表里没有写 id 和 created_at因为一个自增、一个有默认值插入时可以被跳过。第二VALUES 后面的每个值顺序必须和前面字段列表一一对应。字符串和日期都必须用单引号括起来这是SQL的硬性要求。一条条插太慢MySQL支持一条INSERT插入多行用逗号分隔即可。两张表的数据我一次灌进去INSERT INTO characters (name, gender, age, sect, title, skill_type, power_level, birthday) VALUES (沈青梧, 女, 25, 青梧剑派, 青梧剑仙, 剑术, 88, 1999-03-03), (沈白水, 女, 27, 青梧剑派, 白水剑客, 剑术, 76, 1997-08-14), (陆悬河, 男, 29, 悬河书斋, 悬河先生, 书法化形, 82, 1995-07-15), (陆小泠, 女, 23, 听雨楼, 小泠姑娘, 音波功, 69, 2001-12-05), (叶惊蝉, 女, 22, 听雨楼, 惊蝉客, 暗器, 79, 2002-05-21), (燕孤鸿, 男, 35, 北境孤鸿堡, 孤鸿堡主, 长枪, 91, 1990-01-09), (云渺, 女, 27, 青云观, 渺渺真人, 阵法, 84, 1997-09-01), (岳凌霜, 男, 29, 凌霜阁, 凌霜剑客, 剑术, 86, 1995-12-02), (萧听雨, 男, 40, 听雨楼, 听雨楼主, 掌法, 89, 1985-06-18), (苏晚照, 女, 24, 悬河书斋, 晚照书童, 书法化形, 58, 2000-08-11), (秦不悔, 男, 26, 青云观, 不悔道人, 阵法, 81, 1998-10-10), (温如言, 女, 21, 凌霜阁, 如言琴师, 音波功, 63, 2003-11-23);写完跑一句SELECT COUNT(*) FROM characters;返回12就说明全部进去了。多行插入相比一行行写省的不只是打字时间。生产环境下需要批量导入数据时一次INSERT多行的性能优势非常明显能减少大量客户端与数据库之间的交互开销。4.2 写入时的五个常见翻车现场插数据阶段最容易翻车的几个点我挨个说一遍第一字符串没加引号或者用了双引号。SQL标准里字符串用单引号虽然MySQL默认也接受双引号但养成单引号的习惯最稳妥免得以后换到其他数据库时踩坑。数字类型的值可以不加引号但为了统一我通常除了纯数值都加引号。第二违反非空约束。比如 age 没填表定义里明确写了 NOT NULLMySQL会直接报错Field age doesnt have a default value。字段列表和值数量对不上时会报 Column count doesnt match value count at row 1。所以写INSERT时字段列表一定列清楚别偷懒。第三违反唯一键。name 字段有唯一键约束如果你第二次插入沈青梧会报 Duplicate entry 沈青梧 for key uk_name。看到这个报错不要慌它恰好说明约束在起作用数据库帮你挡住了重复数据。第四手工指定自增id。新手偶尔会写 VALUES (1, 沈青梧, ...)把id也填进去。MySQL允许这么做但有个隐患下次不指定id插入时自增序号会从你填过的最大值往后继续跳有时候直接跳出一大截让不明所以的人以为序号怎么缺了。练习阶段尽量别手动填自增列。第五中文变成问号。这就是建库时没指定 utf8mb4 的后果。如果已经踩了补救方式是把库、表、字段的字符集都改掉再重新插入数据具体写法在第8章。插完数据后一定要回头看一眼实际情况执行SELECT * FROM characters;把整张表打出来确认和预期一致这一步才算闭环。5. 查询是重头戏SELECT加WHERE把表玩出花来5.1 基础投影、别名与去重SELECT是SQL里出场率最高的语句。先认识它的基本结构查哪些列从哪张表查加什么过滤条件按什么排序取多少行。最简单的查全表是SELECT * FROM characters;星号代表所有列。但工作中我强烈建议写列名不要动不动 SELECT *。写列名一是输出清晰二是减少数据传输。比如只查姓名、门派、武力值SELECT name, sect, power_level FROM characters;想给查询结果的列换一个显示名用 AS 起别名。别名不影响原表数据只是让结果更易读。中英文别名都可以中文别名记得加引号或者直接使用SELECT name AS 姓名, power_level AS 战力 FROM characters;去重用 DISTINCT 关键字。比如我想知道这张表里一共涉及几个门派SELECT DISTINCT sect FROM characters;返回青梧剑派、悬河书斋、听雨楼、北境孤鸿堡、青云观、凌霜阁正好6个门派。注意DISTINCT是对整行去重单独某一列重复不算。5.2 条件筛选比较、逻辑、模糊、区间WHERE 是整个查询的灵魂。先看比较运算符SELECT name, power_level FROM characters WHERE power_level 85;这条查所有武力值大于85的角色返回沈青梧88、燕孤鸿91、岳凌霜86、萧听雨89。大于、小于、不等于的写法都类似、、、、、! 和 都表示不等于MySQL两种都认。多条件组合用 AND、OR、NOT。比如查青梧剑派或者凌霜阁的女角色SELECT name, sect, gender FROM characters WHERE (sect 青梧剑派 OR sect 凌霜阁) AND gender 女;括号一定不能省。不加括号的话AND的优先级比OR高整条语句会被理解成青梧剑派的所有人再加上凌霜阁的女角色结果完全不一样。这是新手高频踩坑点必须记牢。区间查询用 BETWEEN比如查武力值在80到90之间含边界的角色SELECT name, power_level FROM characters WHERE power_level BETWEEN 80 AND 90;集合匹配用 IN上面两个门派的条件用IN写更清爽SELECT name, sect FROM characters WHERE sect IN (青梧剑派, 凌霜阁);模糊匹配用 LIKE配合两个通配符% 代表任意多个字符_ 代表任意单个字符。查所有称号里带剑字的角色SELECT name, title FROM characters WHERE title LIKE %剑%;返回沈青梧青梧剑仙、沈白水白水剑客、岳凌霜凌霜剑客三行。查姓沈的角色就是name LIKE 沈%。查两个字的名字则用name LIKE __也就是两个下划线。这个技巧平时用得不算多但面试题里经常出现建议亲手跑一次加深印象。NULL的判断要单独拎出来讲。NULL在SQL里是未知不能用等号判断。你写WHERE title NULL永远查不出数据必须写成SELECT name FROM characters WHERE title IS NULL; SELECT name FROM characters WHERE title IS NOT NULL;我们这张表恰好所有角色都有称号所以第一条返回空结果第二条返回全部12个角色。这个坑新手必踩没有例外。想真正理解它可以自己UPDATE一行把title改成NULL再分别试。5.3 排序与限量ORDER BY和LIMIT的配合查询结果的默认顺序是没有保证的想按某种规则排列就用 ORDER BY。武力值从高到低SELECT name, power_level FROM characters ORDER BY power_level DESC;DESC是降序ASC是升序默认就是ASC可以省略。想要多列排序就逗号分隔比如先按门派排同门派内按武力值降序SELECT name, sect, power_level FROM characters ORDER BY sect ASC, power_level DESC;LIMIT 用来限量取行。取武力值最高的前3名SELECT name, power_level FROM characters ORDER BY power_level DESC LIMIT 3;这里要特别注意书写顺序WHERE 先过滤ORDER BY 排序LIMIT 最后限量。顺序写错MySQL直接报语法错误。LIMIT 后面还可以接两个参数比如LIMIT 2, 5表示跳过前2行取接下来5行分页场景常用。入门阶段先掌握单参数写法就够。6. 改与删UPDATE/DELETE的安全红线6.1 UPDATE的精确打击数据不是一成不变的改数据用UPDATE。基本语法UPDATE characters SET power_level 90 WHERE name 沈青梧;这句话的意思是把 name 等于沈青梧的那一行的武力值改成90。SET 后面可以跟多个字段用逗号分隔UPDATE characters SET power_level power_level 5, title 青梧剑圣 WHERE name 沈白水;power_level power_level 5是在原值基础上加5非常常用的写法。SET里的字段顺序无所谓多个字段更新是原子的要么全部成功要么全部失败。这里必须划一条红线UPDATE 一定要带WHERE。不带WHERE等于无差别修改表中所有行。我早期真实干过这种事本来想把某个角色的武力值清零结果漏写了WHERE一整表数据全被清零最后只能从备份恢复。那之后我养成了一个习惯执行UPDATE之前先跑一遍同样条件的SELECT确认我打算改哪些行再执行UPDATE。6.2 DELETE与TRUNCATE的区别删数据用DELETE语法和UPDATE类似WHERE同样是安全红线DELETE FROM characters WHERE name 温如言;不带WHERE的DELETE FROM characters;会把整表数据清空但表结构仍然存在。DELETE和另一个清空命令TRUNCATE要区分开它们的区别在实战中非常重要对比项DELETETRUNCATE能否带WHERE能精确删除指定行不能只能整表清空是否走事务走可以回滚不走执行后不可回滚自增计数保留原计数继续往后排自增计数重置速度逐行删相对慢重建表速度快一句话总结DELETE是选择性删行TRUNCATE是整表推倒重来。入门阶段绝大多数情况你只需要DELETE。TRUNCATE和DROP直接连表带结构一起删掉这两类操作我建议在任何非练习环境里都要极其谨慎执行前反复确认。6.3 保护数据的三条军规结合我自己的教训改删操作前请遵守这三条能救你无数次。第一先SELECT再UPDATE/DELETE。用相同WHERE条件先查出结果集肉眼确认影响行数和目标数据再执行改删。第二没有WHERE的UPDATE/DELETE一律视为非法操作。每次写完这两类语句都回头看一眼有没有WHERE。我的个人习惯是先把WHERE写好再回去补前面的语句。顺序反过来写最不容易漏。第三重要操作放在事务里。MySQL的InnoDB引擎支持事务可以这样操作BEGIN; UPDATE characters SET power_level power_level 10 WHERE name 叶惊蝉; SELECT * FROM characters WHERE name 叶惊蝉; -- 确认结果 ROLLBACK; -- 不对就回滚 -- COMMIT; -- 确认无误再提交BEGIN 开始一个事务执行完发现不对可以 ROLLBACK 回滚确认没问题再 COMMIT 提交。练习环境多ROLLBACK几次没坏处这是你对后悔药的熟悉过程也是未来处理真实数据时的保命技能。7. 名著小练习五道题由易到难附完整解题思路7.1 热身题门派检索与武力排行题目一查询武力值大于85的角色只要姓名、门派、武力值三列按武力值从高到低排序最多取前5行。先拆解需求过滤条件是武力值大于85排序是武力值降序限量是最多5行。SQL如下SELECT name, sect, power_level FROM characters WHERE power_level 85 ORDER BY power_level DESC LIMIT 5;执行结果只有4行燕孤鸿91、萧听雨89、沈青梧88、岳凌霜86。为什么取前5只出了4行因为武力值大于85的角色一共就4个。LIMIT 5的意思是最多返回5行不是必须返回5行。这个细节理解了以后写分页就不会搞错。题目二查询听雨楼和凌霜阁两个门派中武力值不低于80的角色。这里有两个筛选维度门派用IN武力值用两个条件用AND连接SELECT name, sect, power_level FROM characters WHERE sect IN (听雨楼, 凌霜阁) AND power_level 80;结果只有两行萧听雨89、岳凌霜86。注意听雨楼另外两位陆小泠69、叶惊蝉79以及凌霜阁的温如言63都因为武力值不足80被正确过滤掉了。这道题考的就是多条件组合时AND的用法以及条件之间的括号配合。7.2 进阶题模糊匹配与多条件组合题目三找出称号中含有剑字的角色以及姓名中姓氏为沈的角色两个条件满足其一即可输出姓名、称号、门派。满足其一就是OR。称号含剑字是title LIKE %剑%姓沈即name LIKE 沈%SELECT name, title, sect FROM characters WHERE title LIKE %剑% OR name LIKE 沈%;结果沈青梧、沈白水、岳凌霜三行。这里再提醒一次OR两侧的条件一定要各自写完整别省写成title LIKE %剑% OR 沈%。这种偷懒写法会把第二个条件当成普通字符串永远为真查出来就是整表数据。这种错误特别隐蔽查一次坑一次。题目四查询年龄在25岁到35岁之间、且不是青梧剑派的角色按年龄升序排列。区间用BETWEEN排除门派用 !。年龄升序用 ORDER BY age ASCASC可省略。SQLSELECT name, age, sect FROM characters WHERE age BETWEEN 25 AND 35 AND sect ! 青梧剑派 ORDER BY age ASC;结果依次是秦不悔26、云渺27、陆悬河29、岳凌霜29、燕孤鸿35。为什么沈青梧和沈白水不在里面因为两人都属青梧剑派被第二个条件排除了。为什么萧听雨不在里面因为他40岁超出区间。一个条件一个条件地核对是练习时最有效的验证方式。7.3 综合题统计聚合和分组合并题目五统计每个门派的人数按人数从多到少排序。这道题涉及两个新东西聚合函数COUNT用来计数GROUP BY用来按门派分组。这属于单表操作里从明细到汇总的升级入门篇先尝个鲜SELECT sect, COUNT(*) AS cnt FROM characters GROUP BY sect ORDER BY cnt DESC;结果听雨楼3人青梧剑派、悬河书斋、青云观、凌霜阁各2人北境孤鸿堡1人。COUNT(*) 是统计每组行数AS cnt 给结果列起别名方便后面的 ORDER BY 引用。看完这个结果门派规模排名一目了然。聚合函数后面还有SUM、AVG、MAX、MIN等这个系列下一篇再展开讲。五道题全部跑通之后建议自己再编几个问题比如平均武力值最高的门派是哪个有没有角色生日在同一个月。练习的意义不在于题海而在于把每条SQL里的每个关键字都弄明白为什么这么写。8. 实战中踩过的坑每一条都是真金白银的教训8.1 漏写WHERE一次让全表归零的乌龙这个坑前面提过但值得单独拿出来说因为它太典型了。当时我维护一张测试表想更新某一行的状态SQL写了一半被同事叫走回来接着写结果直接执行了一句不带WHERE的UPDATE整表数据瞬间被覆盖。万幸是测试环境要是发生在生产环境后果不敢想。从那以后我的固定动作是凡是UPDATE和DELETE写完先完整读一遍重点看WHERE执行前心里默念一遍WHERE在哪。另外能用事务就先开事务出问题直接ROLLBACK。这些习惯看着繁琐关键时刻真的是保命符。8.2 字符集没指定中文全部变成问号刚用MySQL那会儿我建库图省事直接CREATE DATABASE test;就完事。插入中文数据时存进去的全是???查出来也是???。原因就是库的默认字符集不支持中文。解决办法在第2章提过建库时明确指定DEFAULT CHARACTER SET utf8mb4。如果已经建错库补救也不复杂ALTER DATABASE classic_practice CHARACTER SET utf8mb4; ALTER TABLE characters CONVERT TO CHARACTER SET utf8mb4;但要注意CONVERT TO 会重写表数据线上大表执行时必须挑业务低峰期。更重要的是这类问题最好在建库那一刻就规避后面改字符集多出来的工作量都是当初省那两分钟的代价。8.3 NULL判断、保留字、自增值三个新手极易忽略的隐性坑第一个是NULL判断。WHERE title NULL查不出任何结果必须用 IS NULL。原因在于NULL是未知值任何和NULL的等值比较结果都是未知WHERE只保留结果为真的行。所以你要记住NULL不等于0也不等于空字符串它是一种未填写的状态。我建议你动手把某一行的title改成NULL再分别跑 IS NULL 和 NULL 两条语句亲眼看差别比背十遍都管用。第二个是保留字。SQL有些关键字不能直接用作表名、列名比如order、group、desc、rank。真要用可以用反引号包起来写成order但更推荐直接换一个不冲突的名字。新手最容易踩的是把表起名叫 order然后每次查询都报语法错误还以为是SQL写错了。建议所有字段名、表名起名时先过一遍是不是SQL保留字省得给自己埋雷。第三个是自增列跳号。AUTO_INCREMENT的值不会因为删了几行就补回去。你删掉id5再插入新行新行id是13而不是5这是正常行为别指望数字连续。想要业务上的连续编号是另外的场景不能依赖自增主键。我见过有人因为id怎么不连续把数据翻来覆去查了半天理解自增机制之后这根本不是问题。最后再分享一个我个人的习惯练习完一张表别急着扔。把当时的建表语句、练习SQL和踩坑记录存成一个文本下次遇到类似问题直接翻。学数据库没有太多捷径就是把基础操作反复练扎实把每次报错都当成学习材料读。这张《山河拾遗录》人物表从建表到增删改查全部跑过关你对MySQL单表操作的基本功就算正式立住了。