拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SQL学习笔记:从基础语法到慢SQL优化的完整实战指南

SQL这门语言很多人觉得就是“增删改查”四个字背几个命令就能上手。可真到了工作中面对一张张动辄几十个字段的表、嵌套三层起步的查询需求以及时不时冒出来的慢查询告警才发现当年“基础扎实”的自信根本经不起推敲。我整理SQL学习笔记时特意结合了日常使用频率最高、踩坑最深的一些知识点把那些零散的语法、函数和优化思路重新梳理了一遍。这篇总结不只是列语法更想把每个操作背后的适用场景和坑点讲清楚希望对正在补SQL基础、或者准备系统复习一遍的朋友有实际帮助。1. SQL整体学习路径与核心思路1.1 先搞懂SQL到底在解决什么问题SQL全称是Structured Query Language结构化查询语言。它本质上是跟关系型数据库对话的一套标准接口。你不需要关心数据在磁盘上怎么存储、索引结构是B树还是哈希表只需要用声明式的语法告诉数据库“我要什么数据”至于怎么取最快是数据库优化器考虑的事。理解这一点特别重要。很多初学者容易陷进一个误区试图用写程序逻辑的方式去写SQL比如纠结先做哪个条件过滤、要不要手动拆临时表。实际上SQL是描述“结果集”的语言你描述得越清晰优化器越容易找到高效路径。我在带新人时经常说一句话写SQL之前先想清楚最终要拿到什么样的表然后倒推需要哪些步骤这样思路能清晰很多。SQL的核心能力可以划分为四类DQL数据查询SELECT是工作中占比超过80%的操作DML数据操作INSERT、UPDATE、DELETEDDL表结构定义CREATE、ALTER、TRUNCATE等DCL权限控制GRANT、REVOKE一般DBA用得更多学习顺序上我建议先啃透SELECT的各种用法因为查询语法覆盖了过滤、关联、聚合、子查询、窗口函数等一系列核心技能把这些掌握了后面学DML和DDL几乎没有障碍。1.2 一份能落到实处的SQL学习路线结合我自己走过的弯路比较推荐下面这条学习路径环境搭建阶段本地装一个MySQL或者SQL Server找一份有代表性的示例数据比如电商订单表、用户表边学边练。单表查询阶段掌握SELECT基础语法、WHERE条件过滤、ORDER BY排序、LIMIT分页以及DISTINCT去重。聚合与分组阶段理解GROUP BY的分组逻辑掌握COUNT、SUM、AVG、MAX、MIN这几个聚合函数搞清楚WHERE和HAVING的区别。多表关联阶段INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN的区别和执行逻辑这是SQL学习的第一道坎。子查询与集合运算阶段IN、EXISTS、ANY、ALL的用法UNION和UNION ALL的取舍。窗口函数进阶ROW_NUMBER、RANK、DENSE_RANK、SUM() OVER()等解决“分组内排名”“累计求和”这类复杂需求。性能优化入门EXPLAIN执行计划怎么看、索引失效的场景、慢SQL的常见特征。这套路径走下来日常工作中90%的取数需求基本都能覆盖。2. SQL基础语法核心细节解析2.1 SELECT查询不只是SELECT * FROMSELECT语法是SQL的地基。很多人写了几年SQL还是只会SELECT *这其实是学习态度的偷懒。真正规范的写法应该明确列出需要的字段这个习惯在数据量大、字段多的时候尤其重要。一个完整的查询子句顺序是固定的SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT。这个顺序是SQL语法规定的书写顺序但它和执行顺序并不一样。数据库真实执行顺序是FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。理解执行顺序是排查SQL报错和性能问题的关键。比如WHERE子句中不能用SELECT里定义的别名就是因为WHERE先执行别名还没生成。-- 推荐写法明确列出字段别用星号 SELECT user_id, user_name, created_at FROM users WHERE status 1 ORDER BY created_at DESC LIMIT 20;2.2 WHERE过滤条件写不好数据就取不对WHERE子句是查询的“闸门”它支持的条件类型包括比较运算符、、、、、、逻辑运算符AND、OR、NOT、范围判断BETWEEN AND、IN、模糊匹配LIKE和空值判断IS NULL。这块有两个高频坑第一个坑是NULL值处理。SQL里NULL表示“未知”它不等于空字符串也不等于0。任何与NULL做的比较运算结果都是NULL而NULL在WHERE中会被当成FALSE过滤掉。所以判断字段是否为空必须用IS NULL或者IS NOT NULL不能用 NULL。-- 错误写法查不出任何结果 SELECT * FROM users WHERE phone NULL; -- 正确写法 SELECT * FROM users WHERE phone IS NULL;第二个坑是LIKE模糊匹配的性能。前导百分号的写法LIKE %abc会导致索引失效在数据量大的表上会触发全表扫描。如果业务确实需要这样匹配建议考虑全文索引或者搜索引擎方案。2.3 排序与分页LIMIT后面的坑ORDER BY默认是升序ASC降序要显式写DESC。多字段排序时从左到右依次生效只有前面的字段值相同才会用后续字段排序。分页在MySQL里用LIMIT offset, count实现在SQL Server里用OFFSET FETCH或者TOP NOT IN在Oracle里用ROWNUM或FETCH FIRST。这里有个容易忽略的问题LIMIT的偏移量越大查询越慢。比如LIMIT 1000000, 20这种写法数据库要先把前一百万行都读出来再丢掉代价很高。实际工作中处理深分页更优的做法是记住上一页最后一条数据的ID用条件过滤代替偏移量-- 深分页优化写法 SELECT * FROM orders WHERE order_id 1000000 ORDER BY order_id LIMIT 20;2.4 数据去重DISTINCT和GROUP BY怎么选去重是清洗数据时的高频操作。DISTINCT会对结果集去重而GROUP BY是分组聚合。当只是简单去重时两者效果一样但DISTINCT写法更简洁。-- 查询所有不重复的城市 SELECT DISTINCT city FROM users; -- 等价写法 SELECT city FROM users GROUP BY city;需要关注的是千万别写成SELECT DISTINCT col1, col2这种它的语义是col1和col2的组合去重不是单独对col1去重。如果在去重的同时还要统计数据比如统计每个城市的用户数就只能用GROUP BY了。SELECT city, COUNT(*) AS user_cnt FROM users GROUP BY city;2.5 时间与字符串函数取数时的利器SQL中时间函数的坑非常多尤其是不同数据库的差异。MySQL里日期格式化用DATE_FORMAT、日期加减用DATE_ADD/DATE_SUB、取当前时间用NOW()。SQL Server里格式化用CONVERT或FORMAT、取当前时间用GETDATE()。Oracle又有另一套TO_CHAR、SYSDATE。跨数据库写SQL时时间函数基本没有通用性需要特别注意。比较推荐的做法是业务逻辑中尽量别在SQL里做复杂的日期运算能传到代码里处理就传代码里。但下面这种简单的按天分组统计用SQL函数还是很方便的-- MySQL统计最近7天每天的订单量 SELECT DATE_FORMAT(created_at, %Y-%m-%d) AS day, COUNT(*) AS order_cnt FROM orders WHERE created_at DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY DATE_FORMAT(created_at, %Y-%m-%d) ORDER BY day;字符串函数方面最常用的是CONCAT拼接、SUBSTRING截取、REPLACE替换、TRIM去空格注意它只去掉首尾的空格、UPPER/LOWER转换大小写。清洗数据时经常遇到的一个场景是把字段中的空值替换成默认值这时用COALESCE最合适。-- 将NULL和空字符串都替换为未知 SELECT user_id, COALESCE(NULLIF(phone, ), 未知) AS phone_fixed FROM users;3. 进阶查询能力聚合、关联与子查询3.1 GROUP BY分组聚合HAVING和WHERE的区别GROUP BY是SQL学习中的一个分水岭。它的语义是把一张表按照某些字段拆成多个“小组”然后对每个小组分别做聚合计算。理解了这个逻辑聚合函数就不再是死记硬背了。使用GROUP BY时有个铁律SELECT后面出现的非聚合字段必须出现在GROUP BY中。不然会返回不确定的结果MySQL默认开启了ONLY_FULL_GROUP_BY模式后会直接报错。HAVING和WHERE的区别是老生常谈但总有人混淆WHERE在分组之前过滤作用于每一行原始数据HAVING在分组之后过滤作用于聚合结果。比如“筛选订单量大于100的客户”必须在HAVING里写因为COUNT(*)这个结果在WHERE阶段还不存在。SELECT customer_id, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders WHERE status paid -- 先过滤掉未支付订单 GROUP BY customer_id HAVING COUNT(*) 100 -- 再筛选订单量达标客户 ORDER BY total_amount DESC;3.2 JOIN关联查询LEFT JOIN结果为啥变多了多表关联是实际工作中绕不开的场景。INNER JOIN取两表交集LEFT JOIN保留左表全部记录RIGHT JOIN保留右表全部记录FULL JOIN两表全部保留MySQL不直接支持FULL JOIN需要用UNION模拟。新手最容易犯的错误是LEFT JOIN之后结果行数比左表多了。这个问题的根源在于左表的一行数据在右表有多条匹配记录。比如一个客户下了多笔订单客户表LEFT JOIN订单表时这个客户就会出现在多行结果中。-- 客户维度统计每个客户的订单量和总金额 -- 注意必须用GROUP BY把一对多拉回一对一 SELECT c.customer_id, c.customer_name, COUNT(o.order_id) AS order_cnt, COALESCE(SUM(o.amount), 0) AS total_amount FROM customers c LEFT JOIN orders o ON c.customer_id o.customer_id GROUP BY c.customer_id, c.customer_name;这里需要特别提醒COUNT(o.order_id)和COUNT()在LEFT JOIN场景下语义不同。COUNT(o.order_id)只统计右表非空的记录数而COUNT()会统计包括NULL在内的所有行。当左表记录在右表没有匹配时COUNT(*)会返回1这个坑很容易让统计结果出错。3.3 子查询与EXISTS哪种写法更优子查询分为标量子查询返回单个值、行子查询返回一行、表子查询返回多行多列。常用的场景包括WHERE中使用IN或EXISTS、FROM中作为派生表、SELECT中作为计算字段。EXISTS和IN在语义上都可以表示“存在性判断”但执行方式不同。EXISTS是逐个判断外层表的每一行一旦找到匹配就停止属于相关子查询IN是先执行内层子查询生成结果集再与外层逐行比对。当子查询结果集很小而外层表很大时IN通常更快当外层表很小而子查询表很大时EXISTS通常更快。-- 查询有订单记录的客户 SELECT * FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id c.customer_id );EXISTS的子查询SELECT 1是惯例写法其实SELECT后面写什么都无所谓因为EXISTS只关心“有没有记录”。在FROM子句中嵌套子查询时子查询作为一个派生表使用必须起别名这是很多初学SQL的人容易漏掉的关键点。-- 先聚合出每个客户的总金额再筛选出总金额超过平均值的客户 SELECT customer_id, total_amount FROM ( SELECT customer_id, SUM(amount) AS total_amount FROM orders GROUP BY customer_id ) AS t WHERE total_amount ( SELECT AVG(total_amount) FROM ( SELECT customer_id, SUM(amount) AS total_amount FROM orders GROUP BY customer_id ) AS avg_t );3.4 UNION合并结果集UNION和UNION ALL怎么选UNION用于合并两个或多个查询的结果集。它自动去重代价是需要对合并后的结果做排序去重操作UNION ALL直接合并不去重性能更好。如果业务上两个查询的结果本来就不会重复直接用UNION ALL更高效。使用UNION有个约束两个查询的列数和数据类型需要对应。列名以第一个查询的列名为准。-- 合并未支付订单和异常订单 SELECT order_id, 未支付 AS reason FROM orders WHERE status pending UNION ALL SELECT order_id, 金额异常 AS reason FROM orders WHERE amount 0;4. 窗口函数与WITH子句SQL进阶的必经之路4.1 窗口函数到底是什么窗口函数是SQL进阶中最值得投入时间学习的部分。它和GROUP BY最大的区别是GROUP BY会把多行聚合成一行丢失明细窗口函数不会合并行而是在每一行旁边同时输出聚合或排名结果。窗口函数的基本语法结构是函数() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)。PARTITION BY是分区分组ORDER BY决定窗口内的计算顺序。-- 按部门分区、按薪资排名结果保留员工明细 SELECT emp_name, dept_id, salary, RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rk FROM employees;这个查询的输出结果中每个部门内部的员工都会有一个排名值但原始行并没有被折叠员工明细依然完整。4.2 RANK、DENSE_RANK和ROW_NUMBER的区别这三个排名函数非常容易混淆。用一个具体例子说明员工薪资RANKDENSE_RANKROW_NUMBERA10000111B9000222C9000223D8000434RANK会跳过并列后的排名出现两个第2名后下一位是第4名DENSE_RANK不跳号并列后下一位是第3名ROW_NUMBER不管是否并列直接按顺序给出行号。SQL Server老版本中还有ROW_NUMBER和RANK但没有DENSE_RANK2008 R2之前这是历史版本兼容时可能遇到的问题。4.3 窗口聚合累计求和和移动平均窗口函数和聚合函数组合可以实现很多“分组内累计”的需求。比如计算每个用户截至当前订单的累计消费金额SELECT customer_id, order_id, amount, SUM(amount) OVER (PARTITION BY customer_id ORDER BY order_id) AS cum_amount, AVG(amount) OVER (PARTITION BY customer_id ORDER BY order_id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS avg_3_orders FROM orders ORDER BY customer_id, order_id;ORDER BY在窗口函数中之所以重要是因为它定义了“窗口的边界”。默认情况下从分区第一行到当前行形成累计效果加了ROWS BETWEEN ... AND ...可以自定义窗口范围用来做移动平均这类计算。4.4 WITH子句把复杂查询拆成人话WITH子句也叫Common Table ExpressionCTE是SQL中非常重要的“拆解工具”。它的作用是把一个复杂的查询拆成多个有名字的临时结果集让代码可读性大幅提升。很多复杂报表用一条SQL写出来嵌套四五层子查询自己隔天看都想骂人。用WITH改写之后逻辑一下就顺了WITH customer_stats AS ( SELECT customer_id, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders WHERE status paid GROUP BY customer_id ), high_value_customers AS ( SELECT * FROM customer_stats WHERE total_amount 100000 ) SELECT c.customer_name, h.order_cnt, h.total_amount FROM high_value_customers h JOIN customers c ON h.customer_id c.customer_id ORDER BY h.total_amount DESC;这种写法最大的好处是每一步做什么一目了然排查问题时也能快速定位是哪一步的结果不对。CTE在多数主流数据库中还有优化效果同一查询中多次引用同一个CTE时数据库通常会物化结果避免重复扫描。5. 慢SQL优化与EXPLAIN执行计划实战5.1 慢SQL是怎么产生的慢SQL是生产环境中影响数据库稳定性的头号杀手。一个慢查询轻则拖慢接口响应重则把数据库CPU打满、连接数耗尽造成整个系统雪崩。从经验来看慢SQL的高发场景有下面几类多表关联时关联字段没有索引产生全表扫描WHERE条件中字段上用了函数或隐式类型转换导致索引失效SELECT *返回大量不需要的字段产生大量IOLIMIT深分页偏移量过大数据量巨大且没有合理的分表策略复杂的正则匹配如LIKE %关键字%排查慢SQL的第一件事是打开慢查询日志。MySQL中在配置文件里设置slow_query_logON和long_query_time1就可以记录执行时间超过1秒的SQL。5.2 EXPLAIN执行计划优化慢SQL的导航仪拿到慢SQL后第一步是看它的执行计划。MySQL中用EXPLAIN加在SQL前面SQL Server中用SET SHOWPLAN_ALL ONOracle中用EXPLAIN PLAN FOR。EXPLAIN输出中我最关注这几个字段字段关注点说明type重要ALL是全表扫描明显可以优化最好能到ref或constpossible_keys重要可能用到的索引为空说明没有可用索引key重要实际用到的索引为NULL说明没用上rows参考预估扫描行数越小通常越快Extra重点关注出现Using filesort或Using temporary通常需要优化type字段的取值从好到差依次是system const eq_ref ref range index ALL。如果看到ALL基本就是全表扫描优化空间很大。比如下面这条EXPLAIN结果中type为ALL、rows达到200万条说明这条SQL把整张表扫了一遍。主要问题在WHERE的customer_no字段上没有索引EXPLAIN SELECT * FROM orders WHERE customer_no C10001;解决办法就是加索引CREATE INDEX idx_customer_no ON orders(customer_no);加完索引再看EXPLAINtype会从ALL变成refrows降到几行查询速度往往是数量级的提升。5.3 索引失效的经典场景索引加了不等于一定被用到。几个最常见的索引失效场景值得背下来在索引列上使用函数WHERE DATE(created_at) 2025-01-01会使索引失效应改写为范围条件WHERE created_at 2025-01-01 00:00:00 AND created_at 2025-01-02 00:00:00。隐式类型转换字段类型是字符串但条件写WHERE phone 13800001111数据库会做类型转换导致索引失效应该写成WHERE phone 13800001111。前导模糊匹配LIKE %关键字导致索引失效而关键字%可以使用前缀索引。联合索引最左前缀原则联合索引(a, b, c)只有在查询条件包含a时才能走索引直接查b或c则不行。OR条件中存在非索引列查询中OR两边的字段一个有索引一个没有优化器可能选择全表扫描。-- 联合索引(uuid, event_time)示例 -- 这条可以走索引 SELECT * FROM events WHERE uuid xxx AND event_time 2025-01-01; -- 这条无法走索引缺少最左前缀字段uuid SELECT * FROM events WHERE event_time 2025-01-01;5.4 并行SQL优化思路数据量特别大时单条SQL无论怎么优化都难以满足性能要求这时可以考虑并行SQL的思路。Oracle中可以通过PARALLEL hint或多个会话并行处理MySQL 8.0之后的InnoDB在count和部分聚合场景也支持并行扫描。实际工程中更常见的并行方案是“任务拆分”把一个大查询按照时间范围、地区等维度拆成多个子任务用多线程并发执行再把结果合并。这个方案能绕过单条SQL的瓶颈但要特别注意拆分维度的均匀性防止某个子任务特别大导致整体卡在木桶最短板上。6. SQL工具与常见环境问题处理6.1 常用的SQL开发工具选择工具选对了写SQL的效率能提升不少。不同场景下我的推荐不太一样HeidiSQL轻量级MySQL管理工具连接快、导出数据方便绿色版免安装适合日常开发和数据维护。SQL Server Management StudioSSMSSQL Server官方工具调试存储过程、查看执行计划都很强。PL/SQL DeveloperOracle开发的主流工具之一写存储过程、调试包很顺手连接局域网内其他机器的Oracle数据库时需要配置好Oracle客户端和tnsnames.ora。DBeaver开源免费、支持几乎所有数据库用JDBC连接界面清爽适合多数据库混合管理的场景。工具只是手段最重要的是理解每种数据库的差异。同一个SQL语句在MySQL、SQL Server和Oracle中可能语法完全不同比如分页、字符串拼接、日期格式化。跨库迁移时别指望语句能平滑切换这是动手前就该有的心理预期。6.2 SQL Server安装与卸载的典型坑SQL Server的安装卸载一直是热门问题很多初学者在这个上面花了大量时间。SQL Server 2008 R2、2012、2016、2019、2022几个版本我都装过如果说安装是简单操作那卸载才是真正的坑。SQL Server安装最大的问题是组件多、服务多卸载不干净会导致重装失败。常见的报错包括“无法卸载 Microsoft SQL Server 2008 R2 安装程序支持文件因为安装了其他功能”、“26003错误”等。处理这类问题比较稳妥的思路是依次卸载SQL Server相关功能和组件先主后从。在“服务”中找到SQL Server相关服务和计划任务全部停止。手动删除残留的安装目录和注册表项。下载专用的Microsoft SQL Server安装清理工具做最后清理。注意清理注册表要特别谨慎建议备份后再操作。SQL Server 2008 R2涉及的补丁问题也比较多检查是否已安装补丁可以在“控制面板-程序和功能”中查看已安装的更新或者在SQL Server安装中心里查看版本号。SP3或SP4补丁建议打上很多2008 R2的疑难杂症其实是补丁缺失导致的。6.3 PL/SQL Developer连接远程Oracle工作中有时候不直接连接服务器而是用PL/SQL Developer连接局域网内其他机器的Oracle数据库。这里有三个关键配置Oracle客户端本机需要安装与数据库版本匹配的Oracle客户端至少是Instant Client。tnsnames.ora配置在这个文件里配置数据库连接串指定IP、端口和服务名ORCL (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 192.168.1.100)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED)(SERVICE_NAME orcl)) )防火墙目标机器的1521端口需要放开不然会一直报“No listener”超时错误。最容易忽略的一点是32位和64位的兼容问题。PL/SQL Developer如果是32位的必须搭配32位的Oracle Instant Client位置需要在首选项里指定正确否则会报“Could not load oci.dll”。6.4 其他工具类需求导出与备份日常工作中经常需要把查询结果导出成SQL文件或者Excel。HeidiSQL的导出功能做得比较完善可以按行分批导出避免大数据量时导出中断。MySQL本身也提供了mysqldump命令行工具适合导出整个库或指定表。用mysqldump导出时有几个参数很实用# 只导出表结构不导出数据 mysqldump -u root -p -d database_name schema.sql # 导出指定表的数据 mysqldump -u root -p database_name table_name table_data.sql # 设置字符集避免乱码 mysqldump -u root -p --default-character-setutf8 database_name backup.sql如果是导出带条件的数据EXPORT/INTO OUTFILE是另一个选择但要注意MySQL对导出路径和文件权限的限制。多数情况下先用SELECT把数据查出来再在图形化工具里选择“导出结果集”更直接。7. SQL注入原理与防御思路7.1 什么是SQL注入SQL注入是数据库安全中最常被提及的威胁之一。它的原理并不复杂程序在拼接SQL字符串时把用户输入的内容直接当成了SQL代码的一部分执行导致攻击者可以用构造的输入修改查询逻辑。一个经典的例子登录时执行SELECT * FROM users WHERE username admin AND password 123456如果用户名输入框里填了 OR 11拼出来的SQL变成了SELECT * FROM users WHERE username OR 11 AND password 123456由于11恒为真只要密码正确查询就能绕过用户名校验返回第一个用户的记录。如果应用程序逻辑判断“查到记录即登录成功”攻击者就拿到了账号权限。这类漏洞的根本原因是“数据和代码没有分离”。防御思路也围绕这一点展开。7.2 防御SQL注入的正确姿势第一优先级是使用参数化查询PreparedStatement。参数化查询会把SQL结构和参数值分开传输给数据库数据库先编译SQL结构再把参数当纯数据绑定进去。这样无论参数里包含什么内容都只是数据永远不会被当成SQL执行。# Python示例使用参数化查询避免SQL注入 import sqlite3 conn sqlite3.connect(example.db) cursor conn.cursor() # 安全写法 cursor.execute( SELECT * FROM users WHERE username ? AND password ?, (username, password) )第二道防线是权限最小化。数据库账号按需分配权限应用账号只给必要的SELECT、INSERT、UPDATE、DELETE权限不给DROP、TRUNCATE等高危权限。这样即使被注入攻击者能做的事也有限。第三是输入验证。对内容格式做白名单校验比如ID必须是数字、邮箱必须符合邮箱格式。白名单校验比黑名单过滤可靠得多因为黑名单永远可能漏掉新变种。从技术原理上理解SQL注入不是为了教人攻击而是为了在设计和开发时能有意识地把好安全关口。很多SQL注入漏洞本质上都是因为开发时图省事、直接拼接字符串导致的。8. 常见问题排查与实用技巧速查8.1 高频报错和解决思路学习和使用SQL的过程中有几类报错出现频率极高我把排查思路整理如下问题典型场景排查方向无法删除数据库SQL Server 2008中数据文件被占用、存在活动的连接会话检查是否有进程占用数据库杀掉相关会话后再试SQL Server可先将数据库设为单用户模式再删除查询结果精度丢失金额字段用了FLOAT或DOUBLE金额和计算精度要求高的场景应使用DECIMAL类型避免浮点数误差死锁问题多个事务同时更新多张表且顺序不一致规范事务中操作表的顺序在SSMS中结合死锁图分析具体等待关系排序结果不稳定ORDER BY的字段有重复值没加唯一字段做次级排序在ORDER BY中追加唯一字段保证排序结果的确定性数据导入乱码导出SQL文件时字符集不一致统一数据库、连接、文件三个环节的字符集MySQL中优先使用utf8mb48.2 SQL学习中的几个好习惯写SQL是一件熟能生巧的事但有些好习惯能让你少走弯路第一代码格式化要趁早养成。关键字大写、字段列表一行一个、缩进对齐这些看似不太重要的习惯在SQL语句变长后能帮你节省大量排查时间。团队协作时这个问题尤其重要。第二先跑通再优化。我见过不少同学一上来就想写“最终版”SQL结果一步错步步错调试时间远超过先写简单版再逐步改的时间。先确保逻辑正确再用EXPLAIN分析和优化性能。第三用临时表验证中间结果。复杂查询时报错时别着急看最终结果应该把每一步的中间结果SELECT出来逐段确认数据是否符合预期。排查时把问题范围一步步缩小比盯着整条SQL冥思苦想要高效得多。第四敢用LIMIT保护自己。DELETE和UPDATE执行之前先把条件用SELECT验证一下确认影响的行数符合预期再执行。不带WHERE的DELETE和UPDATE是生产事故的常见根源。-- 删除前先验证避免误删整个表 SELECT * FROM orders WHERE status invalid; -- 确认无误后再执行 DELETE FROM orders WHERE status invalid;第五多备份少硬刚。改表结构和批量更新数据前先做好数据备份。宁可多花五分钟备份不要在出事之后花两小时恢复。8.3 从基础到实战的最后一公里SQL学到最后拼的不再是语法背诵而是拆解业务需求的能力。把“运营想看本月每个品类的销售情况以及和上月的环比”这种模糊描述翻译成清晰的表结构、字段、过滤条件和聚合逻辑才是真正的核心功力。这部分能力没有捷径只能靠大量练习积累。我的建议是找一套完整的业务数据库比如开源的TPC-H测试数据或者网上公开的电商数据库给自己出题复购率怎么算、同期对比怎么做、用户留存漏斗怎么跑解决这些问题时你会发现前面学的基础语法会慢慢串联成一张网。写在最后整理这份SQL学习笔记时我又把基础语法、窗口函数、慢SQL优化、安全防御几个模块从头过了一遍。说句心里话SQL这门语言入门容易但真正用好需要用项目和问题不断打磨。如果你正在学习SQL希望这篇总结能帮你少踩一些坑如果你已经有一定基础也不妨把它当作一份查漏补缺的地图。我自己在实战中最深的体会是SQL能力的提升往往不来自看多少篇教程而是来自你真正花时间解决了一个又一个具体的数据问题。多写、多练、多在报错中找答案能力和手感自然就上来了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门