ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL单表查询实战:掌握SELECT执行顺序与分组聚合

MySQL单表查询实战:掌握SELECT执行顺序与分组聚合 在 MySQL 入门阶段编号为 mysql09 的单表查询练习是一个很典型的分水岭。前面的基础语法看着都懂但真正落到一张表上做数据筛选时很多人会在WHERE、ORDER BY、GROUP BY、LIMIT的组合中迷失明明数据存在却查不出来聚合统计结果里出现看不懂的报错分页一深页面就卡顿。单表查询是后续多表JOIN、子查询、窗口函数的基础如果这一层没有形成清晰的执行链路认知后面写复杂 SQL 时只会更被动。这篇文章围绕单表查询练习展开会先从 SELECT 的执行顺序讲清楚单表查询的整体逻辑再给出一套可以直接复现的建表脚本和练习数据然后按条件过滤、排序去重、分组聚合、函数、分页的顺序逐步演示 SQL 写法最后整理常见报错、排查路径和生产环境注意事项。学完以后你能独立完成常见的单表查询并且能解释“为什么这个条件查不到数据”“为什么分组报错”“为什么排序结果不符合预期”这些实际问题。1. 单表查询到底在查什么SELECT 的完整执行链路单表查询是指仅针对一张表执行 SELECT 操作不涉及多表连接。它要解决的核心问题只有两个从哪张表取数据以及取哪些行、哪些列。可以把一张表想象成仓库里的货架每一行是一件商品每一列是商品的一个属性。单表查询就是在仓库里按规则找到符合条件的商品并且只把需要的属性带回来。1.1 一个查询由哪些部分组成一个典型的单表查询通常包含这些部分SELECT name, score FROM student WHERE class_id 1 ORDER BY score DESC LIMIT 3;这段 SQL 的意思是从student表里先筛选出class_id 1的学生再按分数从高到低排序最后只取前 3 行。各关键字的作用如下关键字作用类比SELECT指定要返回哪些列也可以包含表达式、函数、常量决定最终展示商品哪些属性FROM指定数据来源表决定去哪个仓库取货WHERE在分组前按行筛选数据在货架前手工筛掉不要的商品GROUP BY对满足条件的行进行分组把同类商品放回同一个筐HAVING对分组后的结果进行筛选检查每个筐是否符合验收标准ORDER BY对最终结果排序按价格或重量重新摆放LIMIT限定返回行数只看前几个1.2 书写顺序不等于执行顺序很多初学者把 SQL 当成英文句子读认为先写 SELECT 就应该先执行 SELECT。实际不是这样。MySQL 虽然要求你按 SELECT 开头去写 SQL但真正执行时要遵循一条固定链路FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT书写顺序是SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT两者对比非常关键因为很多报错和“查不到数据”的困惑都来自执行顺序。举例来说SELECT class_id, COUNT(*) AS cnt FROM student WHERE score 80 GROUP BY class_id HAVING cnt 2 ORDER BY cnt DESC;执行链路的含义是FROM student先确定从student表拿数据。WHERE score 80把成绩大于 80 的行筛选出来。GROUP BY class_id把筛选后的行按班级分组。HAVING cnt 2对分组统计出的结果做过滤。SELECT class_id, COUNT(*) AS cnt生成最终输出列。ORDER BY cnt DESC按统计人数倒序排列。1.3 为什么 WHERE 里不能使用 SELECT 别名这是单表查询里最容易被问到的点。有人会写SELECT score * 1.1 AS new_score FROM student WHERE new_score 90;MySQL 会报错Unknown column new_score in where clause。原因就是执行顺序WHERE在SELECT之前执行。当WHERE准备筛选行时score * 1.1这个计算结果的别名new_score还没有生成所以无法引用。但ORDER BY在SELECT之后执行所以别名可以在ORDER BY中正常使用SELECT score * 1.1 AS new_score FROM student ORDER BY new_score DESC;注意MySQL 对GROUP BY、HAVING中使用别名的兼容性比标准 SQL 宽松这是 MySQL 的扩展能力。为了可移植性和可读性生产代码里建议保留完整的表达式不要过度依赖别名。2. 搭建练习环境建库、建表、准备可复现数据单表查询的练习离不开一套稳定的数据。这里先用 MySQL 8.0 做示例准备一张student学生表包含姓名、班级、性别、年龄、成绩、邮箱和创建时间并在表里故意留一个成绩为NULL的行用于演示 NULL 判断和聚合函数的行为。2.1 环境要求与安装确认学习环境建议使用 MySQL 5.7 或 8.0推荐 8.0。操作前先确认客户端能连上 MySQL 服务mysql --version正常输出类似mysql Ver 8.0.36 for Linux on x86_64 (MySQL Community Server - GPL)如果还没有安装 MySQL可以参考官方安装包或发行版仓库安装。安装完成后常见启动方式有# systemd 环境 systemctl start mysql # 无 systemd 的旧系统 service mysql start启动后再用命令行登录mysql -uroot -p如果出现ERROR 2002 (HY000): Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock说明服务还没有启动或者 socket 文件路径不对优先检查服务状态。2.2 创建练习数据库登录成功后执行建库语句CREATE DATABASE IF NOT EXISTS mysql_practice DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE mysql_practice;建库时显式指定utf8mb4字符集很重要。utf8mb4能存储绝大多数 Unicode 字符包括中文、Emoji 以及特殊符号。排序规则utf8mb4_unicode_ci是以_ci结尾表示大小写不敏感实际生产中需要根据业务场景选择_ci、_bin或_as_cs等排序规则。2.3 建表语句与练习数据创建学生表DROP TABLE IF EXISTS student; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, class_id INT NOT NULL, gender CHAR(1) NOT NULL DEFAULT M, age INT NOT NULL, score DECIMAL(5,2), email VARCHAR(64), create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_class_id (class_id), INDEX idx_score (score) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段设计说明字段类型说明idINT主键自增nameVARCHAR(32)学生姓名class_idINT班级编号genderCHAR(1)性别M 或 FageINT年龄scoreDECIMAL(5,2)分数最大 999.99避免使用 FLOATemailVARCHAR(64)邮箱允许为 NULLcreate_timeDATETIME创建时间由数据库默认填充这里特别说明分数使用DECIMAL(5,2)而不是FLOAT。浮点类型在存储和计算过程中可能产生精度误差成绩、金额这类字段在业务上需要精确保存应该使用定点数类型。接着插入练习数据INSERT INTO student (name, class_id, gender, age, score, email, create_time) VALUES (Alice, 1, F, 18, 92.50, aliceexample.com, 2024-03-01 09:00:00), (Bob, 1, M, 19, 85.00, bobexample.com, 2024-03-01 10:30:00), (Carol, 2, F, 20, 78.00, carolexample.com, 2024-03-02 11:20:00), (David, 2, M, 18, 88.50, NULL, 2024-03-02 14:10:00), (Eve, 3, F, 21, 95.00, eveexample.com, 2024-03-03 09:40:00), (Frank, 1, M, 20, 67.00, NULL, 2024-03-03 15:00:00), (Grace, 2, F, 19, 82.00, graceexample.com, 2024-03-04 10:00:00), (Henry, 3, M, 22, 73.50, henryexample.com, 2024-03-05 16:30:00), (Ivy, 3, F, 18, 89.00, ivyexample.com, 2024-03-05 18:00:00), (Jack, 1, M, 21, NULL, jackexample.com, 2024-03-06 09:15:00);插入后验证数据量SELECT COUNT(*) FROM student;预期结果是10。2.4 创建一个专用练习账号学习环境里虽然可以直接用 root 登录但生产环境绝对不能这样做。练习时可以先建立一个权限有限的应用账号养成最小权限习惯CREATE USER practicelocalhost IDENTIFIED BY practice123; GRANT SELECT, INSERT, UPDATE, DELETE ON mysql_practice.* TO practicelocalhost; FLUSH PRIVILEGES;使用该账号登录mysql -upractice -p这样即使练习 SQL 写错也不会影响 root 账号和系统库。3. 单表查询基础语法过滤、排序、去重、分页这一节从最简单的查询开始逐步覆盖日常开发中最常用的基础语法。3.1 基础查询与列投影查看全体学生SELECT * FROM student;快速了解数据时可以使用SELECT *但进入正式代码、视图、报表时不建议这样做。显式列出列名可以减少网络传输、避免表结构变更导致程序出错也让人一眼看出查询真正需要哪些数据。只查姓名和分数SELECT name, score FROM student;如果要为列起别名SELECT name AS student_name, score AS exam_score FROM student;3.2 WHERE 条件过滤与常用运算符条件过滤是单表查询最核心的部分。比较运算符包括、、、、、。逻辑运算符包括AND、OR、NOT。查询年龄在 18 到 20 之间的女生SELECT name, age, gender FROM student WHERE age BETWEEN 18 AND 20 AND gender F;BETWEEN ... AND ...包含边界值等价于age 18 AND age 20。查询班级为 1 或 3 的学生SELECT name, class_id FROM student WHERE class_id IN (1, 3);查询名字里包含字母a的学生SELECT name FROM student WHERE name LIKE %a%;%表示任意长度的任意字符_表示单个任意字符。如果业务里的字符串本身包含%或_需要用ESCAPE指定转义字符SELECT name FROM student WHERE name LIKE %\_% ESCAPE \\;3.3 NULL 判断不能写成等于 NULLNULL在 SQL 中表示“未知”它既不是 0也不是空字符串。新手最容易犯的错是-- 错误写法永远查不到数据 SELECT * FROM student WHERE score NULL;这样写不会报错但结果永远是空。因为任何值和NULL做比较返回的都是NULL而WHERE只会保留结果为TRUE的行。正确写法必须使用IS NULL或IS NOT NULL-- 查询邮箱为空的学生 SELECT name, email FROM student WHERE email IS NULL; -- 查询成绩不为空的学生 SELECT name, score FROM student WHERE score IS NOT NULL;这里有两条数据David 和 Jack 的邮箱或成绩为 NULL写练习时要注意区分。3.4 ORDER BY 排序规则按成绩从高到低排序SELECT name, score FROM student ORDER BY score DESC;如果成绩相同可以再加一个排序字段SELECT name, class_id, age, score FROM student ORDER BY class_id ASC, score DESC;这表示先按班级升序排列班级相同再按成绩降序排列。在 MySQL 中默认排序规则下 NULL 比非 NULL 值“更小”所以升序时 NULL 排在最前面降序时 NULL 排在最后面。如果业务要求把 NULL 统一放最后可以使用FIELD()或表达式调整但更稳妥的做法是先想清楚业务对缺失值的展示要求再决定排序表达式。3.5 去重与 LIMIT 分页查看共有几个班级SELECT DISTINCT class_id FROM student;DISTINCT会对结果做去重。它作用于 SELECT 的整行而不是单列如果有多个列只有多个列的组合完全一致时才算重复。查询成绩排名前三的学生SELECT name, score FROM student ORDER BY score DESC LIMIT 3;分页场景通常需要指定偏移量。查询第 2 页每页 3 条SELECT name, score FROM student ORDER BY score DESC LIMIT 3 OFFSET 3;也可以用 MySQL 的简写形式SELECT name, score FROM student ORDER BY score DESC LIMIT 3, 3;这里第一个 3 是偏移量第二个 3 是返回行数。两种写法的可读性都不差关键是理解偏移量 (页码 - 1) * 每页条数。4. 分组与聚合从明细数据到统计结果单表查询不只是取明细很多报表需求本质是在某一列上做分组再对每组做统计。4.1 聚合函数能统计什么MySQL 内置聚合函数负责把多行数据“压缩”成一个结果。最常用的是函数作用NULL 处理COUNT(*)统计行数会统计所有行COUNT(column)统计某列非 NULL 行数忽略 NULLSUM(column)求和忽略 NULLAVG(column)求平均值忽略 NULLMAX(column)求最大值忽略 NULLMIN(column)求最小值忽略 NULL先看一个整体统计SELECT COUNT(*) AS total_count, COUNT(score) AS score_count, AVG(score) AS avg_score, MAX(score) AS max_score, MIN(score) AS min_score FROM student;这张表有 10 行其中 Jack 的score是 NULL所以COUNT(*)返回 10COUNT(score)返回 9AVG(score)会基于 9 行计算而不是把 NULL 当 0。这个差异非常容易踩坑。4.2 GROUP BY 分组的正确用法统计每个班级的学生人数SELECT class_id, COUNT(*) AS student_count FROM student GROUP BY class_id;统计每个班级的平均分SELECT class_id, AVG(score) AS avg_score FROM student GROUP BY class_id;MySQL 8.0 默认开启ONLY_FULL_GROUP_BY模式。在这个模式下SELECT 列表中出现的非聚合列必须出现在 GROUP BY 中否则会报错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ... which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_modeonly_full_group_by所以下面这种 SQL 在默认模式下不可靠不应依赖-- 不建议name 不是分组字段 SELECT class_id, name, COUNT(*) FROM student GROUP BY class_id;分组后每个班级可能有多名学生但 SQL 没法确定该返回哪一名学生的name结果不可控。生产环境不要修改sql_mode来规避这个报错而是应该调整 SQL让查询语义确定。4.3 HAVING 过滤分组结果统计平均分大于 80 的班级SELECT class_id, AVG(score) AS avg_score FROM student GROUP BY class_id HAVING AVG(score) 80;WHERE在分组前过滤原始行HAVING在分组后过滤分组结果。所以WHERE中不能直接写聚合函数-- 错误聚合函数不能放在 WHERE SELECT class_id, COUNT(*) FROM student WHERE COUNT(*) 3 GROUP BY class_id;正确做法是SELECT class_id, COUNT(*) AS cnt FROM student GROUP BY class_id HAVING COUNT(*) 3;4.4 常见统计需求示例统计每个班级女生人数SELECT class_id, COUNT(*) AS female_count FROM student WHERE gender F GROUP BY class_id;统计每个班级的最高分和最低分SELECT class_id, MAX(score) AS max_score, MIN(score) AS min_score FROM student GROUP BY class_id;统计各分数段人数需要结合下面的CASE WHEN表达式因此放在函数章节再展开。5. 常用函数与表达式让查询更符合业务语义实际业务不会只查原始列往往需要格式化、截取、计算和条件判断。这里介绍最常用的一组函数。5.1 字符类函数查看姓名字符长度、邮箱域名、姓名是否包含特定字符SELECT name, CHAR_LENGTH(name) AS name_length, LEFT(email, 5) AS email_prefix, UPPER(LEFT(name, 1)) AS first_letter FROM student;CHAR_LENGTH返回字符个数而LENGTH返回字节数。在utf8mb4下一个中文占 3 或 4 个字节两者结果可能不同。当你想知道字符串“有多少个字符”时优先使用CHAR_LENGTH。拼接姓名和班级信息SELECT CONCAT(name, - class , class_id) AS student_label FROM student;5.2 数值与日期函数四舍五入和向上取整SELECT name, score, ROUND(score, 0) AS rounded_score, CEIL(score) AS ceil_score, FLOOR(score) AS floor_score FROM student WHERE score IS NOT NULL;这里score IS NOT NULL很关键。如果某行score为 NULLCEIL(NULL)仍然是 NULL虽然不会报错但结果容易让人困惑。日期函数示例SELECT name, YEAR(create_time) AS create_year, DATE_FORMAT(create_time, %Y-%m-%d) AS create_date FROM student;当业务需要按年份统计时很多人会写WHERE YEAR(create_time) 2024这种写法不是绝对错误但如果create_time上有索引对列做函数运算会导致索引无法正常使用。更推荐写成范围条件WHERE create_time 2024-01-01 AND create_time 2025-01-015.3 条件表达式 CASE WHENCASE WHEN适合在查询中做逻辑分支。给成绩分档SELECT name, score, CASE WHEN score 90 THEN A WHEN score 80 THEN B WHEN score 70 THEN C ELSE D END AS grade FROM student;统计每个分数段的人数SELECT CASE WHEN score 90 THEN A WHEN score 80 THEN B WHEN score 70 THEN C ELSE D END AS grade, COUNT(*) AS cnt FROM student WHERE score IS NOT NULL GROUP BY grade;这里GROUP BY grade在 MySQL 中可以工作因为 MySQL 允许分组时引用 SELECT 列表里的别名。但为了跨数据库兼容也可以直接写成GROUP BY CASE WHEN score 90 THEN A WHEN score 80 THEN B WHEN score 70 THEN C ELSE D END两种写法都能得到结果后者更容易迁移到其他数据库。5.4 函数使用注意事项函数虽然方便但使用时要留意三点对索引列使用函数可能会阻断索引优化时优先改写为范围条件。NULL在函数中会继续传播CONCAT(NULL, abc)的结果也是 NULL需要时用IFNULL、COALESCE兜底。字符函数受排序规则影响LENGTH和CHAR_LENGTH在高版本 MySQL 和不同字符集下的差异要提前确认。6. 单表查询综合练习题目、答案与拆解这一节基于上面创建的student表给出 20 道练习题目。建议先不要看答案自己在命令行或客户端里写完再对照参考答案。6.1 练习数据环境交代练习表共 10 行数据包含 3 个班级、5 名男生、5 名女生、1 条分数为 NULL 的数据。环境准备完成后只需要执行建表和插入语句即可开始。6.2 练习题目速查表下面的表格列出题目编号、题目要求、考察点和参考答案。其中参考答案以 MySQL 8.0 默认sql_mode为准。编号题目要求考察点参考答案1查询所有学生的姓名和成绩基础 SELECTSELECT name, score FROM student;2查询姓名、班级、成绩按成绩降序ORDER BYSELECT name, class_id, score FROM student ORDER BY score DESC;3查询成绩大于等于 85 且年龄小于 20 的学生AND 组合条件SELECT name, age, score FROM student WHERE score 85 AND age 20;4查询班级 1 或 2 中的女生IN 和 ANDSELECT name, class_id, gender FROM student WHERE class_id IN (1, 2) AND gender F;5查询成绩在 70 到 90 之间的学生BETWEENSELECT name, score FROM student WHERE score BETWEEN 70 AND 90;6查询邮箱为空的学生IS NULLSELECT name, email FROM student WHERE email IS NULL;7查询姓名以 a 结尾的学生LIKESELECT name FROM student WHERE name LIKE %a;8查询每个班级的平均分GROUP BY AVGSELECT class_id, AVG(score) AS avg_score FROM student GROUP BY class_id;9查询人数小于 3 的班级HAVINGSELECT class_id, COUNT(*) AS cnt FROM student GROUP BY class_id HAVING cnt 3;10查询每个班级的最高分和最低分MAX/MINSELECT class_id, MAX(score) AS max_score, MIN(score) AS min_score FROM student GROUP BY class_id;11统计 A/B/C/D 各等级人数CASE GROUP BY见下方展开12分页查询第 2 页每页 3 条LIMITSELECT name, score FROM student ORDER BY score DESC LIMIT 3 OFFSET 3;13查询姓名长度大于 4 的学生CHAR_LENGTHSELECT name FROM student WHERE CHAR_LENGTH(name) 4;14查询所有班级编号去重DISTINCTSELECT DISTINCT class_id FROM student;15查询成绩排名第 2 到第 4 名的学生LIMIT 偏移SELECT name, score FROM student WHERE score IS NOT NULL ORDER BY score DESC LIMIT 1, 3;16查询年龄最大的学生子查询或排序SELECT name, age FROM student ORDER BY age DESC LIMIT 1;17查询每个班级女生人数WHERE GROUP BYSELECT class_id, COUNT(*) AS female_cnt FROM student WHERE gender F GROUP BY class_id;18查询平均分大于 80 分的班级GROUP BY HAVINGSELECT class_id, AVG(score) AS avg_score FROM student GROUP BY class_id HAVING avg_score 80;19查询年龄大于平均年龄的学生标量子查询SELECT name, age FROM student WHERE age (SELECT AVG(age) FROM student);20查询没有成绩记录的学生IS NULLSELECT name FROM student WHERE score IS NULL;第 11 题完整答案SELECT CASE WHEN score 90 THEN A WHEN score 80 THEN B WHEN score 70 THEN C ELSE D END AS grade, COUNT(*) AS cnt FROM student WHERE score IS NOT NULL GROUP BY grade;结果为gradecntA2B3C3D1D 等级只有 Frank 一人成绩 67 分。6.3 题目分步讲解与参考答案第 9 题需要注意因为它涉及WHERE和HAVING的分工SELECT class_id, COUNT(*) AS cnt FROM student GROUP BY class_id HAVING cnt 3;执行过程是先按班级分组统计每个班级人数再用HAVING过滤掉人数大于等于 3 的班级。WHERE不能在这里使用因为COUNT(*)是聚合结果分组后才产生。第 15 题SELECT name, score FROM student WHERE score IS NOT NULL ORDER BY score DESC LIMIT 1, 3;由于原始数据里 Jack 的成绩为 NULL而 NULL 在排序时的位置特殊这里先通过WHERE score IS NOT NULL排除空值再按降序取第 2 到第 4 名。第 19 题SELECT name, age FROM student WHERE age (SELECT AVG(age) FROM student);子查询先计算出全体学生的平均年龄然后外层查询返回年龄大于平均年龄的学生。虽然出现了子查询但子查询仍然基于同一张表整体逻辑仍然是单表范围内的查询。6.4 练习完成后如何自检不要只满足于 SQL 能跑出结果。建议按下面几条自检检查结果行数是否符合业务预期。把WHERE、GROUP BY、ORDER BY临时删掉一个对比结果的差异。检查包含 NULL 的行是否被正确处理。对结果有疑惑时用SELECT *先看原始数据再做换算。7. 常见错误现象与排查链路单表查询报错和结果不符合预期通常来自几个固定区域连接与表结构、NULL 处理、分组模式、字符集排序规则、分页写法。下面从现象倒推原因。7.1 常见错误对照表错误现象常见原因检查方式处理建议ERROR 2002 (HY000): Cant connect to local MySQL server through socketMySQL 服务未启动或 socket 路径不对检查服务状态确认 socket 文件位置启动 MySQL 服务或调整连接参数使用 TCPAccess denied for user practicelocalhost (using password: YES)账号不存在或密码错误核对账号名、密码、host用 root 检查用户表和权限修正密码ERROR 1049 (42000): Unknown database数据库名拼写错误SHOW DATABASES;使用存在的库名或重新建库ERROR 1054 (42S22): Unknown column name in field list列名拼写错误DESC student;对照真实字段名修改 SQLERROR 1146 (42S02): Table mysql_practice.student doesnt exist表不存在或当前库不对SELECT DATABASE();、SHOW TABLES;切换到正确数据库并建表条件查不到数据但数据确实存在NULL 比较、字符集或排序规则、大小写区分去掉条件逐步排查使用IS NULL确认 collationExpression #1 of SELECT list is not in GROUP BY clauseONLY_FULL_GROUP_BY模式下 SELECT 列未包含在 GROUP BY 中SELECT sql_mode;修改 SQL不要直接关闭模式MySQL 客户端连接被拒绝或提示 Host not allowed账号 host 权限限制或防火墙拦截查看mysql.user、检查端口调整账号 host 或网络策略分页数据重复或缺失ORDER BY 字段不唯一分页顺序不稳定加上主键作为第二排序键ORDER BY score DESC, id ASC7.2 连接与建表问题排查遇到ERROR 2002时先确认服务是否启动systemctl status mysql # 或 ps -ef | grep mysqld如果服务未启动先启动服务再重新连接。如果服务已启动但客户端仍通过 socket 连接失败可以改用 TCP 方式mysql -h127.0.0.1 -P3306 -upractice -p建表阶段如果提示表已存在可以先确认是否该删除SHOW TABLES; DROP TABLE IF EXISTS student;重复执行练习时DROP TABLE IF EXISTS可以确保环境干净可复现。7.3 NULL 比较和分组模式问题排查排查 NULL 相关问题时建议在关键列上单独执行SELECT name, score, email FROM student WHERE score IS NULL OR email IS NULL;如果期望统计平均分但结果和手工计算不一致先确认AVG(score)是否忽略了 NULL 行。排查分组报错时先查看当前模式SELECT sql_mode;如果sql_mode中包含ONLY_FULL_GROUP_BY就要检查 SELECT 的每个非聚合列是否都被 GROUP BY 覆盖。生产环境不要为了去掉报错而修改全局sql_mode因为关闭后分组结果的确定性会下降。7.4 查询结果和预期不符时的排查顺序当一条 SQL 不报错但结果不对按下面的顺序排查确认当前使用的数据库SELECT DATABASE();确认表和字段真实存在DESC student;去掉ORDER BY、GROUP BY、LIMIT先看基本结果。单独检查WHERE条件确认运算符逻辑。检查 NULL 数据是否被意外排除或包含。检查字符集和排序规则对大小写、中文排序的影响。最后用EXPLAIN看执行计划和实际扫描行数。注意排查时不要一次性修改多个条件每调整一个条件就重新执行一次才能定位真正的原因。8. 单表查询的生产环境注意事项与最佳实践练习环境里怎么快速写都没问题但把 SQL 放到业务系统或报表任务里还需要多考虑几层可读性、性能、稳定性、可维护性。8.1 查询列名规范少用 SELECT *快速探索数据时用SELECT *没有错但正式代码里建议显式列名。原因有三点显式列名能减少不必要的数据传输尤其当表有几十个字段且包含大文本字段时。表结构变更时显式列名可避免查询结果意外多出字段减少接口解析出错。查询意图更清晰别人看代码时能直接知道需要哪些数据。推荐写法SELECT id, name, class_id, score FROM student WHERE class_id 1;不推荐SELECT * FROM student WHERE class_id 1;8.2 用 EXPLAIN 检查单表查询执行计划优化单表查询的起点是EXPLAINEXPLAIN SELECT name, score FROM student WHERE score 80 ORDER BY score DESC;输出中常见字段含义字段含义type访问类型常见为 ALL全表扫描、index、range、ref、eq_ref、constpossible_keys可能用到的索引key实际使用的索引rows预估扫描行数Extra附加信息如 Using index、Using where、Using filesort如果看到type ALL说明是全表扫描。小表全表扫描问题不大但数据量增大后WHERE或ORDER BY的字段应合理加索引。如果看到Extra Using filesort说明排序需要额外处理通常意味着ORDER BY字段的索引设计需要优化。学习环境里可以对照建立索引前后执行计划的变化CREATE INDEX idx_score ON student(score);8.3 排序、过滤和分页的索引意识单表查询中WHERE条件的筛选字段和ORDER BY的排序字段是最需要索引意识的地方。常见优化方向过滤条件字段数量不多时为高频查询字段创建索引。多条件组合查询可以考虑联合索引但要关注索引最左前缀原则。分页深时LIMIT 1000000, 10会扫描大量行。更推荐基于键集的分页-- 记录上一页最后一条数据的 id比如 100 SELECT name, score FROM student WHERE id 100 ORDER BY id LIMIT 10;这种写法在数据量大时比直接使用 offset 更稳定。8.4 数据模型与 SQL 编写规范从单表查询开始就应该养成几项基本规范分数、金额、数量等精确数值使用DECIMAL不要使用浮点类型。创建时间由数据库默认值维护应用层不要随意覆盖。SQL 文件纳入版本控制带注释说明业务用途。为查询结果列使用清晰的别名尤其是统计字段。多表场景下所有字段名都带表前缀避免歧义。大批量统计查询放到从库或分析备库执行避免拖慢主库业务。8.5 下一步学习建议单表查询是整个 SQL 体系的地基。完成这套练习后下一步可以按顺序学习多表关联INNER JOIN、LEFT JOIN、RIGHT JOIN理解关联条件对结果行数的影响。子查询与派生表学会把单表查询的结果作为中间数据继续加工。窗口函数ROW_NUMBER()、RANK()、SUM() OVER()等解决排名和累计统计问题。索引优化结合EXPLAIN分析慢查询。事务与锁理解INSERT、UPDATE、DELETE在并发环境下的行为。学习过程中建议把这一节的练习题重复做三遍第一遍照着答案理解第二遍不看答案写第三遍换一套数据重新验证。能把单表查询的条件过滤、分组聚合和排序逻辑讲清楚MySQL 后面的路才会顺。单表查询没那么多“高深技巧”它考验的是对执行顺序的理解、对数据本身的敏感度以及面对报错时能不能沿着链路一步步定位。把执行顺序记牢把 NULL 的行为理解透把索引意识放到日常写 SQL 的过程中这套练习才算真正产生了价值。
返回列表