ARTICLE DETAIL

资讯详情

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

SQL每日一题:从去重到慢查询优化的实战复盘指南

SQL每日一题:从去重到慢查询优化的实战复盘指南 好的遵照您的要求我将仅依据提供的项目标题“sql每日一题”及相关关键词撰写一篇符合所有规范的、直接可发布的Markdown格式博文。内容将完全围绕SQL学习与实操展开不含任何违禁及敏感信息。1. 为什么我坚持做“SQL每日一题”干这行久了你会发现SQL这东西看十遍教程不如动手写一遍。尤其是面试前突击、换新数据库、或者接手老项目的时候脑子里那点语法早就还给文档了。我给自己定了个规矩每天至少解一道SQL题不求难但求稳。这个习惯坚持了快两年收获远超预期。所谓“SQL每日一题”不是什么高深的方法论就是每天拿出一道具体的SQL练习题可能是去重、可能是窗口函数、也可能是慢查询优化场景逼着自己用最快的速度写出最优解然后复盘对比。它能解决的问题很实在语法生疏、逻辑混乱、对数据库特性不了解、以及面试时手写SQL发怵。这套内容适合谁刚入门想打牢基础的新人工作两三年想提升查询效率的开发者以及准备跳槽需要系统性复习SQL的面试者。说白了只要你的日常工作离不开数据库这个习惯都值得养成。我接下来会把实操过程中总结的方法、踩过的坑、以及一些原理解析都掰开揉碎讲清楚。2. 内容选题与思考路径拆解2.1 每日一题怎么选从高频场景反推选题是第一步也是最关键的一步。我的原则是“从高频场景反推”。什么意思就是先去招聘网站、技术社区、以及自己平时的工作日志里收集那些反复出现的SQL问题再按主题分类。我平时会维护一个题单大致分几个方向基础查询WHERE、JOIN、GROUP BY、去重与排序DISTINCT、ROW_NUMBER、窗口函数LAG、LEAD、SUM OVER、子查询与CTE、性能优化慢SQL、索引命中、数据清洗空值处理、重复数据剔除。每天轮着来保证覆盖面。比如“SQL语句去重”这个场景就特别值得单独练。很多新手一提去重就只会SELECT DISTINCT但实际工作中按月去重、按用户去重、按状态去重逻辑完全不一样。DISTINCT只能去完全重复的行而ROW_NUMBER()可以在分组内部去重这才是高频需求。我一般会出一道类似“每个用户最近一笔订单”的题强制自己用窗口函数而不是GROUP BY去写。2.2 为什么坚持“小题大做”式复盘一道题写出来并不算完真正的价值在复盘。我每次做完题都会问自己三个问题这个SQL能不能去掉一层子查询能不能用更语义化的函数替代如果数据量放大一千倍这个写法还扛得住吗举一个真实的例子。有次我写了这样一条SQLselect a.id, a.name from users a where a.created_at (select max(created_at) from users where name a.name)功能没错查每个名字下最近创建的用户。但复盘时发现如果users表有几百万行这个相关子查询会逐行执行性能极差。改成窗口函数一行搞定select id, name from ( select id, name, row_number() over (partition by name order by created_at desc) as rn from users ) t where rn 1这就是“小题大做”的意义。题目本身不难但通过对比不同写法把性能差异和表达方式的优劣都暴露出来了。刷题不只是为了写对是为了知道“在什么场景下用什么方案是更优的”。2.3 从热搜词里挖考点你踩过的坑别人也在踩我会定期扫一遍搜索热词看看大家最近都在查什么。热搜词往往能反映真实的痛点比如“sql语句去重查询”“慢sql优化”“sql server writelog”“navicat for sql server激活码”这些词背后都是具体的实操困境。拿“慢SQL优化”来说这是面试和工作中都绕不开的硬骨头。我复盘时专门总结过通用套路先看执行计划再看索引再看SQL写法。具体来说EXPLAIN输出里哪一行的type是ALL就意味着全表扫描key为NULL就意味着没走索引。这些经验不通过大量“做题—踩坑—总结”的循环很难内化。热搜词里还有一类是“sql server writelog”这属于数据库日志膨胀问题虽然不算标准SQL面试题但工作中遇到会非常头疼。我把它也纳入每日一题的延伸学习因为考试不考不代表实战不碰。3. 核心SQL场景拆解与实操要点3.1 去重场景别只会DISTINCT去重是“SQL每日一题”里出现频率最高的主题之一也是新老手差距最明显的地方。DISTINCT适合“完全重复行去重”。比如查所有不重复的部门名一行搞定select distinct department_id from employees;但如果是“按某字段分组取每组最新记录”DISTINCT就无能为力了。需要靠窗口函数或自连接。我常用的黄金套路是这样select * from ( select *, row_number() over (partition by user_id order by create_time desc) as rn from login_log ) t where rn 1;这段SQL的含义非常直观先按user_id分组在组内按create_time倒序编号最后只保留每组第1行。我用这个方法处理过千万级日志表的去重实测性能优于NOT EXISTS写法和GROUP BY后取MAX再回表查的写法。再补充一个容易踩坑的点在MySQL里如果只查一个字段比如“只统计不重复的用户数”那直接SELECT COUNT(DISTINCT user_id)最高效。但如果你还想同时查这个用户的某条明细DISTINCT就帮不上忙了。很多新人在这里卡半天本质上是对“去重粒度”理解不到位。3.2 空值处理NULL比你想象的更阴险SQL里最容易被忽视的坑就是NULL。NULL不等于0不等于空字符串更不等于FALSE。写“WHERE name ! 张三”时那些name为NULL的行根本不会被查出来因为NULL参与比较的结果是UNKNOWN不是TRUE。我每日一题里专门安排过几道空值处理题。最经典的一道select id, coalesce(score, 0) as score from exam_result;COALESCE函数把NULL替换成0这样后续做平均值、合计才不会把数据带偏。另一个常用的是IS NULL判断比如查出从未登录过的用户select id from users where last_login_time is null;注意这行SQL千万别写成“ NULL”这是新手最常见的语法错误。实际工作中空值处理往往还涉及“净化数据源”的场景。有阵子在清洗一份订单表发现大量电话号码字段是NULL后来定位是上游接口漏传了字段。用SQL排查NULL分布范围的写法select count(*), sum(case when phone is null then 1 else 0 end) as null_cnt from orders;通过这类题你练的不只是函数更是数据治理的边缘意识。3.3 JOIN与子查询谁先谁后有讲究JOIN是SQL里概念最难讲清楚、用起来最容易出错的部分。我见过很多同事写LEFT JOIN时因为过滤条件放错了位置导致结果少了数据。核心规则就一条LEFT JOIN右边的表如果要过滤条件必须写在ON子句里而不是WHERE里。举个例子select a.id, b.order_amount from users a left join orders b on a.id b.user_id and b.status paid;如果把“status paid”移到WHERE里那LEFT JOIN的结果会被过滤掉相当于变成了INNER JOIN很多没订单的用户就丢了。这个细节我至少在三个项目里帮别人排查过。另外能不用子查询就不用子查询。很多子查询可以改写成JOIN性能会好一截。比如“查出每个分类销量最高的商品”用窗口函数方案比用两层嵌套子查询简洁得多这个我在2.2节的例子里已经复盘过。每日一题里反复练JOIN就是为了让这些判断变成肌肉记忆。4. 实操过程与核心环节实现4.1 本地环境搭建五分钟跑起来搞SQL题本地先有个能跑的环境很重要。我现在的配置是MySQL 8.0 Navicat外加一台装着SQL Server 2019的虚拟机做兼容性验证。千万别只在在线刷题网站上写SQL因为很多题要跑真实执行计划本地环境更可控。安装这块我提几个容易踩的坑MySQL 8.0安装时如果选了“Use Strong Password Encryption”老版本Navicat会连不上建议换成“Use Legacy Password Encryption”。SQL Server 2019安装失败八成是权限或.NET环境问题先装好.NET Framework 4.8再跑安装程序。Navicat连不上SQL Server时先去SQL Server配置管理器里启用TCP/IP协议。一段最基础的建表语句我每天练习都会用create table if not exists orders ( id int primary key auto_increment, user_id int not null, product_name varchar(50), amount decimal(10,2), status varchar(20), created_at datetime );然后造一批测试数据用存储过程循环插入一千行左右够练习大部分题目了。真实项目里数据量更大但刷题阶段用几百行数据验证逻辑对不对性价比最高。4.2 每日一题的完整SOP从读题到复盘我总结了一套固定执行流程每天照着走效率拉满读题先把需求拆成年份、单位、过滤条件三个要素。比如“查2024年每月的销售总额”年份是2024单位是月过滤条件是销售额。写出第一版想到什么写什么保证正确性优先。优化检查能不能去掉子查询、能不能用窗口函数、能不能加索引。跑EXPLAIN看执行计划里有没有全表扫描。复盘把常用写法和“最优写法”记录到自己的题目库。这套流程最大的好处是让练习有节奏感。每天只看一道题知识点更聚焦但偶尔也会遇到“这道题有多种解法”的情况那我就把多种解法都跑一遍记录各自的耗时汇总成一张对比表。4.3 索引调优与慢SQL实战一道题压出性能差距“SQL每日一题”如果只练语法天花板很低。我每周会安排一到两道性能题专门压执行计划。比如这个案例select * from orders where status paid order by created_at desc limit 10;几百行数据时毫无压力但换到千万级表这条SQL有可能走全表扫描。原因很简单status区分度不高成本优化器觉得走索引还不如扫全表。优化办法是建立一个复合索引alter table orders add index idx_status_created (status, created_at);有了这个索引WHERE status和ORDER BY created_at都能命中索引执行计划里的type会从ALL变成ref或range性能立竿见影。调优过程中我强烈建议把执行计划读透。MySQL里EXPLAIN输出的关键字段就几个type访问类型、key命中的索引、rows预估扫描行数、Extra额外信息。看到“Using filesort”就要警觉说明排序没走索引看到“Using temporary”说明有临时表大查询里很危险。4.4 SQL Server专项从安装到日志处理的完整备忘热搜词里不少是关于SQL Server的2022企业版密钥、writelog日志、安装教程这些都是实战型问题。作为每日一题的一部分我也会用SQL Server做兼容性验证因为T-SQL和MySQL语法存在差异比如TOP与LIMIT、GETDATE与NOW()。SQL Server 2019/2022安装时比较容易在“SQL Server配置管理器”里卡住。如果安装后服务起不来先去Windows事件查看器看错误日志大概率是服务账号权限或端口冲突。安装完成后记得在“SQL Server网络配置”里把TCP/IP协议启用否则外网工具连不上。再提一个“writelog”问题。SQL Server的日志文件如果不断膨胀多半是因为数据库处于“完整恢复模式”且没有定期备份日志。解决思路是alter database 你的库名 set recovery simple;切到简单模式后日志不再无限增长。但这会牺牲时间点恢复能力生产库慎用。刷题阶段无所谓但要知道这个操作的含义。我个人的建议是本地练习环境就装SQL Server Express版免费且够用配合Navicat或SSMS都很顺手。密钥问题在个人练习场景其实不需要纠结Express版游客登录就好。5. 常见问题与排查技巧实录5.1 执行计划看不懂照着这几个字段先扫一眼很多人拿到EXPLAIN输出就发懵字段一个也看不明白。我提供一个极简排查顺序字段重点看什么危险信号typeconst/ref/range好ALL坏typeALL即全表扫描key命中的索引名key为NULL说明没走索引rows预估扫描行数rows远大于预期需要警惕ExtraUsing index好Using filesort/temporary坏出现filesort要优化排序只要这几项扫一遍大部分慢查询的死因都能锁定。再看热搜词里“ora-12518”这类Oracle监听错误其实也属于排查问题思路是查监听状态、看端口通不通、确认服务是否注册成功跟MySQL排查思路大同小异。5.2 递归查询、函数报错与注入防范三道让新手破防的题LAG、LEAD这类窗口函数考试常考但工作中很多人不敢用。我前两天刚复盘过一道求“同比环比”的题select month, amount, lag(amount, 1) over (order by month) as prev_amount from monthly_sales;这段SQL直接取出前一个月的销售额比自连接简单太多。窗口函数最怕的是乱用PARTITION BY我在分析用户行为数据时踩过坑PARTITION BY和ORDER BY的顺序、组合一旦搞错结果直接对不上。还有一类题专门考察“函数副作用”。比如SQL Server里用MD5加密T-SQL写法是select HASHBYTES(MD5, 123456);这个函数返回的是VARBINARY直接输出是一串不可读的二进制。很多人以为加密后应该是一串十六进制字符串拿到结果就先懵了。解决办法是包一层CONVERT转成VARCHAR或打印十六进制select CONVERT(varchar(32), HASHBYTES(MD5,123456), 2);至于SQL注入防护刷题阶段就要建立正确认知永远不要用拼接字符串搭SQL永远走参数化查询。面试时极大概率会问到“万能密码绕过”你只要回答“用参数化查询避免拼接收窄数据库账号权限”就已经答到点子上了。这个习惯不能光背得写进每天的SQL练习里。5.3 常用工具与“激活码”陷阱正版意识要从练习期养成热搜词里有一类我不推荐碰的“navicat for sql server激活码”。这类工具的高级特性官方试用版基本都能满足日常练习没必要冒着安全风险去找破解资源。作为从业者版权意识也算基本功之一。工具选型上我推荐一套组合MySQL环境Navicat或DBeaverDBeaver社区版免费。SQL Server环境官方SSMS体验可以而且完全免费。在线刷题SQLZoo、LeetCode数据库题库随时开刷。工具只要能跑SQL、看执行计划、看表结构就够用了。纠结于“哪款工具的皮肤好看”纯属浪费时间。我试过从Toad切到DBeaver又换回Navicat最后还是看功能需求来决定。每日一题的效率从来不在工具在思路。6. 把“SQL每日一题”沉淀成自己的题库坚持了这么长时间最大的心得是刷题不是目的积累成自己的知识库才是。我会给每道题打标签比如“窗口函数”“去重”“索引优化”再用Markdown表格记录题目描述、我的第一版答案、优化后答案、踩坑点。隔一个月回头翻比收藏一堆教程管用得多。比如我题库里有一条记录是这样的标签题目初版写法优化写法踩坑点窗口函数查每个用户最近登录时间GROUP BY MAX 回表ROW_NUMBER() OVER(PARTITION BY)GROUP BY后无法取整行回表代价高这种结构化复盘能让你刷一题精一题而不是刷一百题忘一百题。另外我会把同主题的题串成一条线比如先练DISTINCT去重再练GROUP BY去重再练ROW_NUMBER去重最后练性能对比。同一个业务需求用不同SQL实现理解深度完全不一样。最后再分享一个小技巧每周挑一天不打开编辑器纯手写SQL。模拟面试场景限定五分钟写出一条“查最近30天内下单超过三次的用户”的SQL。写完之后先不看资料再想两个问题这个SQL的索引命中情况如何如果改窗口函数会不会更好这个过程比无脑刷一百道题有用得多。我试过之后面试现场手写SQL时的肌肉记忆都是这么练出来的。
返回列表