ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SQL查询核心基础:从SELECT到JOIN的常用写法与避坑指南

SQL查询核心基础:从SELECT到JOIN的常用写法与避坑指南 这一期先聊SQL核心中的核心查询。别急着上来就看窗口函数和索引优化那些技术文章一抓一大把但基础不牢后面全是空中楼阁。我根据自己的实际使用经验和带新人的体会把SQL里最常用的那部分知识整理成上下两篇这篇先讲查询相关的核心逻辑、常用写法和最容易踩的坑争取让你看完就能动手写。先交代一下我自己的情况我日常主要跟MySQL和SQL Server打交道项目里也用过Oracle和国产的达梦下面讲到方言差异的地方会特别标注。如果你正好在准备面试或者工作中需要写SQL但没什么系统学过这篇应该对你有用。老手也可以快速扫一遍尤其是“常见问题”那部分有些坑真的是过一段时间就会有人来问一次。1. SQL是什么为什么每个数据人都绕不开它1.1 SQL是一门“描述”语言不是你想象的那种编程语言很多人第一次接触SQL会下意识拿它跟Java、Python对比觉得它也是某种编程语言。这个理解不算错但很影响学习心态。SQL全称是Structured Query Language结构化查询语言。它最核心的设计思想是你告诉数据库“我要什么”而不是“怎么拿”。Java里你要遍历一个数组取符合条件的元素得写循环、写判断SQL里你只要写一句SELECT xxx FROM 表 WHERE 条件数据库自己决定怎么扫描、怎么关联、怎么排序。这套逻辑是上世纪70年代提出的关系模型到今天依然是数据处理领域的主流。所以学SQL第一件事是转变思维方式不要去想“数据库是怎么做的”先想清楚“我要的是什么结果”。这个思维转换越早完成写SQL越顺手。很多新手写SQL很痛苦不是语法记不住而是脑子里还在用编程语言的思路去指挥数据库自然拧巴。1.2 SQL能做什么不只是增删改查SQL的全貌大概分成四块DQL数据查询语言SELECT最核心也是本文重点。DML数据操作语言INSERT、UPDATE、DELETE往表里加数据、改数据、删数据。DDL数据定义语言CREATE TABLE、ALTER TABLE、DROP TABLE管理表结构。DCL数据控制语言GRANT、REVOKE控制谁能看什么、改什么。面试或者实际工作中DQL占的比重最高。不管你是数据分析师、后端开发、测试还是运维每天写得最多的就是SELECT。而且SQL是面试题的常客从简单查询到复杂统计几乎每个技术岗都得考一轮。1.3 从真实需求看大家学SQL的常见卡点我搜了一下后台的搜索记录发现几个出现频率特别高的问题SQL Server版本选择。不少人搜“sql server 2008 r2下载”和“sql server 2022下载”说明大家在选环境时很纠结。我的建议很简单新项目直接上最新稳定版个人学习别用老古董。SQL Server 2008 R2早就不在官方支持期了装你都装不痛快学起来又憋屈没必要。慢SQL优化。这个其实是“SQL必知必会”的下半场内容但很多人前置关心了。说实话优化这东西你先把基础查询写利索了再谈优化才有意义。上来就折腾执行计划结果SELECT都写不利索没戏。SQL注入。安全话题严格说不是SQL语法问题而是程序写法问题。但既然搜索热度这么高我放在后面常见问题里专门讲一下原理和防御方式这个必须搞清楚。SQL语句去重、去除空值、输出序号。这些都是SELECT的常规操作这篇都会覆盖到。1.4 想练SQL先搞定一个能跑的环境学SQL不能光看必须上手敲。环境选择上我建议分场景如果你是纯新手只想快速验证语法我推荐用SQLite或者在线SQL练习平台。SQLite是一个嵌入式数据库不需要安装服务端下个工具就能跑零成本。等你有一定基础了再装MySQL或者SQL Server做更复杂的练习。如果你打算找工作我建议直接上MySQL或者SQL Server。MySQL在互联网公司用得最多网上资料也最多SQL Server在企业内部系统里仍然很常见尤其是Windows环境下的项目。安装的时候注意SQL Server安装包比较大步骤多一点但都是图形界面按着提示来就行唯一要记得的是选好实例名和身份验证模式。不要因为卡在安装就放弃这关过了后面全是坦途。另外提一句国内不少企业用达梦数据库。达梦的SQL语法大体兼容Oracle如果你以后可能接触国产化项目可以在学有余力时看看Oracle风格的写法后面转型会顺很多。2. 表、行、列数据库的最基本盘2.1 把数据库当成一个“超级Excel”用Excel来类比数据库是最容易理解的方式一张表Table相当于Excel里的一个Sheet。表里的一行Row相当于一行记录。表里的一列Column相当于一列属性。比如用户表列有id、name、age、created_at每一行就是一个具体用户。你要查所有用户的姓名就写SELECT name FROM user你要查年龄大于30的用户就写SELECT name FROM user WHERE age 30。这个Excel类比能帮你解决70%的入门困惑。Excel里你怎么筛选SQL里你基本可以怎么理解只是把鼠标点选换成了文字表达。2.2 主键是表的“身份证号”每张表都应该有一个主键Primary Key这是关系数据库里的一个基本设计原则。主键的作用是唯一标识一行记录不允许重复也不允许为空。最常用的主键类型是自增整数比如id每插入一行自动加1。也有用业务字段做主键的比如订单号、学号但前提是必须全局唯一。为什么主键这么重要因为很多操作要靠它精准定位数据。更新一条记录时UPDATE user SET age age 1 WHERE id 123这个WHERE id 123就是靠主键定位。如果表里没有主键你就要用多个条件去拼容易误伤其他行。2.3 数据类型别什么数据都往一个字段里塞建表的时候每一列都要指定数据类型。最常见的几类整数INT、BIGINT小数DECIMAL、FLOAT字符串VARCHAR、TEXT日期时间DATE、DATETIME、TIMESTAMP布尔BOOLEANMySQL里常用TINYINT代替一个新手容易犯的错是把数字存成字符串。比如手机号你说它是数字吧它又不需要加减乘除但如果你用VARCHAR存后面做范围比较就会出问题。再比如日期如果按字符串存排序就变成按字典序排结果完全不对。所以选类型不是随便选选而是要为后面的查询逻辑服务的。2.4 索引为什么加了索引查询就快了索引是数据库里一个很重要的概念初学者不用往深了钻但至少要明白它存在的意义。你可以把索引理解成书的目录。没有目录你要找某一页只能从头翻到尾这叫全表扫描有了目录直接定位到对应的章节效率翻倍。数据库里的索引就是给某些列建目录让WHERE条件能快速找到目标行。不过索引不是越多越好。每建一个索引写入数据时都要额外维护它会拖慢INSERT和UPDATE。所以实际工作中索引通常加在查询频繁的字段上而不是所有字段都加。2.5 SQL方言标准是标准各家有各家的脾气SQL有一个ISO标准但各家数据库实现并不完全一致。同一句SQL在MySQL里跑得好好的拿到SQL Server里可能就报错。最常见的就是“限制返回多少行”MySQL用LIMIT 10SQL Server用SELECT TOP 10Oracle和达梦用FETCH FIRST 10 ROWS ONLY或者老版本的ROWNUM所以你写SQL之前先搞清楚你连的是哪一种数据库。这个“方言差异”是初学者最容易忽略的坑后面我会专门列一个对比表。3. SELECT查询每天用得最多的语句3.1 最先学会的从一张表里拿数据最基本的SELECT长这样SELECT column1, column2 FROM table_name;比如查用户表里所有用户的姓名和年龄SELECT name, age FROM user;如果你想把所有列都查出来可以用星号SELECT * FROM user;但我建议你实际开发中尽量少用SELECT *。原因有两个一是你查出来的数据比你需要的多白白增加了网络传输和内存开销二是如果哪天表结构变了字段顺序变了你的代码可能会出问题。明确列出你需要的列是SQL的“政治正确”之一。3.2 给列起别名让结果更可读有些列名是英文缩写或者查询出来是表达式直接展示出来很怪。这时可以用AS起别名SELECT name AS 用户姓名, age AS 年龄 FROM user;AS那句在SQL Server和MySQL里可以省略直接name 用户姓名也行但为了清晰我建议写成AS。别名在后面的排序、分组里还能再次引用这个后面会碰到。3.3 DISTINCT去重查出唯一值很多人在进行数据清洗时最常用的操作就是去重。比如你想知道订单表里到底有哪些用户下过单每个用户可能会下很多单直接用SELECT user_id FROM order会把同一个用户查出很多遍。这时用DISTINCTSELECT DISTINCT user_id FROM order;它会把user_id重复的行合并成一条。这里有个坑如果你写SELECT DISTINCT user_id, status FROM order它去重的是(user_id, status)这个组合而不是只看user_id。也就是说同一个用户如果下过“待支付”和“已完成”两种状态的订单会输出两条。理解这点你才不会写出“去重没去掉”的SQL。3.4 OEDER BY排序让结果有顺序查询结果默认的顺序是不保证的取决于数据库怎么扫描数据。如果你希望按某个字段排序用ORDER BYSELECT name, age FROM user ORDER BY age DESC;DESC表示从大到小ASC表示从小到大不写默认是ASC。多列排序也很常见比如先按部门排再按工资排SELECT dept_id, salary, name FROM employee ORDER BY dept_id ASC, salary DESC;这个语句的意思是先按dept_id从小到大排在dept_id相同的情况下再按salary从大到小排。这个执行顺序要理解清楚不然你会得到意想不到的结果。关于NULL的排序不同数据库不一样。MySQL里NULL默认排在最前面升序时SQL Server里默认排在最后面。如果你对NULL的排序位置有要求建议显式写出来比如ORDER BY age IS NULL, age这样不管什么数据库都能得到一致的结果。3.5 限制返回行数TOP、LIMIT、FETCH的区别这是个高频面试点。三个数据库三种写法数据库写法说明MySQLSELECT ... LIMIT 10从第0行开始取10行SQL ServerSELECT TOP 10 ...直接取前10条Oracle / 达梦SELECT ... FETCH FIRST 10 ROWS ONLY或老版本用ROWNUM 10我见过很多从MySQL转到SQL Server的人第一反应就是写LIMIT然后报错一脸懵。这就是我前面说的方言问题。面试题里也经常故意出这种“你写的SQL在这个库报错”的题目考察的就是对不同数据库的了解程度。4. WHERE过滤从全表到精准4.1 最基本的过滤比较和等值SELECT查询没有WHERE就像你打开Excel不筛选直接看全部数据这在小表里没问题数据一大就完蛋。WHERE用来过滤行它的执行时机是先从表里取出所有行一行一行判断条件是否满足满足的才留下来进入下一步。SELECT name, age FROM user WHERE age 18;常用的比较操作符有、不等于有些数据库也认!、、、、。其中在SQL标准里是“不等于”MySQL也支持!SQL Server两种都支持。4.2 用IN、BETWEEN表达范围如果条件有多个离散值用IN很方便SELECT name FROM user WHERE status IN (active, pending);这个写法等价于status active OR status pending。但IN的写法更清晰而且在某些数据库里性能会更好因为它可以走索引优化。如果是连续的数值范围用BETWEENSELECT name, age FROM user WHERE age BETWEEN 18 AND 30;注意BETWEEN是包含边界的也就是age 18 AND age 30边界值都会被算进去。有些人习惯写BETWEEN 18 AND 30但希望排除30那不能改BETWEEN本身得自己写 18 AND 30。4.3 模糊匹配LIKE千万别忘了通配符LIKE用于模糊匹配字符串。两个通配符最常用%匹配任意长度的任意字符_匹配单个字符比如查所有姓“张”的用户SELECT name FROM user WHERE name LIKE 张%;查第二个字是“三”的用户SELECT name FROM user WHERE name LIKE _三%;这里有个特别坑的地方如果你写LIKE 张它只匹配“张”这个字不会匹配“张三”。必须加上通配符%才行。很多新手在这栽跟头查出来结果为空还以为数据有问题其实是LIKE用错了。4.4 NULL处理不是 NULL是IS NULL这是一个几乎每个人都会犯的错。SQL里NULL表示“未知”它不等于空字符串也不等于0。判断一个字段是不是NULL不能写 NULL而要写IS NULLSELECT name FROM user WHERE email IS NULL;如果你想查“邮箱不为空”的用户写email IS NOT NULL。为什么 NULL不行因为NULL和任何值比较的结果都是NULLNULL在WHERE条件里会被当作FALSE处理所以WHERE email NULL永远查不出数据。这个坑我在带人的时候几乎每批都会遇到现在提前指出来希望你别再走弯路。如果你想把NULL替换成某个默认值可以用COALESCE函数SELECT name, COALESCE(phone, 未填写) AS phone_display FROM user;COALESCE返回第一个非NULL的参数值这在做报表或展示时非常实用对应了很多人搜的“sql去除空值”的需求。4.5 AND、OR和括号优先级别让逻辑打架多个条件组合时用AND和OR连接。注意AND优先级比OR高如果你不记得这个规则就用括号明确分组。比如你要查“年龄25岁且姓张的用户或者年龄30岁的用户”SELECT name, age FROM user WHERE (age 25 AND name LIKE 张%) OR age 30;如果不加括号写成了WHERE age 25 AND name LIKE 张% OR age 30由于AND优先级高它实际变成了“年龄25且姓张或者年龄30”两种意思完全不同。这种逻辑错误很隐蔽数据量小的时候甚至发现不了。5. 分组聚合把数据压缩成结论5.1 聚合函数从数据里拿到统计值光把行查出来还不够很多时候我们要的是汇总结果。五个最常用的聚合函数COUNT统计行数SUM求和AVG平均值MAX最大值MIN最小值比如统计用户总数SELECT COUNT(*) AS user_count FROM user;求订单总金额SELECT SUM(amount) AS total_amount FROM order;这里有一个小细节COUNT(*)和COUNT(column)有区别。COUNT(*)统计所有行包括字段为NULL的行COUNT(column)只统计该字段不为NULL的行。如果你要统计“填了手机号的用户数”就该写COUNT(phone)。5.2 GROUP BY按类目分别统计聚合函数单独用的时候是对全表做汇总。如果你希望按某个维度分别统计就要用GROUP BY。比如按用户状态统计人数SELECT status, COUNT(*) AS cnt FROM user GROUP BY status;查询结果会变成多行每个status一行对应的人数就是该状态下的用户数。使用GROUP BY时有一个铁律SELECT后面出现的列要么是分组列要么是聚合函数。比如上面这个例子你可以在SELECT里写status和COUNT(*)但不能写name因为每个分组里有多个不同的name数据库不知道该取哪个值。这个规则写错了会直接报错MySQL在老的SQL_MODE下可能不报错但结果随机很迷惑。5.3 HAVING分组之后的过滤WHERE是在分组前过滤行HAVING是在分组之后过滤分组。一个是“哪些行参与统计”一个是“哪些分组展示出来”。比如统计每个部门的平均工资然后只留下平均工资大于8000的部门SELECT dept_id, AVG(salary) AS avg_salary FROM employee GROUP BY dept_id HAVING AVG(salary) 8000;WHERE和HAVING很容易混淆我记的诀窍是WHERE写在GROUP BY前面过滤的是原始行HAVING写在GROUP BY后面过滤的是分组。什么时候用哪个看你的条件是“针对每一行”还是“针对统计结果”。5.4 一个综合案例统计各部门人数和平均工资把前面几个知识点串在一起写一个稍微完整点的查询统计每个部门的员工人数和平均工资只保留员工数大于5的部门按平均工资从高到低排序。SELECT dept_id, COUNT(*) AS emp_count, AVG(salary) AS avg_salary FROM employee WHERE status active GROUP BY dept_id HAVING COUNT(*) 5 ORDER BY avg_salary DESC;执行顺序我简单说一下先通过WHERE把离职的员工过滤掉然后按部门分组分别计算人数和平均工资再用HAVING把人数不够的部门删掉最后按平均工资排序。理解这个顺序你写复杂SQL时就不会乱套。6. 多表关联SQL的价值在连表6.1 为什么要把数据拆成多张表初学者常会疑惑为什么不把所有数据都放在一张大表里回答这个问题要从数据冗余说起。假设用户表里存了用户所属的部门名称如果部门改名了你要把所有该部门下的用户数据一起改万一漏改一条数据就前后矛盾了。通过把部门单独拆成一张表用户表里只存部门的ID部门名称只维护一份就不会有这个问题。这个设计叫“规范化”是关系数据库的核心理念。好处是数据不冗余、好维护代价就是查询的时候需要把多张表关联起来这就用到了JOIN。6.2 JOIN的几种用法INNER、LEFT、RIGHTJOIN最常用的三种INNER JOIN只返回两个表能匹配上的行。查不到对应记录的就不出现。LEFT JOIN左表全部保留右表有匹配就带过来没有就用NULL填充。RIGHT JOIN右表全部保留左表没有匹配就NULL。这个用得少一些因为你可以把表的顺序换一下用LEFT JOIN达到同样效果。举个例子。用户表和订单表要查每个用户和TA的订单SELECT u.name, o.order_no, o.amount FROM user u LEFT JOIN order o ON u.id o.user_id;注意我在这里给表起了别名user u后面写u.name、o.amount就更简洁了。别因为懒不写别名查询字段一多全表名写起来很痛苦。6.3 INNER JOIN和LEFT JOIN怎么选一个表决定这里有个特别实用的经验你先想想“哪张表是主体”。如果你要统计“每个用户下的订单”主体是用户表那LEFT JOIN user和order没下单的用户也会显示出来。如果你只要“有订单的用户”主体是订单表那就INNER JOIN只保留成功匹配的行。从结果上看INNER JOIN查出来的行数可能比LEFT JOIN少——少的正是那些在左表里存在、但在右表里没有匹配的行。所以写关联前先问自己一句我要不要保留没有匹配的记录要就LEFT不要就INNER。6.4 ON和WHERE的区别过滤时机不一样LEFT JOIN里特别容易踩一个坑ON条件和WHERE条件放的位置不同结果完全不同。SELECT u.name, o.amount FROM user u LEFT JOIN order o ON u.id o.user_id AND o.amount 100;这个写法部门表全保留订单表先过滤出金额大于100的再关联没下单的用户仍然会出现订单字段为NULL。SELECT u.name, o.amount FROM user u LEFT JOIN order o ON u.id o.user_id WHERE o.amount 100;这个写法先关联再用WHERE把不满足金额条件的行过滤掉。因为没下单用户的o.amount是NULLNULL 100不为真所以这些用户也被过滤掉了。两条SQL一个保留全部用户一个只保留有有效订单的用户差别就是这么直接。这个问题的本质是ON条件在JOIN过程中起作用WHERE在JOIN完成后起作用。理解这个顺序你就能控制过滤时机了。6.5 子查询把查询结果当临时表用子查询就是嵌套在另一个SQL里的查询。最常见的两种场景第一种用在WHERE条件里。比如查“年龄大于平均年龄的用户”SELECT name, age FROM user WHERE age (SELECT AVG(age) FROM user);第二种用在FROM后面把子查询结果当成一张临时表来查询。比如查每个部门的最高工资然后在这个结果的基础上再排序、过滤SELECT dept_id, max_salary FROM ( SELECT dept_id, MAX(salary) AS max_salary FROM employee GROUP BY dept_id ) t WHERE max_salary 10000;这里的t是子查询结果表的别名MySQL里这个别名不能省略不然直接报语法错误。SQL Server和Oracle同样也需要别名所以养成习惯写完子查询括号后面立刻加一个别名。6.6 笛卡尔积警告忘了ON条件会爆炸如果两个表关联时没有写ON条件SQL会做笛卡尔积——两个表的所有行两两配对。用户表1000行订单表10000行结果就是1000万行。这种错误在测试环境可能没感觉在线上直接能把数据库打垮。我见过有新手写JOIN忘了ON然后奇怪为什么数据多出来这么多。排查思路很简单如果JOIN查询结果行数异常大先看是不是ON条件漏了。7. 函数和表达式让SQL能处理更多场景7.1 字符串函数拼、截、取、替换SQL里的字符串函数特别常用但不同数据库函数名差别很大。我先列几个常见场景和写法你根据自己用的数据库对号入座。拼接字符串MySQL用CONCAT(name, age)SQL Server用name age注意如果其中一个是数字SQL Server会自动转Oracle用name || age。截取字符串SUBSTRING(str, start, length)在MySQL和SQL Server里都能用。去空格TRIM(str)这个基本通用。转大小写UPPER(str)、LOWER(str)通用。函数记不住没关系关键是知道有这些能力。真到用的时候去官方文档搜“字符串函数”比死记强多了。网上很多人搜“sql函数用法大全”我劝你不要试图全背下来记住常用的剩下的随时查。7.2 日期函数最容易踩坑的一块日期处理在SQL里是最容易出错的。明明结果看起来没问题但一跨月、跨年就翻车。一个常见需求查最近7天的数据。MySQL可以这么写SELECT * FROM order WHERE create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY);SQL Server则是SELECT * FROM order WHERE create_time DATEADD(DAY, -7, GETDATE());看到差别了吗时间函数名不一样参数顺序也不一样。每次跨数据库写日期处理我都要去翻一下官方文档确认参数这很正常。另一个常见坑是时区。数据库存储的日期如果是UTC时间查询时没做时区换算查出来的数据在你本地看来就是“少了8小时”。排查问题的时候先确认时区再确认SQL不要纠结半天。7.3 条件分支CASE WHENSQL里的if-elseCASE WHEN是SQL里的条件表达式可以理解成“case when 条件 then 结果 else 默认值 end”。比如把用户状态从英文映射成中文SELECT name, CASE WHEN status active THEN 启用 WHEN status pending THEN 待审核 ELSE 停用 END AS status_text FROM user;CASE WHEN在报表统计里尤其好用。比如统计每个年龄段的人数SELECT CASE WHEN age 18 THEN 未成年 WHEN age BETWEEN 18 AND 30 THEN 青年 ELSE 中年以上 END AS age_group, COUNT(*) AS cnt FROM user GROUP BY age_group;注意这里的分组字段age_group是SELECT里的别名GROUP BY可以选择使用这个别名但不同数据库支持情况不一致MySQL支持SQL Server也支持Oracle在旧版本可能不行。稳妥起见可以写成GROUP BY CASE WHEN ... END不过可读性就差一些。7.4 输出行号ROW_NUMBER帮你排序编号很多人搜“sql 输出序号”本质上是要给查询结果加一个自增的行号。最标准的做法是用窗口函数ROW_NUMBER()。SELECT ROW_NUMBER() OVER (ORDER BY create_time DESC) AS row_no, name, create_time FROM user;这个函数会给每一行分配一个从1开始的序号排序依据在OVER里指定。如果还要按部门分组每个部门内部编号可以写ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY create_time DESC)。窗口函数是SQL进阶的一个重要方向因为它是“必知必会”的进阶内容后面下一篇文章我会专门展开。7.5 顺带说说SQL代码排版和工具写SQL写到后面可读性比炫技更重要。团队协作时一坨没有缩进的SQL别人看得头疼自己也看得头疼。我个人的排版习惯是关键字SELECT、FROM、WHERE、GROUP BY、ORDER BY单独一行每个字段一行字段太多时加缩进对齐。有些数据库客户端自带格式化功能比如DataGrip、DBeaver都有格式化SQL的快捷键这也是很多人搜索“sql代码排版工具”的原因。工具选型上如果你日常连MySQLMySQL Workbench够用如果你要连多种数据库我推荐DBeaver免费开源跨平台支持MySQL、SQL Server、Oracle、达梦等而且能直接导入SQL文件执行我们团队日常排查数据都用它。7.6 别背函数大全要学会“查手册”市面上那张“SQL函数用法大全”的帖子每个函数都列出来几十上百个看着很吓人。其实我工作这么多年常用的也就那二三十个。函数这东西是拿来查的不是拿来背的。遇到一个不熟悉的处理需求我的思路是先想清楚自己要做“字符串处理”“日期处理”还是“数值计算”再去翻对应数据库的手册找出候选函数然后写个简单SQL试一下效果。试的过程很快十几秒就搞定。这种做法比“我记得有个函数叫什么来着”靠谱得多。8. 常见问题与排查技巧实录8.1 语法报错You have an error in your SQL syntax怎么排查这个报错几乎人人都会遇到很多人一看就慌。我总结一下我的排查顺序第一步看报错位置。MySQL的报错会明确提示错误在“near ‘xxx’”附近先看xxx是什么大概率问题就出在它前面。第二步检查关键字拼写。SELECT写成SELEC或者TABLE写错都会触发语法错误。这个最基础但也是最多人犯的。第三步检查字符串和引号。字符串要用单引号括起来如果忘了闭合或者用了中文引号都会报语法错误。第四步检查逗号。多写一个逗号或者少写一个逗号也很常见尤其是在多字段的SELECT列表和多条件的WHERE里。第五步如果是嵌套查询把里面那一层SQL单独拉出来跑一遍。如果内层SQL能跑外层还报错那问题多半在外层的语法上。这个操作看起来很笨但排查效率极高。8.2 慢SQL优化先从执行计划说起“慢SQL优化”是搜索热词。这确实是个大话题但入门其实不复杂。你先得能定位到哪条SQL慢。很多数据库都有慢查询日志MySQL里可以通过SHOW VARIABLES LIKE slow_query_log查看是否开启。打开之后超过指定时间的SQL会被记录下来这就是你的优化清单。拿到一条慢SQL后第一件事不是闷头改写法而是看执行计划。MySQL里在SQL前面加一个EXPLAINSQL Server里是SET SHOWPLAN_ALL ON然后执行。执行计划会告诉你这个查询是怎么扫描表的有没有走索引两个表是怎么关联的。最常见的慢SQL原因一是没走索引全表扫描二是查询了不必要的列三是数据量太大但写法强制数据库做了大量计算。优化方向基本就是给WHERE和JOIN条件的列加索引、减少返回的列、避免在索引列上用函数、把子查询改成JOIN某些场景下。注意加索引这件事不是万能的。加了索引后写入性能会下降所以要在查询和写入之间找平衡。新手的建议是先定位到真正慢的SQL再优化别一上来给全表所有字段都加索引那是给自己挖坑。8.3 SQL注入经常被提起但没几个人真正搞懂的坑搜“sql注入万能密码绕过”的人不少。我今天从一个开发者的角度把SQL注入的原理讲清楚也讲讲怎么防。SQL注入的本质是程序把用户输入的内容直接拼进SQL语句导致用户输入被当成SQL代码执行。举个例子一个登录功能如果代码是这样写的SELECT * FROM user WHERE username admin AND password 任意值 OR 11如果用户在密码框输入了任意值 OR 11最终拼出来的SQL就是SELECT * FROM user WHERE username admin AND password 任意值 OR 11因为OR 11永远为真这个语句就会返回所有用户登录直接被绕过。这类攻击的原理就是这么朴素。防御的核心也特别简单不要相信用户输入不要拼SQL用参数化查询。在Java里用PreparedStatement的?占位符在MyBatis里用#{}Python里用?占位符这样用户输入只会被当成字符串来处理永远不会变成SQL代码的一部分。我现在接手任何项目第一件事就是检查有没有字符串拼接SQL的地方。国内很多面试也爱问这个不是为了让你去攻击别人而是考察你有没有安全意识知不知道怎么写出安全的代码。8.4 MyBatis动态SQL和“SQL执行10秒自动关闭”的困惑搜热词里有“mybatis动态sql”还有人问“jvm或者spring boot会设置一个sql执行10秒自动关闭吗”。先说动态SQL。MyBatis里的动态SQL本质就是用XML或注解写带有条件的SQL片段根据参数决定最终执行哪个SQL。你会看到类似sql idexample_where_clause、where、foreach这类的标签这是MyBatis Generator自动生成的通用查询模板。它的作用是拼接查询条件比如参数里传了name就加name ?没传就不加。理解动态SQL的关键是你得先会写静态SQL然后才能理解那些动态标签在拼接什么。连静态SQL都没写明白看这种模板自然是天书。再说“10秒自动关闭”。数据库连接池、JDBC驱动、应用层可能会有超时设置但通常不会有一个全局的“SQL执行10秒自动关闭”的简单开关。实际项目中如果遇到“执行到一半报超时”要看几个地方数据库自身的时间限制、连接池获取连接的等待时间、Statement的执行超时时间。Spring Boot里spring.datasource相关配置可以设置连接超时和验证查询但设置的是连接层面的行为不是在SQL执行到10秒时帮你kill掉。真出现慢SQL优先想的是优化SQL本身而不是调超时时间。8.5 跨数据库的兼容坑MySQL写法到了SQL Server就崩换数据库是很多公司要面对的现实问题从一个库迁到另一个库最痛苦的就是SQL不兼容。我整理了一张速查表大家换库的时候对着改功能MySQLSQL ServerOracle / 达梦限制行数LIMIT 10TOP 10FETCH FIRST 10 ROWS ONLY或ROWNUM字符串拼接CONCAT(a, b)a ba || b获取当前日期CURDATE()GETDATE()SYSDATE自增主键AUTO_INCREMENTIDENTITY(1,1)序列SEQUENCE分页LIMIT offset, countOFFSET ... ROWS FETCH NEXTOFFSET ... ROWS FETCH NEXT这张表不用背收藏起来真遇到的时候翻开看。犯过一次错你就知道为什么面试总要考方言差异了。8.6 权限和可见性为什么有些表“隐藏”了有同学搜“sql 对用户隐藏数据库”。这个场景在企业里很常见你用一个只读账号连数据库结果发现很多库根本看不到。这不是数据库“坏了”而是账号权限不够。SQL Server里登录名只能看到自己有权限的数据库MySQL里同样GRANT只授予了部分权限那么SHOW DATABASES就只会显示有权限的那部分。想隐藏某张表或某个库正确的做法是给应用分配最小权限账号而不是希望它从界面上消失。这也是安全审查的常见要求。在项目里我建议每个人都记住一个原则数据库账号的权限遵循最小够用原则。开发账号不要用sa或root随便一条DELETE写错条件没有权限保护的话哭都来不及。9. 写在后面SQL容易系统难这一篇的内容基本覆盖了SQL查询的常用主体SELECT、WHERE、分组、关联、函数、常见坑。写SQL本身不难难的是在正确的场景里选对写法并且理解数据库会怎么执行你的SQL。关于本系列的下一篇我打算重点讲窗口函数的实战用法、CTE公共表表达式怎么简化复杂查询、事务和锁是怎么回事、存储过程还值不值得学以及慢SQL优化的完整排查思路。这些内容在面试里是拉开差距的地方实际工作中也经常碰到。最后说几条我个人的建议碰到不会写的SQL先拆需求再套模板。把“我要按什么分组、要不要保留未匹配的行、过滤条件在分组前还是分组后”想清楚SQL自然就写出来了。一定要学会看执行计划。它不只能帮你找慢SQL还能帮你理解数据库的行为。多读官方文档少看二手总结。我写这篇也是想把常见经验做个梳理但真到细节层面官方文档永远是最权威的。练习方面你可以自己建几张表造点假数据然后试着回答这些问题每个用户的订单总金额是多少上个月没有下单的用户有哪些超过平均订单金额的订单长什么样这些题目看着简单真能一次写对的人不多。把基础打牢再往深处走会顺很多。
返回列表