写这份SQL学习笔记,其实是我把这两年踩过的坑、看过的文档、帮同事擦过的屁股重新过了一遍脑子。搜索热词里全是"sql server 2008 r2下载""sql server 2019安装教程""sql优化""sql注入万能密码绕过"这类关键词,说明大家最关心的三件事从来就没变过:怎么把环境跑起来、怎么写得不慢、怎么别被人黑。这篇笔记不按教材目录走,我按自己实际学习SQL的路径来组织——先过环境关,再过语法关,然后是优化和安全这两道坎,最后补充工具链和面试常见题型。不管你正在准备面试,还是工作中被慢查询折磨得头疼,这篇笔记应该都能让你少走点弯路。
1. 学SQL之前,先把环境这关过了
很多人学SQL死在第一步,不是语法难,是环境装不上。我见过太多人花一整天装SQL Server,最后卡在报错上,连数据库长什么样都没看到就放弃了。这一节把我在Windows环境下的安装和初始化经验完整复述一遍,包括那些官方文档不会告诉你的坑。
1.1 SQL Server 还是 MySQL:选型不能只看"哪个火"
先定个调:我的笔记主线用SQL Server,但同时会给出MySQL的对应写法。因为热词里"sql server 2008 r2下载"和"mysql执行sql文件"同时出现,说明很多人其实根本不清楚两家的区别。
选型时看三个维度就够了:
| 维度 | SQL Server | MySQL |
|---|---|---|
| 部署难度 | Windows下安装包大,配置项多 | Linux/Windows都轻量,配置相对简单 |
| 语法风格 | T-SQL,窗口函数、CTE非常成熟 | 8.0后窗口函数补齐,但部分语法仍有限制 |
| 典型场景 | 企业级ERP、金融系统、学校教学 | Web应用、互联网业务、开源技术栈 |
我的建议是:如果你是做Java后端,先学MySQL;如果你在传统企业或学校环境,SQL Server跑不掉;两条腿走路最好——两边的SQL语法有九成相似,真正需要记的差异点后面我会逐个标注。
1.2 安装过程中最容易翻车的三个坑
我按SQL Server 2019在Windows 10/11上的安装来复盘。先强调一遍最重要的事:安装前务必关闭杀毒软件,关闭Windows防火墙对SQL端口的拦截。这不是玄学,SQL Server安装过程中要注册一堆服务,杀毒软件误拦导致的失败率极高。
第一个坑是"机器学习服务器组件"下载失败。热词里专门有"sql server 2019机器学习服务器组件下载",说明中招的人不少。其实这个组件是给R语言和Python集成的,纯学SQL、做常规OLTP业务根本用不到。如果卡在这一步,直接取消勾选就行,对后续使用毫无影响。
第二个坑是实例配置时忘了记住实例名,导致后面连接时不知道服务器地址。默认实例连接写localhost就行,命名实例要写成localhost\实例名。我建议初学者安装时直接选"默认实例",省掉所有麻烦。
第三个坑是身份验证模式。安装时默认是"Windows身份验证模式",但很多教程的SQL语句例子都是sa账号登录,两者对不上就懵了。我的建议是选"混合模式",自己设一个sa密码,同时勾选当前Windows账号为管理员——这样你既可以用Windows账号免密连接,以后要用sa也能用。
1.3 初始化连接:连接不上时先查这几处
环境装完,你大概率会碰到"连接不上"的报错。我排查的顺序永远是下面这张清单:
- 检查SQL Server服务是否正在运行。按下
Win+R输入services.msc,找到SQL Server (MSSQLSERVER),确认状态是"正在运行"。没运行就右键启动。 - 检查TCP/IP协议是否启用。打开
Sql Server Configuration Manager,找到"SQL Server网络配置" -> "MSSQLSERVER的协议",把TCP/IP启用。默认情况下TCP/IP可能是禁用的,这会导致所有远程连接失败。 - 检查防火墙入站规则。SQL Server默认端口是1433,你可以在防火墙高级设置里新建入站规则放行1433端口。
- 如果用了
localhost连不上,试试127.0.0.1或.。有时候本机解析出问题,换IP反而能通。
这套排查流程我至少帮人处理过十几次,九成都是第二、第三步没做对。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 查询基本功:从单表 SELECT 开始建立肌肉记忆
环境通了以后,SQL学习就算是正式入门了。这一章不讲那种看一眼文档就会的SELECT * FROM 表,我专门挑热搜里出现频率最高、同时也是问得最多的几个基础语法点,逐个讲透。
2.1 去重查询的三种写法与性能差异
"sql语句去重查询"这个关键词简直是初学者提问区的常青树。去重最常见的需求是:查出一张表里所有出现过的"用户ID",或者统计一个订单表里哪些商品被买过,而且只看一次。
三种主流写法:
sql复制-- 写法1:DISTINCT,最简单
SELECT DISTINCT user_id FROM orders;
-- 写法2:GROUP BY,最通用
SELECT user_id FROM orders GROUP BY user_id;
-- 写法3:窗口函数,适合要带出其他字段的场景
SELECT user_id FROM (
SELECT user_id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time) AS rn
FROM orders
) t WHERE t.rn = 1;
实际使用中,我最推荐的是GROUP BY。原因有二:第一,GROUP BY在查询优化器里产生的执行计划通常比DISTINCT更可控;第二,后面加聚合函数(COUNT、SUM)时你不可能跳过它。DISTINCT只适合那种"查一下有哪些值"的临时探索。
而写法3是另外一个维度的问题:当你想去重后还要保留某条记录的完整字段(比如每个用户最早的那条订单),DISTINCT和GROUP BY都做不到,必须是窗口函数。这也是面试里"每个分组取第一条记录"的标准答案。
2.2 BETWEEN AND 的边界陷阱
"sql between and的用法总结"能上热词,说明这个看似简单的语法,坑起来真不是人。
BETWEEN AND在SQL Server里是包含边界值的,也就是说下面两条语句结果一致:
sql复制SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
SELECT * FROM orders
WHERE order_date >= '2024-01-01' AND order_date <= '2024-01-31';
看清楚,日期是<=,也就是2024-01-31 00:00:00之后但不超过2024-01-31 23:59:59的记录都会被包含。但如果你存储的是带时间部分的DATETIME类型,比如2024-01-31 10:30:00,它确实会被查出来,因为10:30:00小于23:59:59。
真正容易出问题的是你只精确到天的字符串比较。如果order_date是VARCHAR存储的'2024-01-31',那一切正常;如果存的是'2024-1-5'这种不补零的格式,字符串比较就会错乱,'2024-1-31'会排在'2024-2-1'后面。
所以我的习惯是:日期范围查询一律不用BETWEEN,老老实实写>= 和 <,而且右边界用"下一天的零点":
sql复制SELECT * FROM orders
WHERE order_date >= '2024-01-01'
AND order_date < '2024-02-01';
这个写法把所有1月31日的记录全包进来,而且不会出现"2月1日 00:00:00被误伤"的边界问题。
2.3 空值处理:NULL 不是空字符串
"sql去除空值"这个热词背后,有两种完全不同的需求:一种是只查非NULL的记录,另一种是把数据里的空字符串抹掉。两者别混。
先说NULL。NULL表示"未知",它不参与任何比较运算。WHERE name = NULL永远查不到数据,你必须用IS NULL或者IS NOT NULL:
sql复制-- 正确:查所有没有填邮箱的用户
SELECT * FROM users WHERE email IS NULL;
-- 错误示范:什么都查不到
SELECT * FROM users WHERE email = NULL;
然后说空字符串。有些应用层会把"未填"存成空字符串'',这跟NULL是两回事。如果要把空字符串当成空值一起过滤,得写成:
sql复制SELECT * FROM users
WHERE email IS NOT NULL
AND email <> '';
要注意<> ''也会把NULL排除掉,所以如果表里两种脏数据都有,就必须两个条件一起写。还有一种更省心的方法是使用COALESCE把NULL统一替换成默认值再做判断:
sql复制SELECT * FROM users
WHERE COALESCE(email, '') <> '';
2.4 输出序号:ROW_NUMBER 与变量自增的取舍
热词里的"sql 输出序号",我理解是给查询结果加上行号。比如分页显示,或者导出Excel时需要带序号列。
SQL Server里两种常见做法:
sql复制-- 方法1:ROW_NUMBER窗口函数(推荐)
SELECT ROW_NUMBER() OVER (ORDER BY order_time DESC) AS seq, *
FROM orders;
-- 方法2:自增变量(旧版本没有窗口函数时的写法)
SELECT @row := @row + 1 AS seq, t.*
FROM orders t, (SELECT @row := 0) r
ORDER BY t.order_time DESC;
强烈建议用方法1。窗口函数是标准SQL能力,不仅限于SQL Server和MySQL 8.0+,而且执行计划的可读性远好于变量自增。方法2虽然能在MySQL 5.7这种老版本上跑,但变量的赋值顺序在复杂SQL里非常容易出错,比如你ORDER BY和SELECT的执行顺序没搞对,序号就会乱跳。
3. 进阶语法:子查询、CTE 与"判断存在"的正确姿势
单表查询跑通后,必然要面对多表关联和复杂逻辑。这一章把三个高频问题一起聊:WITH AS怎么用、EXISTS/IN/LEFT JOIN怎么选、动态SQL怎么配合MyBatis。
3.1 WITH AS 的两种典型用法
"sql with as用法"能进热词,说明这玩意儿新手基本靠查。我理解WITH AS(也叫CTE,Common Table Expression)的价值就俩字:清晰。
典型场景一:把一个字表里最常用的过滤结果抽出来,后面反复引用。
sql复制WITH active_users AS (
SELECT user_id FROM users WHERE status = 'active' AND last_login >= '2024-01-01'
)
SELECT o.order_id, o.amount
FROM orders o
JOIN active_users a ON o.user_id = a.user_id
WHERE o.amount > 100;
这个SQL一眼就能看懂:先圈定活跃用户,再查这些用户的订单。如果没有CTE,你得在JOIN里写一坨子查询,可读性直接下降。
典型场景二:递归查询,比如组织架构树。
sql复制WITH employee_tree AS (
SELECT emp_id, emp_name, manager_id, 1 AS lvl
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.emp_id, e.emp_name, e.manager_id, et.lvl + 1
FROM employees e
JOIN employee_tree et ON e.manager_id = et.emp_id
)
SELECT * FROM employee_tree;
递归CTE是面试高频题,也是实际开发里查菜单树、部门树的标准答案。
3.2 EXISTS、IN 与 LEFT JOIN 的判断存在之争
热词里有"sql 中判断存在的写法 exists in 和left join",这个问题我在工作里被问过不下十几次。先说结论:
- 判断"子表里是否存在对应记录",用
EXISTS最直接; - 关联字段在子表里唯一,且需要直接对比值,用
IN没问题; - 要同时取主表和子表的字段,用
LEFT JOIN加IS NULL/IS NOT NULL。
具体拆开看。需求:"查所有下过订单的用户"。
sql复制-- EXISTS 写法
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);
-- IN 写法
SELECT * FROM users u
WHERE u.user_id IN (SELECT o.user_id FROM orders o);
在执行计划层面,EXISTS在子表有索引的情况下,常常会走"半连接"(semi join),比IN全量扫描子查询结果再匹配要快。虽然现代优化器很多时候会自动改写,但在大表场景下,EXISTS依然是我的首选。
反过来,"查没有下过订单的用户":
sql复制-- EXISTS 的否定形式
SELECT * FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);
-- 用 LEFT JOIN 更直观
SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.user_id
WHERE o.user_id IS NULL;
两种写法结果相同,但NOT EXISTS在多数情况下性能更稳,尤其是orders.user_id上有索引时。LEFT JOIN写法适合同时需要o表字段作进一步计算的场景,如果只是单纯判断存在与否,不建议用它。
3.3 动态 SQL 与 MyBatis 的配合
"flowable 能写sql吗""mybatis动态sql"这些热词,说明大家学SQL的时候已经不满足于"手写一条查询",而是想在框架里灵活拼SQL。
MyBatis里动态SQL核心是<if>、<where>、<foreach>这几招。常规范式:
xml复制<select id="searchOrders" resultType="Order">
SELECT * FROM orders
<where>
<if test="userId != null">
AND user_id = #{userId}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="orderTimeBegin != null">
AND order_time >= #{orderTimeBegin}
</if>
</where>
ORDER BY order_time DESC
</select>
注意两个细节,都是实际项目里最容易被喷的:一是所有条件都要用AND开头,配合<where>标签会自动去掉多余的AND;二是参数一律用#{xxx}而不是${xxx}——前者编译成占位符参数,后者直接拼SQL,会有注入风险,这个后面第五章细讲。
Flowable那种工作流引擎能不能写SQL,答案是能,通过CustomSqlMapper扩展,但我不建议为了图省事在流程引擎里堆复杂SQL,维护起来真的很痛苦。
4. 慢SQL优化:从8秒到0.3秒的完整追踪
"sql优化""慢sql优化""并行sql优化"这三个热词撑起了整个优化的讨论。这一章我用一个真实优化案例来讲透优化思路,这也是我个人觉得整篇笔记最有价值的部分。
4.1 先看执行计划,别猜
有一张订单流水表order_flow,数据量大概5000万行,业务要查某个客户最近30天的订单明细。一开始同事写的SQL是这样:
sql复制SELECT order_id, customer_id, order_time, amount
FROM order_flow
WHERE customer_id = 'C20240001'
AND order_time >= '2024-11-01'
AND order_time < '2024-12-01'
ORDER BY order_time DESC;
跑一次要8秒。同事第一反应是加索引,直接对customer_id加了普通索引,结果毫无改善。我让他把执行计划打出来,发现优化器选择了全表扫描。
加索引无效的原因是:只对customer_id建索引,优化器预期"符合customer_id的记录数"仍然太大,干脆不回表了。正确做法是建联合索引:
sql复制CREATE INDEX idx_customer_time ON order_flow (customer_id, order_time);
这次执行计划走了索引范围扫描,查询从8秒降到了0.3秒。区别就在于:单列索引解决不了"等值+范围"组合条件,联合索引的列顺序要遵循"等值列在前,范围列在后"。
4.2 索引失效的常见原因
很多同学建了索引但没效果,基本都是踩了下面几个坑:
第一,对索引列做了函数运算。比如WHERE YEAR(order_time) = 2024,索引直接失效,因为数据库要先算完函数才能比较。正确写法是WHERE order_time >= '2024-01-01' AND order_time < '2025-01-01'。
第二,隐式类型转换。WHERE customer_id = 123,如果customer_id是VARCHAR,SQL Server可能做隐式转换,导致索引失效。要保证类型一致。
第三,左模糊查询。LIKE '%abc%'没法走B+树索引,因为字符串的索引顺序是从左往右的。左模糊就只能全扫描,这是物理限制。
第四,OR连接的条件里只要有一个字段没索引,就可能全表扫描。用UNION ALL拆成两条SQL通常能保住索引。
4.3 并行 SQL 优化:什么时候该让数据库"多人协作"
"并行sql优化"这个词看着高级,其实理解起来很简单:当一个SQL的任务能被拆成多个小任务同时执行时,数据库会分配多个线程去跑,最后汇总结果。SQL Server的并行度由MAXDOP控制,MySQL 8.0主要是InnoDB的并行扫描。
但它不是银弹。并行执行有固定的调度开销,小表查询开并行反而变慢。我自己的经验是:单条SQL扫描超过千万行、且要做的聚合或排序很重时,才值得考虑并行。
在实际调优中,我优先把索引和SQL写法做到位,然后才考虑并行配置。SQL Server可以通过OPTION (MAXDOP 4)针对单条SQL限制并行度:
sql复制SELECT customer_id, COUNT(*) AS order_cnt
FROM order_flow
GROUP BY customer_id
OPTION (MAXDOP 4);
如果你在Spring Boot里遇到单个SQL执行10秒被自动关闭的问题,先别怀疑并行,多半是连接池或数据库的command timeout配置太小。数据源连接和SQL执行的超时是两套参数,注意分开排查。
5. SQL注入攻防:万能密码背后的原理
热词里"sql注入万能密码绕过"和"sql注入"排得那么靠前,说明这不仅是安全专家的领域,也是所有写SQL的人必须理解的常识。有个段子:一个不懂SQL注入的后端,不是一个合格的后端。这话糙理不糙。
5.1 万能密码绕过是怎么发生的
想象一段用于登录的SQL:
sql复制SELECT * FROM users
WHERE username = 'admin' AND password = '123456';
如果代码是这样拼的:
csharp复制string sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
攻击者在用户名框里输入admin' --,密码随便填,拼出来就是:
sql复制SELECT * FROM users
WHERE username = 'admin' --' AND password = '123456';
--在SQL Server里是注释符,后面的密码校验被注释掉,于是只要存在用户admin,这条SQL就能查出记录,登录直接绕过。更嚣张的输入是' OR 1=1 --,那就意味着所有用户都能被查出来。
这种问题的本质是:把用户输入当成SQL代码执行了。你以为是传一个参数,实际上攻击者把你的SQL语句"改写"了。
5.2 参数化查询如何彻底解决
根本解法就是参数化查询:让SQL语句的结构里只有占位符,用户输入作为数据传入,数据库引擎永远不做字符串拼接。
csharp复制using (var cmd = new SqlCommand(
"SELECT * FROM users WHERE username = @username AND password = @password",
conn))
{
cmd.Parameters.AddWithValue("@username", username);
cmd.Parameters.AddWithValue("@password", password);
// 执行
}
就算username传admin' --,数据库也只知道"我要找一个用户名是admin' --字符串的记录",而不是把字符串解析成代码。比任何过滤黑名单都可靠。
5.3 参数化与动态SQL共存的安全底线
讲到第三章MyBatis那段,你可能想问:"动态SQL不是要拼接吗?会不会重新引入注入风险?"答案是:只要使用#{}参数占位,就安全;一旦写了${},直接拼字符串,就跟上面的C#字符串拼接一样危险。
所以在MyBatis里我的铁律是:
- 排序字段、表名等无法参数化的场景,必须用白名单校验后拼接,比如只允许
ASC/DESC,字段名必须匹配枚举; - 普通查询值,一律
#{},禁用${}; - 写SQL排查工具或后台系统时,也要坚持参数化,别觉得"内部系统没人攻击"。
6. 常用工具链与效率技巧
学SQL离不开趁手的工具。这一章把我日常用到的工具和几个"能省半小时"的操作写出来。
6.1 DBeaver 与 HeidiSQL 导入 SQL 文件
热词里"dbeaver导入sql文件""heidisql导入sql文件"都有,正好一起说。
DBeaver支持几乎所有主流数据库,导入SQL文件的方式很简单:连接数据库后,文件 -> 打开 SQL 脚本,把sql文件拖进去,然后选中全部执行。需要注意的大坑是:如果SQL文件里有USE database;或者CREATE DATABASE,要先确认你连接的账号有建库权限,否则会中途报错。另外,SQL文件里的字符集可能和数据库不一致,中文乱码先检查UTF-8和GBK,DBeaver的编辑器右下角可以临时切换编码。
HeidiSQL只连MySQL/MariaDB/PostgreSQL/SQL Server,但它对MySQL的支持更顺手。导入SQL文件的路径是:文件 -> 执行 SQL 文件。它会弹一个进度条,错误行会标红,适合大脚本分批执行。
6.2 代码格式化工具与"显示行号"
"sql代码排版工具"这个热词我猜是被团队规范逼出来的。SQL写长了确实难读,我常用的格式化方案:
- SQL Server Management Studio(SSMS)自带格式化和缩进快捷键:
Ctrl + Shift + Q打开查询设计器,再转SQL;但体验一般。 - 在线工具SQLFormat(sqlformat.org)支持多种数据库方言,还支持自定义关键字换行,我一般把缩进宽度设成4,SELECT、FROM、WHERE、ORDER BY强制换行。
- 代码编辑器和IDE插件:VS Code装
sql-formatter插件,JetBrains系自带SQL格式化功能,都能满足团队代码规范。
"sql server显示行号"这个功能在SSMS里默认没开,设一下能极大提升对错行的效率:菜单工具 -> 选项 -> 文本编辑器 -> Transact-SQL -> 常规,勾选"行号"。这个设置对诊断语法错误太有用了。
6.3 SQL Server 自动备份配置
"sql server 2014自动备份"上了热词,我提一下最简单的思路。SQL Server的自动备份有两条路:用SSMS的"维护计划"向导,或者直接写SQL Agent作业。
我倾向直接写T-SQL作业,因为可复制、可版本管理:
sql复制BACKUP DATABASE [YourDb]
TO DISK = N'D:\backup\YourDb_20241201.bak'
WITH NOFORMAT, NOINIT, NAME = N'YourDb-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, COMPRESSION;
搭配SQL Agent作业,每天凌晨2点跑一次,保留最近30天备份文件。我踩过的坑是:备份文件路径的文件夹必须存在,否则作业直接失败,而且SQL Agent服务账号要有文件夹写权限。
7. 面试中反复出现的 SQL 题型
最后这块写给正在准备面试的读者。"sql面试题"也是热词,我整理三个出现频率最高的题型思路,外加一个实际生产中常被问到的"达梦数据库查慢SQL定位"场景。
7.1 分组Top N:窗口函数的标准打法
经典题:"查每个部门工资最高的员工"。不用窗口函数时,你得用子查询加关联,写得很绕:
sql复制SELECT e.*
FROM employee e
JOIN (
SELECT dept_id, MAX(salary) AS max_salary
FROM employee
GROUP BY dept_id
) m ON e.dept_id = m.dept_id AND e.salary = m.max_salary;
窗口函数版本:
sql复制SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM employee
) t
WHERE t.rn = 1;
PARTITION BY dept_id表示按部门分组,ORDER BY salary DESC组内排序,ROW_NUMBER()从1开始编号。取前3名就把WHERE t.rn <= 3。这是面试官最想听到的标准答案,而且实际生产里用得很频繁。
7.2 连续登录N天:基于日期差的分组技巧
"连续登录N天"是互联网公司面试最爱的SQL题。核心解法是用"日期减去行号"得到分组标识:
sql复制WITH user_dates AS (
SELECT user_id, login_date,
DATEADD(DAY, -ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
), login_date) AS grp
FROM login_log
WHERE login_date BETWEEN '2024-11-01' AND '2024-11-30'
)
SELECT user_id, MIN(login_date) AS start_date,
MAX(login_date) AS end_date, COUNT(*) AS days
FROM user_dates
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;
这个思路非常巧妙:连续的日期,减去行号后得到同一个基准日。比如1号、2号、3号的login_date分别减1、2、3,结果都是同一天,这样一GROUP BY grp就能把连续区间捞出来。
7.3 行转列:PIVOT 与 CASE WHEN
"行列转换"题在面试和报表需求里都常出现。把"月份-销售额"转成"每列一个月":
sql复制SELECT
product_id,
SUM(CASE WHEN month = '2024-01' THEN amount END) AS jan_amount,
SUM(CASE WHEN month = '2024-02' THEN amount END) AS feb_amount,
SUM(CASE WHEN month = '2024-03' THEN amount END) AS mar_amount
FROM sales
WHERE month BETWEEN '2024-01' AND '2024-03'
GROUP BY product_id;
SQL Server还提供PIVOT关键字,但我个人更喜欢CASE WHEN写法,因为它可读性好,而且MySQL也通用。
7.4 一个真实场景:达梦数据库用core文件定位慢SQL
最后补一个我在信创项目里遇到的场景,热词里"达梦 bt查看core文件定位sql"应该也是同类问题。达梦数据库是国产数据库,出问题时没有SQL Server那种图形化诊断那么直观。我当时的排查思路是:先看dm_ini里的日志和trace配置,如果服务异常或者SQL卡顿,通过bt(backtrace)查看core文件里的调用栈,结合SQL日志定位是哪条SQL把session卡死的。
这种排查特别考验经验,因为它没有现成的"慢查询日志"界面,你得自己翻dm_*.log,再配合系统视图v$sessions、v$sql_text查正在执行的SQL。
我当时在实际项目中悟出来的体会是:任何数据库出问题,第一步永远不是改代码,而是确认"到底是哪条SQL在什么时间点做了什么操作"。这个习惯帮我省了无数排查时间。
写在最后,几句实在话
SQL这门技能,入门不难,难的是遇到真实数据量时的那份手感。这份笔记围绕那些搜索热词展开,很多内容都是我在实际项目里用真金白银换来的经验,特别是慢SQL优化和SQL注入这两块,几乎每个后端开发都会遇到。如果你正在学,我建议别急着把所有语法背完,先把你手头业务最常跑的那几条SQL吃透,再看看怎么优化、怎么确保安全——这样学得最快。
最后再分享一个小技巧:写SQL之前,先把你想要的结果用大白话说清楚,比如"我想知道每个客户上个月买了多少单",翻译成"按客户分组,对订单ID计数,过滤上个月的时间范围",再落笔写语法。这个习惯帮我少写了很多废SQL,也更容易跟产品对需求。希望这份笔记也能帮到你。
