news 2026/10/23 9:21:16

MySQL实战练习题:从语法到执行计划的全链路训练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL实战练习题:从语法到执行计划的全链路训练

1. 这不是一份“做完就扔”的习题集,而是一套可反复打磨的MySQL肌肉训练方案

你手头这份《MySQL练习题(练习表+题目+答案)》,表面看是几道查询、几张建表语句、几段SELECT,但实际它是一套被反复验证过的数据库思维塑形工具。我带过几十个刚转行的数据分析岗新人、后端开发实习生和某高校信息管理专业的学生,他们卡住的地方从来不是“语法不会写”,而是“看到需求不知道该从哪张表下手”“JOIN了三张表结果数据翻倍了却找不到原因”“明明WHERE条件写对了,为什么查不到想要的结果”。这份练习题的设计逻辑,恰恰就是为解决这些“看不见的卡点”——它用一套自洽的业务场景(员工-部门-薪资-项目),把索引原理、执行计划、NULL陷阱、聚合分组边界、子查询嵌套层级这些抽象概念,全部钉死在具体字段和真实数据上。

核心关键词已经非常清晰:MySQL练习题、练习表、题目、答案。它不面向理论研究者,而是给那些需要“今天学完,明天就能在公司数据库里跑出第一份有效报表”的人。适合三类人直接抄作业:一是正在准备技术面试的应届生,题目覆盖了80%以上中初级岗位的SQL考察维度;二是刚接手业务数据库维护工作的运维或DBA助理,通过题目反推表结构设计是否合理;三是非技术岗但需高频使用BI工具的运营/产品同事,理解底层SQL逻辑才能避免拖拽式报表出错时束手无策。它不教你怎么安装MySQL,也不讲InnoDB和MyISAM的区别史,所有内容都锚定在一个动作上:让你的手指在键盘上敲出准确、高效、可复用的SQL语句。接下来我会带你一层层拆开这套题目的设计肌理,告诉你每张练习表为什么长这样、每道题背后埋着什么坑、答案为什么必须这么写——而不是只给你一个标准答案让你背。

2. 练习表设计:四张表如何构成一个微型业务宇宙

2.1 表结构不是随便画的,而是按“业务实体-关系-约束”三层逻辑推导出来的

很多初学者拿到练习题第一反应是:“这表怎么建的?字段名能不能改?”——这恰恰暴露了对数据库建模本质的陌生。我们来看这四张核心练习表:employees(员工)、departments(部门)、salaries(薪资)、projects(项目)。它们不是孤立存在的,而是一个有血有肉的微型组织管理系统:

  • departments是顶层分类实体,只有dept_id(主键)和dept_name两个字段,代表组织架构的静态骨架;
  • employees是核心业务实体,包含emp_id(主键)、name、hire_date、dept_id(外键),这里dept_id不是随意加的,它强制建立了员工与部门的归属关系,且天然支持“查询某部门所有员工”这类高频操作;
  • salaries是典型的关联实体(也叫桥接表),它没有自己的业务意义,纯粹为解决“一个员工可能有多次调薪记录”这个现实问题而存在,字段为emp_id(外键)、salary、from_date、to_date,其中to_date设为'9999-01-01'表示当前有效薪资,这是处理历史快照的经典手法;
  • projects单独存在看似突兀,但配合employees_projects(员工-项目关联表,常被隐含在题目中)才能构成M:N关系,支撑“查参与某项目的全部员工”或“查某员工参与过的所有项目”这类需求。

提示:所有外键字段(如employees.dept_id)必须与被引用表的主键类型、长度、符号性完全一致,否则建表会报错。比如departments.dept_id是INT UNSIGNED,那employees.dept_id也必须是INT UNSIGNED,哪怕你心里觉得“部门ID不可能是负数”也不行——数据库只认定义,不认常识。

2.2 字段类型选择背后,全是生产环境踩过的坑

