资讯动态

MySQL查询从入门到实战:SELECT、WHERE、JOIN核心用法与避坑指南

发布时间:2026/10/9 12:49:01 来源:尧图企业网站定制
前几天有个刚转后端的朋友问我MySQL的查询到底该怎么上手。他之前折腾了一堆安装配置的东西数据库倒是跑起来了可真到写SQL查数据的时候反而发懵。这个问题其实很典型——很多人把精力全花在环境搭建上反而忽略了最核心的查询操作。我当年也是这样踩了不少坑才慢慢理清楚。其实MySQL的简单查询没那么玄乎核心就是SELECT那一套语法再配上WHERE、ORDER BY、LIMIT这些常用子句能覆盖日常开发中八成以上的数据提取需求。这篇文章我就把自己在实际项目里用到的查询思路和细节整理出来从最基础的SELECT开始一步步拆解到分组、连接查询顺便把那些特别容易踩的坑也一并交代清楚。这篇文章不挑读者哪怕你刚装好MySQL、连表结构都没建过只要照着文中的例子敲一遍也能很快上手。如果已经写过一些SQL那重点看“为什么”的部分——比如WHERE和HAVING到底该用谁、LEFT JOIN和INNER JOIN怎么选这些都是只看官方文档不一定能领会的实操经验。1. 查询之前先备好一张能折腾的表1.1 建表和准备测试数据学查询最忌讳的就是在空库上瞎敲命令没有数据你根本感知不到查询结果的变化。所以我强烈建议先建一张结构稍微复杂一点的表把常见的数据类型都包含进去这样后续每个查询效果都能看得清清楚楚。我这里拿一个学生成绩记录的经典场景来演示既贴近教学又适合工作后用。CREATE DATABASE IF NOT EXISTS school_demo DEFAULT CHARSET utf8mb4; USE school_demo; CREATE TABLE student_score ( id INT PRIMARY KEY AUTO_INCREMENT, stu_name VARCHAR(50) NOT NULL COMMENT 学生姓名, course VARCHAR(50) NOT NULL COMMENT 课程名称, score DECIMAL(5,2) NOT NULL COMMENT 考试成绩, class_no VARCHAR(20) COMMENT 班级编号, exam_date DATE COMMENT 考试日期, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );这张表包含了INT、VARCHAR、DECIMAL、DATE、TIMESTAMP这些常用类型基本能模拟真实业务场景。接下来插入一批测试数据。注意DECIMAL(5,2)表示最多5位数字其中保留2位小数所以最大值是999.99插入成绩时别超了。INSERT INTO student_score (stu_name, course, score, class_no, exam_date) VALUES (张伟, 数学, 92.5, A01, 2024-10-15), (李娜, 数学, 78.0, A01, 2024-10-15), (王强, 数学, 85.5, B01, 2024-10-15), (刘洋, 数学, 91.0, B01, 2024-10-15), (张伟, 语文, 88.0, A01, 2024-10-16), (李娜, 语文, 96.5, A01, 2024-10-16), (王强, 语文, 72.0, B01, 2024-10-16), (刘洋, 语文, 83.5, B01, 2024-10-16), (张伟, 英语, 80.0, A01, 2024-10-17), (李娜, 英语, 89.5, A01, 2024-10-17), (王强, 英语, 65.0, B01, 2024-10-17), (刘洋, 英语, 93.0, B01, 2024-10-17);1.2 先看看表里到底存了什么数据插完之后先用最基础的SELECT确认一下。这一步看似简单其实很多人会忽略导致后面写了半天查询结果表里的数据跟自己预期完全不一样。SELECT * FROM student_score;这条语句会把表里所有行、所有列都捞出来。*代表全部列在数据量小的时候看一眼没问题但到了生产环境千万、上亿行的表用SELECT *就是在给自己挖坑。原因后面细说这里先养成习惯任何时候写查询脑子里都要过一遍“我真的需要这么多列吗”。2. 从SELECT开始理解查询是怎么执行的2.1 SELECT子句的完整语法骨架很多人学SQL的痛点在于明明知道关键字但不知道它们的排序规则。MySQL的SELECT完整语法其实有一条固定的骨架理解了这个骨架写查询就像套模板一样简单SELECT 列名或表达式 FROM 表名 [WHERE 过滤条件] [GROUP BY 分组列] [HAVING 分组后的过滤条件] [ORDER BY 排序列] [LIMIT 行数限制];注意方括号里的内容都是可选子句但它们在SQL中出现的顺序不能乱。这就像做菜时先洗菜、再切菜、最后下锅锅都没热你不可能先把汤盛出来。MySQL在执行时也是按逻辑顺序处理的先定位表再逐行过滤然后分组接着过滤分组结果之后排序最后截取指定行数。理解这个顺序能解释很多让人困惑的现象比如“为什么WHERE里不能用聚合函数”这种经典问题。2.2 列的选择与简单运算查询不一定只能选表的原始列还可以对列做加减乘除运算甚至可以起别名。比如我想看每个学生的“得分率”也就是成绩占总分假设满分100分的百分比SELECT stu_name, course, score, score / 100 * 100 AS score_rate FROM student_score;这里的AS score_rate就是给计算结果起一个别名。别名在后续的ORDER BY、HAVING里都能直接用但要注意一个很多人都会犯的错别名能不能在WHERE里直接用答案是——不能。原因还是上面那个执行顺序WHERE是在SELECT确定别名之前执行的MySQL解析到WHERE时根本不知道score_rate是个什么东西。这算是最经典的SQL认知误区之一了。2.3 去重的正确姿势DISTINCT课堂上经常能看到这样的需求“我想知道有多少个学生参加了考试”。如果用SELECT stu_name FROM student_score张伟会出现三行因为它有三门课的成绩记录。这时候就要用DISTINCT去重SELECT DISTINCT stu_name FROM student_score;DISTINCT会对后面所有列的组合去重也就是说如果你写SELECT DISTINCT stu_name, course那只有当两行的学生姓名和课程都完全相同时才会被合并。还有一个细节值得注意MySQL还支持SELECT DISTINCTROW效果一样只是写法不同平时用DISTINCT就够了。另外之前看过有人问“MySQL的OR能去重吗”这是两码事OR是逻辑运算符用来组合条件它不会改变返回的行数去重必须靠DISTINCT或GROUP BY。3. WHERE条件过滤精准定位数据3.1 比较运算符和逻辑运算符查询的核心价值不在“捞全部数据”而是快速筛出想要的子集。WHERE子句就是干这个的。最基础的就是比较运算符大于、小于、等于、不等于、大于等于、小于等于在MySQL里分别对应、、、!或、、。比如我想看所有数学成绩大于等于85分的记录SELECT stu_name, course, score FROM student_score WHERE course 数学 AND score 85;这里用了AND连接两个条件表示两个条件必须同时满足。OR则是满足任意一个即可。还有一个容易忽略的坑字符串比较和数字比较的规则不同。虽然你写score 85没问题但如果你把成绩列定义成VARCHAR类型然后存了85这样的字符串MySQL会做隐式类型转换可能引发你很费解的排序或比较结果。所以建表时选对数据类型才是好查询的第一步。3.2 区间查询BETWEEN和IN的细节查询某个范围内的数据用BETWEEN ... AND ...比写两个比较表达式更直观。它包含边界值属于闭区间。比如想查80到90分之间的记录SELECT stu_name, course, score FROM student_score WHERE score BETWEEN 80 AND 90;这条语句等价于score 80 AND score 90。很多人记不住闭区间这个特性导致排查问题时以为数据丢了其实是漏掉了边界值。同样查询一组离散值可以用IN。例如“查询A01和B01两个班级的学生成绩”SELECT * FROM student_score WHERE class_no IN (A01, B01);IN后面跟的是一组值只要匹配其中任意一个就返回。它比写多个OR简洁得多而且可读性更好。但有一点要提醒如果IN列表里包含NULL匹配行为会有些微妙不过新手阶段先记住“IN是匹配列表中的值”就够了。3.3 模糊查询LIKE与NULL的判断业务里经常需要“搜索”某个关键字。比如你想知道哪些学生姓“张”SELECT * FROM student_score WHERE stu_name LIKE 张%;这里的%是通配符代表任意长度的任意字符所以张%能匹配“张伟”“张三丰”等所有以张开头的字符串。另一个通配符是下划线_它只匹配一个字符。比如_张能匹配“小张”、“老张”但匹配不了“大老张”。关于模糊查询我要分享一个实际踩过的坑LIKE查询如果以%开头比如%张%MySQL通常无法使用索引数据量大时会全表扫描性能下降非常明显。但这不是说不能用关键是心里要有数。至于NULL的查询很多人会写WHERE score NULL但这永远查不到数据。NULL表示“不知道、不存在”它不能跟任何值用等号比较。正确写法是IS NULL或者IS NOT NULLSELECT * FROM student_score WHERE class_no IS NULL;4. 排序与限量让查询结果有章法4.1 ORDER BY的排序逻辑数据库表里的数据默认是没有顺序概念的它们按存储顺序存放插入顺序不等于查询顺序。想让结果有序必须明确指定ORDER BY子句。比如按成绩从高到低排名SELECT stu_name, course, score FROM student_score ORDER BY score DESC;DESC表示降序ASC表示升序。默认不写时是ASC。我见过不少人误以为默认是主键排序或插入顺序其实在MySQL里没有ORDER BY时结果顺序是不保证的尤其数据量一大或者走了不同执行计划顺序可能完全变样。所以只要你对顺序有要求就必须显式写出ORDER BY。4.2 多列排序怎么排单列排序很容易理解多列排序的规则是先按第一列排第一列相同时再按第二列排。比如你想先按课程排再按成绩降序SELECT stu_name, course, score FROM student_score ORDER BY course ASC, score DESC;执行结果是数学在前语文其次英语最后每门课程内部成绩从高到低排列。这个逻辑跟字典排序很像先比首字母首字母相同再比第二个字母。这里没有技巧只有一条铁律——ORDER BY后面的列顺序决定了排序的优先级。4.3 LIMIT的两种用法和翻页陷阱LIMIT用来限制返回行数。最简单的用法是只给一个参数代表返回前N行。比如只查成绩最高的那一行SELECT stu_name, course, score FROM student_score ORDER BY score DESC LIMIT 1;LIMIT还有两个参数的写法LIMIT 偏移量, 行数。偏移量表示跳过多少行。比如跳过前2行取接下来3行SELECT stu_name, course, score FROM student_score ORDER BY score DESC LIMIT 2, 3;这个写法在分页场景很常见但要注意一个巨大的坑LIMIT 100000, 20这种翻到很后面的写法MySQL依然要扫描前面十万行性能极差。公司里遇到深分页问题通常会改成基于游标的查询用WHERE id 上一次最大id的方式去翻页。新手阶段可以先记住LIMIT的偏移量越大越慢不要为了图省事直接搞一个天文数字。5. 聚合与分组从明细数据中提炼结论5.1 聚合函数COUNT、SUM、AVG、MAX、MIN查询操作远远不止“把数据取出来”很多时候还需要对数据做统计汇总。这时候聚合函数就该登场了。所谓聚合函数就是把多行的数据压缩成一个结果值。常用的有五个COUNT(*)或COUNT(列名)统计行数SUM(列名)求和AVG(列名)求平均值MAX(列名)求最大值MIN(列名)求最小值比如我想知道一共有多少条考试记录SELECT COUNT(*) AS total_records FROM student_score;想求数学平均分SELECT AVG(score) AS avg_score FROM student_score WHERE course 数学;这里有一个细节需要强调COUNT(*)和COUNT(score)并不完全等价。COUNT(*)统计所有行包括NULLCOUNT(列名)只统计该列不为NULL的行。如果某列允许NULL你统计行数时用错函数结果会差很多。这个区别在生产环境特别致命比如统计用户数时如果email列允许为空COUNT(email)会漏掉一大批人。5.2 GROUP BY的分组逻辑聚合函数单独使用是把整张表当成一组。但你经常需要按某个维度分组统计比如“每个学生的平均分”。这就需要GROUP BYSELECT stu_name, AVG(score) AS avg_score FROM student_score GROUP BY stu_name;这条语句会先把数据按学生姓名分成若干组然后对每组分别计算平均值。执行逻辑是这样MySQL扫描完所有数据后根据GROUP BY的列把相同的行聚在一起每组输出一行结果。所以SELECT后面出现的列要么是分组列本身要么是聚合函数计算的结果不能随便混入其他列。比如下面这个写法在大多数场景下就是错误的SELECT stu_name, course, AVG(score) FROM student_score GROUP BY stu_name;因为course没有出现在GROUP BY里MySQL无法确定同组内有多个不同课程时到底该显示哪一门。虽然MySQL某些版本下允许这种查询默认非严格模式可能不报错但它返回的值完全是随机且不可靠的这种写法要坚决避免。5.3 HAVING和WHERE的分工差异分组之后如果想再过滤能用WHERE吗答案是不能。WHERE是在分组之前逐行过滤的而分组后产生的聚合结果比如AVG(score)在WHERE阶段根本不存在。想过滤聚合结果必须用HAVING。经典例子找出平均分大于85的学生SELECT stu_name, AVG(score) AS avg_score FROM student_score GROUP BY stu_name HAVING avg_score 85;我见过不少人把HAVING和WHERE混用要么该用HAVING时用WHERE导致报错要么WHERE能解决的事非要绕道HAVING。记住这句口诀就能少踩90%的坑先有行过滤WHERE后有组过滤HAVING。能用WHERE提前过滤掉的记录就别留到HAVING阶段才处理这样可以减少分组的数据量提升查询效率。6. 多表连接的查询思路6.1 INNER JOIN内连接怎么用前面所有例子都在单张表里操作但实际开发中数据往往是分散在多张表里的。比如考试成绩表可能只存了学生ID学生的姓名、班级、年级等信息在另一张学生信息表里。这时候要查某个学生的所有信息和成绩就得用连接查询。先准备一张学生信息表CREATE TABLE student_info ( stu_id INT PRIMARY KEY, stu_name VARCHAR(50), class_no VARCHAR(20), enroll_year YEAR ); INSERT INTO student_info (stu_id, stu_name, class_no, enroll_year) VALUES (1, 张伟, A01, 2023), (2, 李娜, A01, 2023), (3, 王强, B01, 2022), (4, 刘洋, B01, 2022);注意这张表里有stu_id但student_score表里没有所以真要关联还得改表结构。为了演示我重新建一张成绩表带上stu_idCREATE TABLE student_score_new ( id INT PRIMARY KEY AUTO_INCREMENT, stu_id INT, course VARCHAR(50), score DECIMAL(5,2), exam_date DATE ); INSERT INTO student_score_new (stu_id, course, score, exam_date) VALUES (1, 数学, 92.5, 2024-10-15), (2, 数学, 78.0, 2024-10-15), (3, 数学, 85.5, 2024-10-15), (4, 数学, 91.0, 2024-10-15), (1, 语文, 88.0, 2024-10-16), (2, 语文, 96.5, 2024-10-16), (3, 语文, 72.0, 2024-10-16), (4, 语文, 83.5, 2024-10-16);现在查一下每位学生的姓名、班级、课程和成绩SELECT si.stu_name, si.class_no, sc.course, sc.score FROM student_info AS si INNER JOIN student_score_new AS sc ON si.stu_id sc.stu_id;INNER JOIN只返回两张表能匹配上的数据。意思是学生信息表里有成绩表里也有两者通过stu_id对上了才会出现在结果中。如果某个学生没有成绩记录他就不会出现在结果里。“匹配不上就丢掉”是内连接最核心的特点。6.2 LEFT JOIN和RIGHT JOIN的区别LEFT JOIN是连接查询里另一个高频用法。它的特点是左表FROM后面那张表的行全部保留右表只有匹配上的行才出现匹配不上就用NULL填充。比如我想把学生信息表的数据全列出来没考试的人也保留只是成绩显示为空SELECT si.stu_name, si.class_no, sc.course, sc.score FROM student_info AS si LEFT JOIN student_score_new AS sc ON si.stu_id sc.stu_id;这条语句执行后即使某个学生没有考试成绩他依然会出现在结果里course和score显示为NULL。这个场景在工作中很常见——比如统计“哪些人没打卡”“哪些订单没支付”本质上都是LEFT JOIN后查右表关联字段是否为NULL。RIGHT JOIN恰好相反右表全保留左表匹配不上就用NULL。但因为RIGHT JOIN能通过交换表顺序改写为LEFT JOIN所以实际项目中很少直接用RIGHT JOIN团队协作风格也倾向于统一用LEFT JOIN避免别人阅读时绕圈子。6.3 连接查询的字段限定技巧写连接查询时最烦的问题就是两张表都有同名字段。比如student_info有stu_namestudent_score_new里没有stu_name但两边可能都有id之类的字段直接写SELECT id时MySQL会报“字段不明确”的错误。所以连接查询里必须养成写表别名、用“别名.列名”引用字段的习惯。SELECT si.stu_name, sc.course, sc.score FROM student_info AS si LEFT JOIN student_score_new AS sc ON si.stu_id sc.stu_id WHERE sc.score IS NULL;这段代码的意思就是找出有学生信息但没有任何成绩记录的人。在排查数据缺失问题时特别有用。记住一个判断技巧——只要你是为了“补全信息”优先考虑LEFT JOIN只是为了“求交集”用INNER JOIN就够了。7. 查询操作的常见报错与排查手册7.1 那些一眼看去莫名其妙的报错查询写多了总会遇到报错。我这里整理几个新手最常碰到的情况全是亲身踩过的坑报错信息常见原因正确姿势Unknown column xxx in where clause列名拼错了或者单引号用错了检查列名拼写字段名用反引号字符串值用单引号... isnt in GROUP BYSELECT的列没出现在GROUP BY中只选择分组列或聚合函数结果Invalid use of group functionWHERE里用了聚合函数聚合条件移到HAVING中去You have an error in your SQL syntax语法顺序错乱或关键字拼写错误按完整语法骨架逐段检查Duplicate column name id连接查询中两个表的同名列都被选中使用别名明确指定某个表的列这类报错其实并不可怕关键是要学会看报错的位置提示。MySQL一般会指出near xxx附近有问题重点检查这个位置前后的关键字顺序和拼写。7.2 查询结果与预期不符的排查思路比报错更让人抓狂的是SQL能跑结果却不正确。最常见的几个隐性坑我依次说一遍。第一个坑是NULL参与运算的规则。任何值和NULL比较结果都是UNKNOWN最终过滤时UNKNOWN被视为不满足条件。比如你写WHERE score NULL查不出任何数据就是这个原因。第二个坑是字符串比较的隐式转换。如果字段是VARCHAR类型但存的是数字和数值比较时MySQL会尝试把字符串转成数字转换失败的可能返回0或跳过该行结果跟你本意完全不一样。第三个坑是DISTINCT和ORDER BY的组合。有时候你以为去重后按某列排序但DISTINCT作用在所有SELECT列上如果SELECT了多个列排序结果会出乎意料。遇到这种场景先分析结果集再排序。第四个坑是LIMIT和ORDER BY一起用时如果排序不唯一比如成绩相同返回哪些行是没有固定保证的。要确保一致性和可预期性可以再加一列唯一字段参与排序。7.3 顺手说几个查询优化的底层经验虽然这篇文章的主题是简单查询但有几个和查询相关的性能概念还是要尽早建立认知。第一个是索引。对经常出现在WHERE和JOIN条件里的字段建索引能让查询速度大幅提升尤其数据量过百万后差异非常明显。比如上面的查询如果经常用stu_id关联那就应当在student_score_new的stu_id字段上建索引CREATE INDEX idx_stu_id ON student_score_new(stu_id);第二个是避免SELECT *。显式列出你需要的列一方面减少网络传输数据量另一方面也方便别人读懂你的查询意图。第三个是尽量避免在WHERE的字段上做函数运算比如WHERE YEAR(exam_date) 2024这会导致索引失效应该改写成WHERE exam_date 2024-01-01 AND exam_date 2025-01-01这样的范围条件。8. 我的实操体会从“能跑”到“可靠”写这篇文章的过程中我又重新审视了一遍自己这些年用MySQL写查询的习惯。有个感触特别深很多人包括曾经的我喜欢从网上抄一段SQL跑通就算完事但完全不知道这条SQL为什么这样写、隐含了什么前提。比如最简单的SELECT * FROM table LIMIT 10在数据量小的时候毫无问题可一旦表里数据涨到千万级这种写法就可能让数据库服务器卡顿甚至把内存打满。所以我的建议一直很朴素把基础查询的每个环节吃透比背一百个冷门函数都管用。你现在花半小时把这个文章里的每条SQL亲手敲一遍把执行结果和预期对照一下之后再遇到业务上的查询需求就不会慌。尤其是WHERE、GROUP BY、HAVING、ORDER BY这几个子句的配合关系真的值得多花时间琢磨清楚——它们是所有复杂报表、数据统计分析的最小积木。另外分享一个我常用的验证小技巧写完一条比较复杂的统计查询后先加一个COUNT(*)看看总行数是否符合预期再用LIMIT 5抽查几行明细数据。通过“总数比对”加“明细抽查”双重校验能排查掉绝大多数逻辑漏洞。这个习惯帮我避免过好几次线上统计出错的尴尬场面。最后再补充一句MySQL查询入门不难难的是懂得每一条命令背后到底发生了什么。带着这个意识去学你会比大多数人都走得稳。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