ARTICLE DETAIL

资讯详情

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

SQL入门完全指南:从建表查询到慢SQL优化与安全

SQL入门完全指南:从建表查询到慢SQL优化与安全 很多人聊起SQL第一反应是“这不是程序员才用的东西吗”。其实我做了十几年数据相关的工作最深的体会是SQL早就不是程序员的专属技能了运营要拉数、产品要看漏斗、财务要对账、销售要管客户表只要你的工作跟表格数据沾边SQL就是那个能让你从“等别人给数据”变成“自己动手拿数据”的工具。这篇文章我尽量用一个老数据人的口吻把SQL入门路上最核心的东西给你捋一遍从建表、查询到去重、连接再到慢SQL优化和安全注意点保证你看完就能拿自己手头的表练起来。1. SQL到底是个什么东西为什么人人都该学一点1.1 从一张最简单的表说起咱们先忘掉所有高大上的概念把SQL想象成你和数据库之间的对话语言。数据库里存数据的结构说白了就是一张张二维表格跟Excel表格长得几乎一样有行有列。比如你现在手头有一张用户表列有用户ID、姓名、注册时间每一行就是一个用户。SQL就是让你用一句类似“给我看看所有注册时间在2023年的用户”的话让数据库把符合条件的记录返回给你。你可能会说Excel不是也能干这事吗对几千行数据用Excel没问题但当数据到了几十万行、几百万行或者你想把用户表和订单表关联起来算“每个用户一共买了多少钱”Excel就开始卡顿、公式复杂到你自己都看不懂。而SQL天生就是为这种批量、关联、聚合的场景设计的。你只需要写一句“SELECT 用户ID, SUM(金额) FROM 订单表 GROUP BY 用户ID”数据库会在极短时间内把结果算好还给你这是Excel完全比不了的。1.2 SQL和编程语言的区别在哪里很多人以为学SQL等于学编程心里先怵了三分。其实SQL和Python、Java这类通用编程语言有本质区别通用编程语言是“一步一步告诉计算机怎么做”SQL是“告诉数据库你想要什么结果至于怎么找、怎么排序、怎么优化数据库引擎自己会决定”。这就好比你去餐厅吃饭。编程语言是“你走进厨房告诉厨师先切葱、再热锅、然后倒油”你得指挥每个细节SQL是“你坐下来点菜说我要一份鱼香肉丝”至于厨师怎么切、什么火候你不用管只要结果正确。这种声明式的思维方式恰恰是SQL好上手的原因——你先把“想要什么”说清楚再慢慢学习怎么说得更高效。不过也别高兴得太早SQL的语法虽然只有那么几十个关键字但想写出优雅、高效、不出错的语句还是需要理解一些底层规则。这就是我下面要展开讲的内容。2. 入门第一步建库建表和基础增删改查2.1 你要先知道表长什么样在动手敲SQL之前先搞清楚数据库里的几个基本对象库、表、字段、记录。库就是一个文件夹表是里面的文件字段是文件的列记录是文件的每一行。我们平时说的“查数据库”绝大多数情况都是查表。在MySQL里建库一般是CREATE DATABASE 库名;建表是CREATE TABLE 表名 (...)。很多人上来就跳过建表直接去网上找练习数据其实我建议新手一定要亲手建一次表因为建表的过程能让你理解每一列的数据类型到底意味着什么——是整数还是小数、是固定长度的字符串还是可变长度的、能不能为空。后续遇到类型相关的报错多半是当初建表埋下的坑。2.2 用CREATE TABLE把表造出来我们实际建一张简单的用户表和订单表来感受一下。假设你手头有用户ID、姓名、注册时间还有订单表里的订单号、用户ID、下单时间、金额。CREATE TABLE users ( user_id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, register_time DATETIME ); CREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT NOT NULL, order_time DATETIME, amount DECIMAL(10,2) );这里有几个点新手容易忽略PRIMARY KEY是主键相当于每一行的唯一身份证不能重复也不能为空。你在建表时最好想清楚哪个列适合做主键通常是一个自增的数字列。VARCHAR(50)表示最多50个字符的字符串DECIMAL(10,2)表示最多10位数、小数点后保留2位的数值——金额别用FLOAT否则后面算账时浮点误差会让你怀疑人生。NOT NULL表示这个字段不能为空。很多业务里“用户名不能为空”是硬性要求如果表里已经存在空值加这个约束会报错。2.3 INSERT / UPDATE / DELETE 的日常用法建完表往里面塞数据用的是INSERTINSERT INTO users (user_id, name, register_time) VALUES (1, 张三, 2024-01-01 10:00:00);修改数据用UPDATE千万记得加WHERE。我曾经见过同事写了一条没有WHERE的UPDATE直接把整张表的金额都改成了同一个数幸好当时是测试库不然就是事故。所以UPDATE和DELETE的最重要原则是先写WHERE再写命令或者写完命令后反复确认WHERE条件是否正确。UPDATE users SET name 李四 WHERE user_id 1; DELETE FROM users WHERE user_id 2;另外要习惯用事务包裹多步写操作比如BEGIN/COMMIT这样万一中间某一步写错了还能ROLLBACK回滚。单条语句只要不提交事务也能撤销但很多新手用完就顺手提交了等发现错了才拍大腿。2.4 动手写一条查询SELECT 的骨架查询的骨架其实非常简单SELECT 列名 FROM 表名 WHERE 条件;你先理解这句话后面所有复杂查询都是在这个骨架上加东西。比如查“2024年注册的用户ID和姓名”SELECT user_id, name FROM users WHERE register_time 2024-01-01 AND register_time 2025-01-01;注意这里我没用BETWEEN而是用了一个左闭右开区间目的是为了避免漏掉2024-12-31 23:59:59这种带时分秒的边界值。这个细节我在面试题里见过很多次日常写统计SQL也是高频坑你先记住就行。3. 查询是SQL的魂过滤、排序、去重3.1 WHERE 条件过滤别把全表数据都捞回来很多人刚学会SELECT就喜欢SELECT *把整张表的每一列都查出来再用程序去做过滤。这不叫不会写而是给自己埋坑。数据量一大全表扫描就是慢SQL的头号原因。正确姿势是在WHERE里把条件写好只捞需要的行在SELECT后面只列需要的列尽量减少数据库的负担。另外还要学会组合条件AND表示并且OR表示或者IN表示“在其中”LIKE表示模糊匹配。举个例子查“姓张的用户或者VIP等级为3的用户”可以这样写SELECT user_id, name FROM users WHERE name LIKE 张% OR vip_level 3;需要注意的是LIKE %张%这种两边都带通配符的写法如果表数据量大很难用上索引会走全表扫描。能用前缀匹配比如张%就不要用中缀匹配。3.2 ORDER BY 排序和 LIMIT 限量查询结果默认顺序是不保证的你想按照某个规则排序必须用ORDER BY。默认是升序ASC降序用DESC。SELECT user_id, amount FROM orders ORDER BY amount DESC LIMIT 10;这段就是“拿金额最高的前10个订单”。LIMIT在MySQL里用来限制返回行数很多分页场景其实就是LIMIT 10 OFFSET 20意思是跳过20行拿10行相当于第二页的数据。不过分页越深性能越差这个等后面聊慢SQL再展开。3.3 DISTINCT 去重以及为什么去重没那么简单“SQL语句去重”和“SQL语句去重查询”是热搜词里出现频率很高的词组可见去重是入门时的必遇难点。最简单的去重是SELECT DISTINCTSELECT DISTINCT user_id FROM orders;这句话会返回所有出现过订单的用户ID重复的只保留一条。但很多新手的困惑在于我如果想把多列组合去重呢比如看“用户ID和城市组合有哪些不同的组合”你只需要把多列放在DISTINCT后面比如SELECT DISTINCT user_id, city FROM ...意思是这两列的组合不能重复不是单列不能重复。还有一个更隐蔽的问题DISTINCT是对查询结果集去重不是对源表去重。如果你查了10列其中9列相同但只要1列不同结果会被判定为“不重复”。有些场景确实需要“按某列去重保留其他列某条记录”这种稍微复杂一点一般用窗口函数ROW_NUMBER()配合分组来做我后面会讲到。4. 分组聚合和连接从单表到多表4.1 GROUP BY 聚合函数把明细变成汇总聚合函数有COUNT计数、SUM求和、AVG平均、MAX最大、MIN最小。光有聚合函数不够你得告诉数据库按什么维度聚合这就需要GROUP BY。举个例子统计每个用户的订单数和总金额。SELECT user_id, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders GROUP BY user_id;这里我用了AS给聚合结果起了个中文别名查询结果里列名就变成了order_cnt和total_amount方便阅读。你可能会问为什么GROUP BY user_id之后SELECT里还能写COUNT(*)和SUM(amount)因为聚合函数是“按组计算”的而user_id是分组依据所以这两类可以共存。反过来如果你SELECT里写了order_time但又没把它放在GROUP BY里大多数数据库都会直接报错。4.2 HAVING 与 WHERE 的分工很多新手分不清WHERE和HAVING。有一个特别好的记忆方式WHERE是“先把符合条件的行筛出来再分组”HAVING是“分组之后再筛选哪些组需要被保留”。例如只要“总金额大于1000的用户”。SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id HAVING total_amount 1000;如果你顺手写成了WHERE total_amount 1000数据库会直接报错因为WHERE发生在聚合计算之前那时候还没有total_amount这个别名呢。如果你的条件是“只看2024年的订单”那就得先WHERE order_time ...再GROUP BY再HAVING顺序很有讲究。4.3 JOIN 连接让表与表发生关系单表玩得再溜也只算入门了一半。现实业务里数据都是分开存的用户信息放一张表订单放一张表商品放一张表。要回答“订单里每个商品对应的用户是谁”就得把表连接起来。JOIN的三种最常见类型INNER JOIN只保留两表匹配上的行。LEFT JOIN左表全部保留右表没有匹配就补空值。RIGHT JOIN右表全部保留左表没有匹配就补空值。实际工作中我几乎只用的就是LEFT JOIN因为它最符合“以左表为主补充右表信息”的直觉。比如把订单表和用户表连接SELECT o.order_id, u.name FROM orders AS o LEFT JOIN users AS u ON o.user_id u.user_id;这里AS给表起了别名o和u后面引用列就简短了很多。连接条件用ON指定一般是两个表的共同字段。注意不要漏写ON漏了或者写错条件很可能产生笛卡尔积——那就是每行订单和每个用户都配对一遍数据会爆炸式膨胀这绝对是新手最容易犯的错误之一。4.4 子查询的两种写法子查询就是“查询套查询”。最简单的用法是在WHERE里放一个子查询比如“找出消费总额最高的用户是谁”SELECT user_id FROM orders GROUP BY user_id HAVING SUM(amount) ( SELECT MAX(total_amount) FROM (SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id) AS t );这一长串里内层子查询先算每个用户的总金额外面求最大值再拿这个最大值去匹配用户ID。写法没问题但可读性确实有点差。另一种更清爽的方式是用WITH公共表表达式CTE也叫“临时查询块”WITH user_totals AS ( SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id ) SELECT user_id FROM user_totals WHERE total_amount (SELECT MAX(total_amount) FROM user_totals);WITH的好处是让复杂逻辑一层一层拆开像搭积木一样先算出中间结果再在上面继续查。我强烈建议新手从入门起就养成用WITH写复杂查询的习惯后来的维护成本会低很多。5. 窗口函数SQL进阶的一道坎5.1 什么是窗口函数有一类需求用GROUP BY会丢掉明细比如“每个用户最近的订单时间”“同组内排名”“计算累计值”。这时候就要用窗口函数。窗口函数也是“按组计算”但它不会把多行合并成一行而是每一行还保留着同时多出一列计算结果。你可把它理解成“一边看明细一边看这一行在它所在组内的位置”。基础的窗口函数写法是SELECT user_id, order_time, amount, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn FROM orders;PARTITION BY相当于分组但不合并行ORDER BY控制组内排序ROW_NUMBER()给组内每一行编一个序号。这个查询的结果里每个用户订单会按时间倒序编号1就是最近的一单。5.2 三类最常用的窗口函数场景排名类ROW_NUMBER()连续且唯一遇到值相同也会强制分出先后RANK()值相同时会并列且跳过编号DENSE_RANK()并列但不跳号。比如“各用户订单金额排名”用这三个结果会有微妙区别。聚合类SUM() OVER (PARTITION BY ...)可以实现“累计到当前行的总额”。比如算每月累计营收你可以在窗口里写ORDER BY month让求和范围随行数递增。偏移类LAG()取上一行LEAD()取下一行。比如算订单环比增长LAG(amount)能拿到前一条订单的金额再跟自己这一行做差或者做比。窗口函数在“SQL面试题”和“SQL窗口函数”热搜里常年霸榜因为它是从基础查询迈向分析型查询的分水岭。我的学习建议是先把GROUP BY和JOIN练熟再回头啃窗口函数效果会比一上来就死磕好很多。6. 慢SQL优化入门从会写到写得好6.1 慢SQL是怎么被发现的数据量大起来之后你写的SQL可能从几毫秒变成几秒甚至几十秒这就是“慢SQL”。怎么发现慢SQLMySQL可以用慢查询日志或者直接看EXPLAIN命令。EXPLAIN是数据库给你的执行计划它会告诉你这句SQL是怎么执行的——有没有走索引扫描了多少行有没有全表扫描。新手看EXPLAIN结果只需重点关注几个点type如果是ALL说明扫了整张表很可能是没建索引或者条件写得不合适rows是预计扫描行数越少越好Extra里如果出现Using filesort说明排序没走索引可能要额外优化。这些知识不求一次全懂但至少你的心里要知道慢SQL是有迹可循的不是玄学。6.2 常见的优化手法给高频查询的WHERE字段和ORDER BY字段建索引。比如经常按user_id查订单就给orders(user_id)加索引。避免在条件列上使用函数或计算。比如WHERE DATE(order_time) 2024-01-01这个写法会让索引失效更好的写法是WHERE order_time 2024-01-01 AND order_time 2024-01-02。别滥用SELECT *。字段太多会让回表加重网络传输也慢。分页深时用游标或者限定上一次的最大值来替代大偏移量LIMIT 100000, 10。连表时用小表驱动大表连接字段类型必须一致否则索引也帮不上忙。能在一个查询里算完就别拆成多个查询在程序里循环拼接——虽然看着逻辑清晰但性能和资源消耗都是灾难级的。“并行SQL优化”这个词在热搜里也有但那是大数据或分析引擎里更高级的话题入门阶段先把执行计划和索引用好已经能解决90%的问题了。7. 新手避坑指南和常见问题实录7.1 环境与工具选型想练SQL第一步得有环境。我的建议是别一上来就去折腾安装配置重型数据库服务器你可以先装一个开源的 MySQL 或者 PostgreSQL或者直接用在线练习平台。你自己电脑上装一个 MySQL 8.0 就够用了安装教程一搜一大把但千万注意安装时记住版别编码配置尤其按字符集要选utf8mb4不要默认latin1否则后面存中文就会乱码。图形界面上我推荐用 DBeaver开源免费支持 MySQL、PostgreSQL、SQL Server 等多种数据库免去到处找破解版的麻烦。我见过很多人花大量时间在“navicat 激活码”上有这个功夫用 DBeaver 早都写完几十条查询了。7.2 让人崩溃的NULL和三值逻辑新手遇到最神秘的问题之一就是NULL。NULL不是“等于空字符串”也不是“等于0”它代表“未知值”。数据库里判断一个值是否为NULL不能用 NULL而要用IS NULL或IS NOT NULL。更坑的是三值逻辑NULL和任何值比较的结果既不是真也不是假而是“未知”。比如WHERE amount 100 OR amount 100这个条件看起来覆盖了所有情况但如果amount是NULL这两个条件都是“未知”这行就会被过滤掉。所以在写含NULL字段的条件时要明确想清楚你到底想不想包含空值。想包含就额外加一句OR amount IS NULL。7.3 中文乱码、字符集问题“mysql执行sql脚本”时中文变成问号十个有八个是字符集问题。统一用utf8mb4能解决绝大多数乱码问题。创建数据库时明确指定CREATE DATABASE mydb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;另外从命令行导入SQL脚本时建议在连接参数里也指定字符集比如mysql --default-character-setutf8mb4 -u root -p。这样脚本里如果有中文就不会导入一半变成乱码。7.4 SQL注入安全入门永远不要拼接用户输入说到“SQL注入”很多不了解的人以为这是某个高端黑客技术。说白了SQL注入就是当你在程序里把用户输入的内容直接拼接到SQL字符串里用户可以通过输入特殊字符改变你原来SQL的逻辑从而获取不该看到的数据甚至破坏数据。举个反面例子比如登录查询这样写SELECT * FROM users WHERE user_name 用户输入的字符串 AND password 用户输入的密码;如果用户把用户名输入成 OR 11那么整个语句很可能变成WHERE user_name OR 11密码条件被绕过了。这就是“万能密码绕过”这类词背后的原理。真正的入门安全意识只有一条在程序里访问数据库永远不要用字符串拼接的方式来组装SQL而是用参数化查询。在 Python 的mysql-connector里用%s占位符传参在 Java 的 MyBatis 里用#{}绑定参数。参数化查询会在数据库驱动层把输入当纯数据不会改变SQL结构能直接堵死这类注入。我还想额外提醒一句有些人在学习时喜欢看“CTF”比赛里的SQL注入题以此刷成就感。但如果只是入门我建议你把重点放在“如何正确使用参数化”上而不是研究各种绕过技巧。后者的水很深且容易跑偏对正常工作没有一点正向帮助。8. 写在最后我给新手的几条实操建议最后分享一点真切的个人体会。SQL入门最忌讳“只看不练”语法看十遍不如敲三遍。我当年入门时把网上能找到的练习题都敲了一遍前几十条写得磕磕绊绊但敲到一百条的时候很多日常需求已经能条件反射式地写出大概框架了。你可以自己准备一个练习数据集哪怕就是手工造几十行订单记录反复练习“每个用户最近一单”“各部门月累计营收”“订单金额排名前3的用户”这类问题。遇到不会的先别急着抄答案试着拆解成“先过滤哪部分、再匹配哪张表、最后怎么聚合”这个拆解能力比背诵语法重要得多。另外我建议你从第一天起就养成给表起别名、给查询加注释、复杂逻辑用WITH拆分的习惯。这些不是形式主义等你一个月后回看自己写过的SQL就知道当初的整洁习惯能省下多少回忆时间。在调试时如果遇到报错学会看报错位置和错误码。MySQL 报错信息往往直接告诉你是在语法附近出错还是在数据约束上出错。别慌把出错的句子单独抽出来缩小查询范围比如先跑一个SELECT * FROM 表 LIMIT 10确认数据和表没问题再逐步加条件、加连接。这种“二分排查法”我用了十几年屡试不爽。SQL字形简单但思维深度很深。入门只是拿到了一把钥匙后面还有窗口函数、执行计划、复杂业务建模等你慢慢玩。也别急着一步到位先把“查得出来、结果正确、别人看得懂”这三点做到再考虑“查得快”和“写得妙”。这个节奏是我见过最不容易半途而废的路径。
返回列表