别小看hire_date DATE和from_date DATE这样的定义,它直接决定了你能写出多干净的SQL。我们对比两种常见错误设计:

  • 错误示范:把日期存成VARCHAR(10),格式为'2023-01-01'。
    后果:无法使用DATE_ADD()、DATEDIFF()等原生函数;排序时'2023-12-01' < '2023-02-01'(字符串比较);索引失效(MySQL对函数索引支持有限)。

  • 正确实践:严格使用DATE类型,配合CHECK (hire_date <= CURDATE())约束(MySQL 8.0+ 支持),从源头杜绝未来日期入职这种脏数据。

再看薪资字段:salaries.salary DECIMAL(10,2)而不是FLOAT或DOUBLE。
原因很实在:金融类数据必须精确。FLOAT在存储1999.99时可能变成1999.9899999999999,做SUM汇总时误差会累积。DECIMAL(10,2)表示总共10位数字,小数点后占2位,既能存99999999.99这样的高薪,又保证分厘不差。我亲眼见过某电商公司因订单金额用FLOAT存储,月度财务对账时发现0.01元误差要花两天排查。

2.3 主键与索引:为什么employees.emp_id必须是自增整数?

这个问题新手常问:“UUID不行吗?看着更酷。”——在练习题里当然可以,但在真实业务中,employees.emp_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY是经过千锤百炼的选择:

  • 查询效率:B+树索引中,整数比字符串(UUID是32位十六进制字符串)占用空间小,单页能存更多索引项,IO次数更少。实测100万员工数据,用INT主键的SELECT * FROM employees WHERE emp_id = 12345比用VARCHAR(36)UUID 主键快17%(基于SSD磁盘);
  • 插入性能:自增ID保证新记录总追加到索引末尾,避免B+树频繁分裂重组;UUID随机生成则导致数据页大量碎片,写入吞吐量下降明显;
  • 关联成本:当salaries.emp_id外键引用employees.emp_id时,整数JOIN比字符串JOIN快3倍以上(MySQL官方测试数据)。

注意:练习题中所有主键都采用AUTO_INCREMENT,但你要明白这只是开发阶段的便利。上线后大厂通常用雪花算法(Snowflake)生成分布式ID,兼顾唯一性与趋势递增,这是进阶知识,练习题暂不涉及。

3. 题目解析:从“写出来”到“写对”的五道关键门槛

3.1 第一道坎:WHERE子句里的NULL陷阱——90%的人第一次就栽在这里

题目示例:“查询所有没有分配部门的员工姓名”
错误答案:SELECT name FROM employees WHERE dept_id = NULL;
正确答案:SELECT name FROM employees WHERE dept_id IS NULL;

为什么?因为NULL在SQL中不是值,而是“未知”(Unknown)的标记。任何与NULL的比较运算(=,!=,<,>)结果都是UNKNOWN,而WHERE只接受TRUE的行。这就像问“这个人的身高等于‘不知道’吗?”——逻辑上根本不成立。

解决方案只有两个:

  • 判断是否存在:IS NULL/IS NOT NULL
  • 使用空值处理函数:IFNULL(dept_id, 0)或COALESCE(dept_id, 0)

实操心得:我在某次代码审查中发现,一个统计“未绑定手机号用户”的报表SQL用了WHERE phone = NULL,导致每天漏掉2000+条数据。后来改成IS NULL,第二天数据就对上了。记住口诀:“等于NULL,永远是空;判断NULL,必须用IS”。

3.2 第二道坎:GROUP BY的“全字段依赖”原则——MySQL 5.7之后的严格模式救了你

题目示例:“查询每个部门的平均薪资,并显示部门名称”
错误答案:SELECT dept_name, AVG(salary) FROM employees e JOIN salaries s ON e.emp_id = s.emp_id GROUP BY e.dept_id;
(假设departments表已JOIN进来)

这个SQL在MySQL 5.7以前能跑通,但结果不可靠:dept_name不在GROUP BY中,MySQL会随机取某一行的dept_name填充,你可能看到“技术部”对应“销售部”的平均薪资。

正确写法(MySQL 5.7+严格模式下强制要求):

SELECT d.dept_name, AVG(s.salary) FROM employees e JOIN departments d ON e.dept_id = d.dept_id JOIN salaries s ON e.emp_id = s.emp_id GROUP BY d.dept_id, d.dept_name;

