从接触数据库开始大多数人的第一条SQL都是SELECT而SHOW往往是那个“我知道它存在但很少深入研究”的兄弟命令。这个主题虽然叫“简单检索”但SELECT和SHOW几乎撑起了日常数据库操作的半边天前者负责从一张或多张表里按条件取回你真正需要的数据后者负责让你在几秒钟内看清当前实例里有什么库、什么表、表结构长什么样、索引建在哪里。这两条命令用好了比背一堆复杂函数实在得多。这个主题适合刚入门 SQL 的开发者也适合那些写了好几个月业务查询、但遇到“查询很慢”或“表结构不对劲”时只能干瞪眼的人。我接下来不会只罗列语法而是从实际使用场景出发把这些语句掰开揉碎顺带讲讲我踩过的坑和现在还在用的排查习惯。1. SELECT和SHOW到底解决什么问题1.1 为什么要把这两条命令放在一起讲很多人觉得 SELECT 是业务查询SHOW 是运维工具两者互不相干。实际工作中这两者经常交替出现你接到一个需求得先SHOW TABLES看看有没有合适的表再SHOW CREATE TABLE确认字段名和类型之后才写 SELECT等查询结果不对又要回去SHOW INDEX FROM看索引有没有生效。所以它们是连在一起的排查闭环而不是两套孤立语法。我习惯把 SELECT 理解成“从已知表里按条件取数”把 SHOW 理解成“查看数据字典和实例状态”。SHOW 本质上是在读取元数据它并不会把表里的业务数据一行行拉出来所以执行速度通常很快非常适合在不确定环境状态的时候先探路。SELECT 则是真正把存储引擎里的数据页解析、过滤、投影并返回结果集这才是查询性能问题的重灾区。1.2 适合谁的阅读方式如果你刚学数据库建议把文中的 SQL 都手动敲一遍不要复制粘贴你会发现很多报错其实是引号、逗号和关键字顺序的问题。如果你已经写过一段时间 SQL重点看后面关于执行顺序、SHOW 变量排查和常见问题的部分这些内容在常规文档里很少写得这么直白。我会用一个简单的员工表当贯穿案例表名emp结构如下CREATE TABLE emp ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL COMMENT 工号, emp_name VARCHAR(50) NOT NULL COMMENT 姓名, dept VARCHAR(30) COMMENT 部门, salary DECIMAL(10,2) DEFAULT 0 COMMENT 薪资, hire_date DATE COMMENT 入职日期 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;后面所有例子都围绕这张表展开你把它复制到自己的测试库里跟着操作效果最直观。2. SELECT骨架列选择、别名与表达式2.1 最基础的 SELECT 写法SELECT 语句的基本结构很简单SELECT column1, column2 FROM table_name;SELECT后面跟的是“我要哪些列”FROM后面跟的是“从哪张表取”。如果你想看全部列可以用SELECT *但注意这不是一个值得长期依赖的写法。*意味着把表里所有字段都返回网络传输和内存消耗都更高而且如果别人在后面 ALTER TABLE 加了字段你的查询结果集会悄悄变化这很容易埋雷。生产环境里我习惯显式列出字段哪怕只是要 5 个字段中的 2 个SELECT emp_no, emp_name FROM emp;这样做的最大好处是结果集稳定、语义清晰配合代码审查时一目了然。2.2 别名和表达式SELECT不仅能取列还能算SELECT 后面不仅可以填列名还可以填表达式。比如你想算年薪可以写SELECT emp_name, salary * 12 AS annual_salary FROM emp;这里的AS annual_salary是给计算结果取了一个别名。AS可以省略直接写salary * 12 annual_salary也能运行但可读性会差一些尤其当表达式复杂时看不出哪一段是别名。我建议始终写AS代码清晰度提升不是一点半点。SELECT 还可以不依赖表直接当计算器用SELECT 1 1, NOW(), hello AS greeting;这在调试时很实用。比如你想确认当前 MySQL 版本支持的日期函数行为或者想快速算一个业务数字不用开 Python直接跑一条 SELECT 就行。2.3 一个容易忽略的点表达式的返回值和 NULL表达式里如果参与运算的列中有 NULL结果通常也是 NULL比如salary * 12当salary是 NULL 时年薪就是 NULL而不是 0。很多新手在这里栽过跟头后面在 WHERE 部分我还会详细说 NULL 的判断。这里先记住一个原则NULL 不是 0也不是空字符串它表示“未定义”所有运算碰到 NULL 都会扩散成 NULL。如果需要把 NULL 转成 0 再计算用IFNULLSELECT emp_name, IFNULL(salary, 0) * 12 AS annual_salary FROM emp;很多统计报表因为没做这一步最后算出的平均值、合计值是错的。这种问题最难查因为它不报错只是结果数字不对。3. WHERE过滤写条件之前先想想这几件事3.1 比较条件和逻辑组合如果没有 WHERESELECT 会把整张表所有行都取出来这叫全表扫描。加了 WHERE 之后MySQL 才按照条件做过滤。基础写法大家都会SELECT emp_name, dept, salary FROM emp WHERE dept 技术部;比较运算符包括,,,,,!或。特别提醒一下SQL 里的等值判断是单等号不是双等号这和大多数编程语言的习惯相反。我见过合作方的同事拿写 SQL直接被报语法错这不是高端错误但确实很容易发生。多个条件组合用AND和ORAND优先级高于OR。如果你想要的是“技术部或销售部里薪资大于 10000 的人”必须加括号SELECT emp_name, dept, salary FROM emp WHERE (dept 技术部 OR dept 销售部) AND salary 10000;如果不加括号实际语义会变成dept 技术部 OR (dept 销售部 AND salary 10000)查询结果完全不一样。我排查过好几个因为漏括号导致数据对不上的问题最后都是靠逐条核对 WHERE 语义才发现的。3.2 LIKE模糊匹配%和_的区别搜索场景常用 LIKE两个通配符必须区分清楚%代表任意长度的任意字符包括零个字符_代表恰好一个字符。看几个例子-- 姓“张”的员工 SELECT emp_name FROM emp WHERE emp_name LIKE 张%; -- 名字第二个字是“三”的员工 SELECT emp_name FROM emp WHERE emp_name LIKE _三%;_这种通配符很容易被忽略但在做姓名、编码类校验时非常有用。另外LIKE 带前导通配符的写法%张通常无法使用普通索引会导致扫描行数变大。数据量不大的时候无所谓数据量上了百万级这个细节会直接让查询时间从毫秒级跳到秒级。3.3 NULL判断这条记录为什么没被查出来这是 SELECT 查询中最高频的“坑”必须单独说。SQL 里判断某个字段是否为 NULL不能用也不能用!必须用IS NULL或IS NOT NULL-- 找出没有分配部门的员工dept字段是NULL SELECT emp_no, emp_name FROM emp WHERE dept IS NULL; -- 找出分配了部门的员工 SELECT emp_no, emp_name FROM emp WHERE dept IS NOT NULL;如果写WHERE dept NULL语法不会报错但结果永远是空集因为 NULL 和任何值比较的结果都是 UNKNOWNWHERE 只保留结果为 TRUE 的行。NULL 也参与不了 IN 判断比如dept IN (技术部, 销售部, NULL)NULL 这个项是没有意义的。我还遇到过一种隐蔽情况应用层把“无部门”录成了空字符串而不是 NULL导致IS NULL查不到。这不是 SQL 的问题是数据规范的问题。写查询之前最好先确认目标字段到底存的是 NULL 还是空字符串否则统计结果会悄悄缺一块。3.4 IN和BETWEEN的边界IN适合枚举几个确定值的场景SELECT emp_name, dept FROM emp WHERE dept IN (技术部, 产品部);这里有个很多人不熟悉的点IN列表里如果有 NULL不影响其他值的判断但会拖慢一些优化器对索引的选择大数据量下尽量别把 NULL 写进 IN 列表。BETWEEN是闭区间包含边界值SELECT emp_name, salary FROM emp WHERE salary BETWEEN 8000 AND 12000;等价于salary 8000 AND salary 12000。这个语义和 Java 里 substring 那套“左闭右开”完全不同碰上“取前 n 天数据”这类需求很容易把边界算重。4. 去重、排序和行数控制4.1 DISTINCT去重的成本和正确用法DISTINCT用来去除结果集中的重复行SELECT DISTINCT dept FROM emp;它的作用是“对 SELECT 出来的一组列做去重”不是只对第一列去重。比如SELECT DISTINCT dept, emp_name去重的是(dept, emp_name)这个组合而不是单独按 dept 去重。如果只需要看部门列表千万别写成这样结果会多出很多看似重复的行。强调一下DISTINCT 需要额外排序或哈希操作代价比普通查询高。能用业务约束避免去重的比如主键本身唯一就不要加 DISTINCT。我在性能排查时见过几次明明结果集很小却因为 DISTINCT 导致临时表 explode最后内存爆了改成业务字段唯一索引后问题消失。4.2 ORDER BY排序方向和多列规则排序用ORDER BY默认是升序ASC降序要显式写DESCSELECT emp_name, salary FROM emp ORDER BY salary DESC;多列排序时只有前一列的取值相同才会用后一列继续排序SELECT emp_name, dept, salary FROM emp ORDER BY dept ASC, salary DESC;上面的语句会先按部门升序排同一个部门内部再按薪资降序排。注意ORDER BY里可以使用 SELECT 列表中的别名这在 MySQL 里是允许的但建议不要在别名和原有列名混在一起时依赖这个特性容易踩到“实际排序列和展示列不一致”的坑。4.3 LIMIT控制行数LIMIT用来限制返回行数常用在分页和“取前几条”场景-- 取薪资最高的前5名 SELECT emp_name, salary FROM emp ORDER BY salary DESC LIMIT 5; -- 分页跳过第0条开始取10条 SELECT emp_name, salary FROM emp ORDER BY salary DESC LIMIT 10 OFFSET 0; -- 分页第二页 SELECT emp_name, salary FROM emp ORDER BY salary DESC LIMIT 10 OFFSET 10;LIMIT 10 OFFSET 20也可以写成LIMIT 20, 10注意顺序是“偏移量, 行数”。我刚开始用的时候经常写成LIMIT 10, 20以为是“10行20偏移”结果查询结果莫名其妙少一截。建议统一用LIMIT ... OFFSET ...这种写法可读性更好。分页还有一个容易忽略的点如果ORDER BY的列不是唯一列比如ORDER BY salary DESC而有多个人薪资相同分页时可能出现同一条记录在上一页出现、在下一页又出现的情况。解决方法是排序时追加唯一列比如ORDER BY salary DESC, id ASC。4.4 回顾一下SELECT的完整执行顺序写 SELECT 时很多人以为数据库是“SELECT 先执行再 FROM再 WHERE”所以看到 GROUP BY 和 HAVING 就发怵。实际上 SQL 的执行顺序是FROM - WHERE - GROUP BY - HAVING - SELECT - DISTINCT - ORDER BY - LIMIT也就是说WHERE 是在 GROUP BY 之前执行的所以 WHERE 里不能使用聚合函数比如WHERE COUNT(*) 1会直接报错得用HAVING COUNT(*) 1放在 GROUP BY 之后。而ORDER BY是在 SELECT 之后执行的所以可以使用 SELECT 里定义的别名。理解这个顺序你就能解释很多看起来很“反直觉”的报错。这也是面试里常考的点但它不是纯理论我在业务里真的遇到过把 WHERE 写成WHERE salary AVG(salary)的写法逻辑上感觉对实际必须改成子查询或 HAVING。5. SHOW命令家族不写业务查询也能摸清数据库家底5.1 用SHOW快速确认当前环境SHOW语句是 MySQL 特有的“查询元数据”入口它不像 SELECT 那样读业务数据而是直接查看系统表和信息。入门阶段最常用的几个-- 当前实例有哪些数据库 SHOW DATABASES; -- 当前数据库有哪些表 SHOW TABLES; -- 查看某个库的所有表 SHOW TABLES FROM test_db;看到SHOW TABLES的结果很多人才意识到自己连“有哪些表”都没建立认知。我在接手前任留下来的项目时第一件事就是跑这三条命令花五分钟摸清库里有什么比直接写 SELECT 靠谱得多。5.2 查看建表语句SHOW CREATE TABLE这是排查表结构最重要的命令SHOW CREATE TABLE emp;它会返回创建该表的完整 DDL 语句包括字段类型、默认值、注释、索引、字符集、外键等。如果表是后面 ALTER 过多次的这个 DDL 是最新的“实际结构”比你在某个 ER 图里看到的更真实。在命令行客户端里可以加\G让输出竖排SHOW CREATE TABLE emp\G\G这个技巧对字段特别多的表非常有用横排输出在屏幕上折行会折得乱七八糟竖排一眼看全。平时写新查询之前先用它确认列名是emp_name还是employee_name能省下大量“列名不存在”的报错时间。我见过团队里因为不同环境表结构没同步SELECT 在开发环境跑得通、生产环境直接报错最后就是靠SHOW CREATE TABLE逐项对比发现的。5.3 看索引、表状态和系统变量SHOW INDEX FROM用来查看某张表的索引信息SHOW INDEX FROM emp;它显示索引名、字段名、索引顺序、唯一性、基数等。排查“为什么这个查询走不了索引”时先看这里的索引定义最直接再用 EXPLAIN 看优化器实际选择信息完全对得上。SHOW TABLE STATUS可以看当前库所有表的大致情况SHOW TABLE STATUS;包括每张表的行数估计值、平均行长度、数据大小、索引大小、创建时间、最后更新时间。做容量评估时很有用它的Rows是估算值InnoDB 下并不精确但足以判断表的量级。SHOW VARIABLES则用来查看系统配置比如常见的安全和性能开关SHOW VARIABLES LIKE sql_mode; SHOW VARIABLES LIKE character%; SHOW VARIABLES LIKE max_allowed_packet;LIKE xxx在这里可以用来过滤变量名%是通配符。排查乱码问题我会先看character_set_client和character_set_results排查导入文件报错我会看max_allowed_packet碰到 GROUP BY 报错会看sql_mode。这些排查习惯后面会展开讲。6. 顺手整理的常见问题与排查心得6.1 查询结果和预期不符先查NULL和空串我最早做数据统计时遇到一个典型的场景导出的名单少了几个人检查 SQL 和条件都没问题后来发现漏掉的人dept字段是空字符串而我过滤条件是dept ! 技术部。关键矛盾在于SQL 里NULL ! 技术部的结果是 UNKNOWN不会返回但空字符串是明确定义的 ! 技术部结果为 TRUE会返回。两者一个没查出来、一个查出来合在一起看就很诡异。处理思路是写查询之前先想清楚目标字段里有没有 NULL、有没有空串。要么在查询里显式兼容要么让数据规范强制统一。直接把两种可能都列出来不要猜测。6.2 引号和转义单引号、双引号、反引号MySQL 默认字符串用单引号数字不需要引号日期通常用字符串形式加引号SELECT * FROM emp WHERE hire_date 2023-01-01;双引号在 MySQL 里默认也当作字符串但开启 ANSI_QUOTES 模式后双引号会被当成标识符引用符行为大变。为了兼容性和可读性统一用单引号包字符串标识符用反引号。如果字符串本身包含单引号比如姓氏OBrien需要转义SELECT * FROM emp WHERE emp_name O\Brien;或者把外层换成双引号也是可以的不过不推荐依赖这种技巧。字符串里想表示反斜杠需要写成\\。这些细节平时遇不到遇到一次就够你折腾半天。6.3 字段名是保留字的处理有些字段名恰好是保留字比如order、desc、group。如果建表时用了这类字段名SELECT 里直接写往往会报语法错误必须用反引号包起来SELECT order, id FROM order_table WHERE desc 正常;这里也提醒一句建表时尽量避免用保留字做字段名。别贪图数据库编辑器给关键字着色等以后必须带着反引号写所有 SQL 时你会后悔这个决定的。6.4 SELECT查询慢的初步排查顺序有人问为什么 SELECT 突然变慢我一般按这个顺序排查第一SQL 语句本身有没有SELECT *是不是把不需要的字段都拉回来了。第二WHERE 条件里的字段是否有索引尤其是不是用了前导通配符 LIKE 或在字段上做了函数计算比如WHERE DATE(hire_date) 2023-01-01这会让索引失效改写为范围条件WHERE hire_date 2023-01-01 00:00:00 AND hire_date 2023-01-02 00:00:00才能走索引。第三用EXPLAIN SELECT ...看执行计划确认type是否出现ALL全表扫描key是否为 NULL。第四用SHOW PROCESSLIST看看当前会话里有没有堆积的长查询或锁等待。这些步骤里SHOW PROCESSLIST是第二个关键时刻。它能列出所有正在执行的 SQL包括耗时、状态、正在操作哪张表。如果发现很多会话处于Waiting for table metadata lock或Updating状态多半是有 DDL 在排队或者事务没提交这时候业务代码再优化都没用要先解决锁问题。6.5 SHOW和SELECT结合排查乱码乱码问题在 MySQL 里很常见。排查思路也很固定先SHOW VARIABLES LIKE character%看字符集设置再SHOW CREATE TABLE emp看表的字符集最后看连接串里有没有设置charsetutf8mb4。早期项目里出现过表是 utf8mb4、字段也是 utf8mb4但客户端连接用的是 latin1结果中文全部变成问号。这里我还想强调一个容易被忽略的配置sql_mode。有些项目里写入不规范比如非法日期、字符串超长但 sql_mode 非严格模式下 MySQL 会自动截断或替换导致数据“存得进去读出来不对”。通过SHOW VARIABLES LIKE sql_mode你能快速判断当前实例是否够严格这对接下来的数据核对非常重要。7. 写在最后的个人实操习惯我在实际工作里有个习惯凡是新建的查询第一版一定先带上LIMIT 20跑一遍确认结果集形态符合预期再逐步放开。这么做能避免一个手滑把几十万行数据拉出来也能让你更快地发现在 WHERE 条件和 SELECT 列表里的低级错误。还有一点写完 SELECT 之后我会顺手看一眼SHOW WARNINGS。尤其在使用 INSERT SELECT、UPDATE JOIN 这类混合语句时很多被优化器悄悄忽略的警告不会直接报错但会留在警告列表里。主动看一眼能提前发现字段类型转换、NULL 扩散、截断这类隐蔽问题。这个小习惯让我少走了很多弯路。SELECT 和 SHOW 表面上都很简单难的是把它们用成一套顺手的方法论知道什么时候用 SHOW 探路什么时候用 SELECT 取数条件写错时知道去查 NULL、去查执行计划、去查系统变量。你把这套东西练熟了后面学 JOIN、子查询、窗口函数的时候会觉得一切都在意料之中。