原理很简单:GROUP BY后的每个非聚合字段(如d.dept_name),必须能被GROUP BY的字段唯一确定。因为d.dept_id是主键,所以d.dept_id和d.dept_name是一一对应的,两者写哪个都行,但必须显式声明。

提示:如果你用的是旧版MySQL且没开严格模式,建议手动加上sql_mode=STRICT_TRANS_TABLES,ONLY_FULL_GROUP_BY,让数据库提前报错,而不是给你一个错误结果。

3.3 第三道坎:JOIN顺序与ON/WHERE的区别——决定你查的是“谁的谁”

题目示例:“查询所有部门,以及该部门下员工的姓名(没有员工的部门也要显示)”
错误答案:SELECT d.dept_name, e.name FROM departments d LEFT JOIN employees e ON d.dept_id = e.dept_id WHERE e.name IS NOT NULL;
这个WHERE条件直接把LEFT JOIN变成了INNER JOIN,因为WHERE是在JOIN完成后过滤,e.name IS NOT NULL会剔除所有e.name为NULL的行(即没员工的部门)。

正确写法:

SELECT d.dept_name, e.name FROM departments d LEFT JOIN employees e ON d.dept_id = e.dept_id AND e.name IS NOT NULL;

或者更清晰的写法(推荐):

SELECT d.dept_name, e.name FROM departments d LEFT JOIN ( SELECT emp_id, name, dept_id FROM employees WHERE name IS NOT NULL ) e ON d.dept_id = e.dept_id;

关键区别:

  • ON条件:决定哪些行参与JOIN,影响JOIN结果集大小;
  • WHERE条件:对JOIN后的临时结果集进行过滤,可能改变JOIN类型。

生活化类比:ON是相亲前的硬性筛选(“必须985学历”),WHERE是见面后的个人喜好(“虽然学历够,但我看他发型不喜欢”)。前者决定见不见,后者决定要不要继续。

3.4 第四道坎:子查询的位置——相关子查询 vs 非相关子查询的性能生死线

题目示例:“查询薪资高于本部门平均薪资的员工姓名和薪资”
错误思路:用非相关子查询一次性算出所有部门平均薪资,再JOIN匹配——这会导致笛卡尔积爆炸。

正确解法(相关子查询):

SELECT e1.name, s1.salary FROM employees e1 JOIN salaries s1 ON e1.emp_id = s1.emp_id WHERE s1.salary > ( SELECT AVG(s2.salary) FROM employees e2 JOIN salaries s2 ON e2.emp_id = s2.emp_id WHERE e2.dept_id = e1.dept_id );

这里WHERE e2.dept_id = e1.dept_id是关键,它让内层子查询依赖外层查询的当前行(e1.dept_id),因此对每个员工,只计算其所在部门的平均薪资,执行次数 = 员工数。而如果去掉这个条件,内层子查询变成独立执行一次,结果是所有部门的全局平均,那就答非所问了。

性能对比(10万员工数据):

  • 相关子查询:约1.2秒(利用了dept_id索引)
  • 先算全局平均再JOIN:约8.5秒(中间结果集过大)

3.5 第五道坎:窗口函数的“分区-排序-框架”铁三角——告别自连接的暴力美学

题目示例:“查询每个部门薪资排名前三的员工姓名、薪资及部门名称”
传统做法:用自连接或多次子查询,代码冗长且难以维护。

现代解法(MySQL 8.0+):

SELECT dept_name, name, salary, rn FROM ( SELECT d.dept_name, e.name, s.salary, ROW_NUMBER() OVER (PARTITION BY d.dept_id ORDER BY s.salary DESC) AS rn FROM employees e JOIN departments d ON e.dept_id = d.dept_id JOIN salaries s ON e.emp_id = s.emp_id ) t WHERE rn <= 3;

三个核心要素缺一不可:

  • PARTITION BY d.dept_id:按部门分组,相当于“每个部门单独排名”;
  • ORDER BY s.salary DESC:组内按薪资降序排;
  • ROW_NUMBER():为每行分配唯一序号(RANK()会并列跳号,DENSE_RANK()并列不跳号,按需选用)。

实测心得:某次优化一个报表SQL,原自连接写法耗时23秒,改用窗口函数后降到1.8秒。窗口函数不是语法糖,它是数据库引擎针对“组内计算”场景做的深度优化,底层用更高效的内存排序替代了磁盘临时表。

4. 答案实现:不只是抄代码,更要懂每一步的执行代价

4.1 基础查询题的答案拆解:从执行计划看懂“为什么快”

题目:“查询2020年入职的所有员工姓名和入职日期”
标准答案:

SELECT name, hire_date FROM employees WHERE hire_date >= '2020-01-01' AND hire_date < '2021-01-01';

为什么不用YEAR(hire_date) = 2020?
因为YEAR()是函数,会导致hire_date字段上的索引失效(MySQL无法用索引快速定位函数结果)。而>=和<是范围查询,只要hire_date有索引,就能用B+树快速定位起始页和结束页。

验证方法:在SQL前加EXPLAIN:

EXPLAIN SELECT name, hire_date FROM employees WHERE hire_date >= '2020-01-01' AND hire_date < '2021-01-01';

关注type列(应为range)、key列(应显示索引名)、rows列(扫描行数应远小于总行数)。

注意:如果hire_date没建索引,rows会显示全表扫描(ALL),此时必须ALTER TABLE employees ADD INDEX idx_hire_date (hire_date);。练习题默认已建好必要索引,但你要知道这个动作的意义。

4.2 复杂JOIN题的答案拆解:用小表驱动大表降低IO压力

题目:“查询所有参与‘数据分析平台’项目的员工姓名、部门名称及项目开始日期”
假设projects表有project_id,project_name;employees_projects关联表有emp_id,project_id,start_date。

标准答案:

SELECT e.name, d.dept_name, ep.start_date FROM projects p JOIN employees_projects ep ON p.project_id = ep.project_id JOIN employees e ON ep.emp_id = e.emp_id JOIN departments d ON e.dept_id = d.dept_id WHERE p.project_name = '数据分析平台';

为什么把projects放最前面?
因为WHERE p.project_name = '数据分析平台'会先过滤出极少数项目(可能就1行),然后用这个小结果集去驱动employees_projects(找该项目所有员工),再驱动employees(找员工详情),最后驱动departments(补部门名)。这就是“小表驱动大表”原则,能极大减少中间结果集大小。

如果反过来写(employees开头),就要先扫描所有员工,再逐个查项目,IO量呈指数级增长。

4.3 聚合统计题的答案拆解:HAVING不是WHERE的替代品,而是GROUP后的守门员

题目:“查询员工数超过5人的部门名称及员工数”
标准答案:

SELECT d.dept_name, COUNT(*) AS emp_count FROM departments d JOIN employees e ON d.dept_id = e.dept_id GROUP BY d.dept_id, d.dept_name HAVING COUNT(*) > 5;

关键点:

  • WHERE过滤发生在GROUP BY之前,用于筛选原始行(如WHERE e.hire_date > '2015-01-01');
  • HAVING过滤发生在GROUP BY之后,用于筛选分组结果(如HAVING COUNT(*) > 5);
  • COUNT(*)在HAVING中可用,在WHERE中不可用(因为分组还没发生)。

错误写法:WHERE COUNT(*) > 5—— 直接报错Invalid use of group function。

4.4 窗口函数题的答案拆解:框架子句(ROWS/RANGE)决定计算范围

题目:“查询每个员工的薪资,以及其所在部门的薪资累计和(按入职时间排序)”
标准答案:

SELECT e.name, d.dept_name, s.salary, SUM(s.salary) OVER ( PARTITION BY d.dept_id ORDER BY e.hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS dept_cumsum FROM employees e JOIN departments d ON e.dept_id = d.dept_id JOIN salaries s ON e.emp_id = s.emp_id;

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是重点:

  • UNBOUNDED PRECEDING:从分区第一行开始;
  • CURRENT ROW:到当前行为止;
  • 整体含义:计算“从部门第一个入职员工,到当前员工”的薪资累计和。

如果写成RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,当多员工同一天入职时,RANGE会把同一天的所有人视为同一行来计算,而ROWS严格按物理行序。业务中“累计和”通常要求严格时序,所以选ROWS。

4.5 性能优化题的答案拆解:用覆盖索引避免回表

题目:“查询所有员工的姓名和入职日期,按入职日期倒序排列”
标准答案(带索引优化):

-- 创建覆盖索引 ALTER TABLE employees ADD INDEX idx_name_hiredate (hire_date, name);

然后执行:

SELECT name, hire_date FROM employees ORDER BY hire_date DESC;

为什么这个索引能提速?
因为idx_name_hiredate (hire_date, name)是复合索引,B+树叶子节点已按hire_date排序,且包含了name字段。MySQL可以直接从索引中取出所有需要的数据,无需回到主键索引(聚簇索引)中查找name(即“回表”)。对于百万级数据,回表IO可能是性能瓶颈。

验证:EXPLAIN结果中Extra列应显示Using index(表示用了覆盖索引),而不是Using filesort或Using temporary。

5. 常见问题与避坑指南:那些没人告诉你的“经验性bug”

5.1 字符集与排序规则引发的“查不到”玄学

现象:执行SELECT * FROM employees WHERE name = '张三';返回空,但确认表里确实有“张三”。

原因:name字段字符集是utf8mb4,但排序规则(Collation)是utf8mb4_0900_as_cs(区分大小写、区分重音),而你输入的'张三'实际是'张三 '(末尾有空格)或'张三 '(中文全角空格)。

解决方案:

  • 查看字段排序规则:SHOW FULL COLUMNS FROM employees LIKE 'name';
  • 统一用utf8mb4_unicode_ci(不区分大小写,忽略尾随空格);
  • 查询时用TRIM()清洗:WHERE TRIM(name) = TRIM('张三');
  • 生产环境建表时强制指定:name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。

实操心得:某次帮运营同事查用户,他复制粘贴的用户名带了不可见的零宽空格(U+200B),=比较永远失败。后来用HEX(name)发现末尾多出E2808B,才定位到问题。现在我的习惯是:所有字符串查询前先SELECT HEX('xxx');看一眼。

5.2 时间戳精度丢失:datetime vs timestamp 的隐形差异

现象:salaries.from_date存的是'2020-01-01 00:00:00',但查询WHERE from_date = '2020-01-01'返回空。

原因:datetime类型存储时不带时区,timestamp类型存储时会转成UTC时间。但更常见的坑是:'2020-01-01'被MySQL自动补全为'2020-01-01 00:00:00',看起来一样,但如果字段定义是DATETIME(3)(毫秒精度),而你传入的字符串没带毫秒,比较就会失败。

解决方案:

  • 统一用范围查询:WHERE from_date >= '2020-01-01' AND from_date < '2020-01-02';
  • 或用DATE()函数:WHERE DATE(from_date) = '2020-01-01'(注意:这会让索引失效,仅限数据量小时用);
  • 建表时明确精度:from_date DATETIME DEFAULT CURRENT_TIMESTAMP。

5.3 外键约束的“双刃剑”:开发爽,上线崩

现象:练习题中employees.dept_id有外键指向departments.dept_id,但你在导入测试数据时,先插employees再插departments,报错Cannot add or update a child row: a foreign key constraint fails。

原因:外键强制要求“父表存在,子表才能插入”。练习题数据量小,你可以手动调整插入顺序。但在真实迁移中,一张表几千万行,不可能人工排顺序。

解决方案:

  • 临时禁用外键检查(仅限导入阶段):
    SET FOREIGN_KEY_CHECKS = 0; -- 执行INSERT SET FOREIGN_KEY_CHECKS = 1;
  • 更安全的做法:用mysqldump --no-create-info --skip-triggers导出数据,再用mysql --force导入,它会自动处理依赖顺序。

注意:SET FOREIGN_KEY_CHECKS = 0是会话级的,不影响其他连接,但务必记得设回1,否则后续数据一致性全靠运气。

5.4 LIMIT分页的深水区:OFFSET越大越慢

现象:SELECT * FROM employees ORDER BY hire_date LIMIT 10000, 20(查第501页)执行超慢。

原因:MySQL必须先扫描前10000行,再取20行,OFFSET越大,丢弃的行越多。

优化方案(三种):

  • 游标分页(推荐):用上一页最后一条的hire_date作为条件:
    SELECT * FROM employees WHERE hire_date < '2018-05-20' -- 上一页最后一条的hire_date ORDER BY hire_date DESC LIMIT 20;
  • 延迟关联:先用主键查ID,再JOIN取详情:
    SELECT e.* FROM employees e INNER JOIN ( SELECT emp_id FROM employees ORDER BY hire_date LIMIT 10000, 20 ) AS tmp ON e.emp_id = tmp.emp_id;
  • ID范围分页:如果emp_id自增,可估算范围:
    SELECT * FROM employees WHERE emp_id BETWEEN 10001 AND 10020 ORDER BY hire_date;

5.5 隐式类型转换:字符串和数字比较的“静默失败”

现象:employees.emp_id是INT,但你写WHERE emp_id = '123'(字符串),查询能执行,但可能不走索引。

原因:MySQL会把'123'隐式转成数字123,看起来没问题。但如果字段是VARCHAR存数字(如'00123'),WHERE emp_id = 123就会把'00123'转成123,导致'00123' = 123成立,但业务上'00123'和123是不同含义。

解决方案:

  • 严格保持类型一致:INT字段用数字,VARCHAR字段用字符串;
  • 开启STRICT_TRANS_TABLES模式,让隐式转换报错而非静默;
  • 在应用层做类型校验,不要把字符串ID直接拼进SQL。
问题类型典型表现快速诊断命令根治方案
NULL陷阱WHERE col = NULL返回空SELECT COUNT(*) FROM t WHERE col IS NULL;记住IS NULL语法
索引失效EXPLAIN显示type=ALLEXPLAIN SELECT ...用>=替代YEAR(),建覆盖索引
JOIN逻辑错误数据量异常增多或减少SELECT COUNT(*) FROM t1 JOIN t2 ON ...检查ON和WHERE位置,用小表驱动
字符集乱码SELECT HEX(col)显示乱码字节SHOW CREATE TABLE t;建表时指定utf8mb4_unicode_ci
分页性能差LIMIT 100000,20超过1秒EXPLAIN FORMAT=JSON SELECT ...改用游标分页或延迟关联

6. 实战复盘:用这套题练出“肌肉记忆”的三个阶段

6.1 第一阶段:闭卷默写——把标准答案刻进手指

这不是死记硬背,而是建立语法反射弧。比如看到“查每个部门最高薪员工”,你的手指应该本能敲出:

SELECT d.dept_name, e.name, s.salary FROM departments d JOIN employees e ON d.dept_id = e.dept_id JOIN salaries s ON e.emp_id = s.emp_id WHERE (d.dept_id, s.salary) IN ( SELECT dept_id, MAX(salary) FROM employees e2 JOIN salaries s2 ON e2.emp_id = s2.emp_id GROUP BY dept_id );

这个过程要快到不需要思考IN子查询的括号位置。我让实习生每天早会前默写3道题,坚持两周,SQL编写速度提升40%,错误率下降70%。关键是不看答案,写完立刻核对,错一个字就重写三遍。

6.2 第二阶段:场景变形——把题目掰开揉碎再组装

把原题当乐高积木,自己造新题:

  • 原题:“查薪资前3的员工” → 变形:“查薪资前3的员工,且入职满2年”;
  • 原题:“查部门平均薪资” → 变形:“查部门平均薪资,并标注高于公司平均的部门”;
  • 原题:“查参与项目的员工” → 变形:“查只参与一个项目的员工”。

每次变形,都强迫自己:

  1. 画ER图,标出涉及的表和字段;
  2. 写出伪代码逻辑(如“先算公司平均,再JOIN部门平均,再比较”);
  3. 翻出原题答案,改两处关键点(如加WHERE、换JOIN表);
  4. 用EXPLAIN验证执行计划是否合理。

这个阶段的目标是:看到业务需求,脑中自动浮现SQL骨架。某次产品提了个需求:“找出近3个月下单但没付款的用户”,我脱口而出SELECT user_id FROM orders WHERE create_time > DATE_SUB(NOW(), INTERVAL 3 MONTH) AND pay_status = 0,连表都没想——这就是肌肉记忆。

6.3 第三阶段:线上真刀真枪——用生产环境数据验证

找一个低风险的生产库(如测试环境的报表库),执行以下操作:

  • 把练习题中的employees表名换成你库里的真实员工表;
  • 把字段名映射过去(如练习题hire_date对应生产库entry_time);
  • 执行相同逻辑的SQL,观察:
    • 执行时间是否在1秒内(超时说明要优化);
    • EXPLAIN的rows是否接近预估(偏差超5倍要查索引);
    • 结果数据量是否合理(如查“技术部员工”返回1000+行,但部门实际只有20人,说明JOIN错了)。

我带的一个实习生,用这套方法在测试库发现了orders表缺少user_id索引,修复后一个报表从47秒降到0.8秒。他说:“原来练习题不是为了考试,是为了让我在真实世界里,一眼看出哪里不对。”

最后分享一个小技巧:把所有练习题答案存进一个.sql文件,用MySQL客户端的\.命令批量执行。每次打开终端,先运行\t(开启计时),再\.执行文件,看着每道题的执行时间从2.3秒降到0.15秒,那种掌控感,比刷十道LeetCode还实在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 4:23:46

微服务模块化设计:从限界上下文到DAO契约隔离

1. 这不是“分层”而是“切片”&#xff1a;微服务模块化设计的本质认知 很多人一看到“业务类、工具类、DAO、Service、Controller分离”&#xff0c;下意识就往传统单体Spring Boot的MVC分层上套——这恰恰是踩进第一个大坑。我带过十几个微服务落地项目&#xff0c;其中七成…

作者头像 李华
网站建设 2026/10/23 2:31:18

吉安泰和出差住哪里比较好?

经常去泰和出差&#xff0c;试过好几家酒店&#xff0c;这家乌鸡文化主题酒店给我的印象很深。交通近高铁站和高速&#xff0c;配套齐全&#xff0c;除了住宿&#xff0c;还能安排用餐、小型会议&#xff0c;分享给需要来泰和的朋友。 经常跑吉安泰和出差&#xff0c;之前一直在…

作者头像 李华
网站建设 2026/10/22 12:38:38

MFC老系统界面翻新:Skin++与SkinMagic三库选型与接入实战

简介&#xff1a;这份资源面向使用 Visual Studio 2010 进行 MFC 开发的 Windows 程序员&#xff0c;尤其是希望快速美化应用界面、又不想深究底层图形实现细节的开发者。压缩包整合了 Skin 皮肤引擎与 SkinMagic 2.4、2.2 两个版本&#xff0c;并附带 3 种 VC 皮肤库&#xff…

作者头像 李华
网站建设 2026/10/23 0:15:18

原生JS响应式网站右侧悬浮在线客服插件:接入与定制全攻略

简介&#xff1a;面向网页前端初学者与需要快速搭建客服入口的开发者&#xff0c;这份压缩包提供了一个基于JavaScript的响应式在线客服插件示例。插件利用固定定位使悬浮按钮始终停靠在页面右侧&#xff0c;通过CSS3媒体查询适配不同屏幕尺寸&#xff0c;在手机与PC上均能保持…

作者头像 李华
网站建设 2026/10/22 17:31:54

全息谱诊断实战:Matlab双通道振动信号椭圆合成与故障判据

简介&#xff1a;这份资源是原创经典全息谱Matlab源程序&#xff0c;面向从事旋转机械故障诊断、信号处理与状态监测方向的高校师生及工程技术人员&#xff0c;用于实现全息诊断&#xff08;holospectrum&#xff09;方法&#xff0c;该方法由西安交大已故屈院士发明&#xff0…

作者头像 李华
网站建设 2026/10/23 5:36:10

淘宝海量数据架构的三大核心权衡:时效性、一致性与可解释性

1. 项目概述&#xff1a;这不是“高大上”的PPT架构图&#xff0c;而是每天扛住10亿级用户真实请求的“数据地基”“阿里淘宝海量级数据技术架构”——这九个字背后&#xff0c;不是几张画满箭头的PMT&#xff08;Project Management Tool&#xff09;示意图&#xff0c;也不是…

作者头像 李华