SQL常用语句基础大全:查询、聚合、关联与优化实战
发布时间:2026/9/28 13:12:58来源:尧图网络
既然题目叫“SQL常用语句(基础)大全”我就直接开门见山把这段时间梳理出来的实用内容一次性倒出来。这不算什么高深玩意儿但胜在够基础、够常用每一段都是我实际写SQL时反复用过的东西也踩过不少坑。全文会围绕查询、过滤、聚合、关联、更新、去重、窗口函数这些核心场景展开配合SQL Server、MySQL通用的写法个别差异点会单独标注。适合刚上手数据库的初学者也适合做后端开发、数据分析、运维的同学当速查手册用。1. SQL基础查询与过滤先把数据捞出来1.1 SELECT的基本用法与列操作SQL里干得最多的一件事就是查数据SELECT就是这一切的起点。我在日常工作里最基础的写法就是指定要查的列能少查就少查千万别动不动SELECT *。这不仅是规范问题更是性能问题。当表里字段多了、数据量大了之后SELECT *会把所有列都拖出来网络传输、内存消耗都跟着涨对索引命中也是一种伤害。-- 基础查询只查需要的列 SELECT user_id, user_name, register_time FROM users;如果嫌列名太丑可以用别名尤其是多表关联的时候别名能省很多打字时间也让结果集看着更清爽。-- 带别名的查询 SELECT user_id AS id, user_name AS name FROM users;这里有个小习惯我建议从一开始就养成能用别名就用别名能用全限定名表名.列名就用全限定名。很多初学者写多表查询时不带表名前缀结果两个表都有id字段直接报ambiguous column name一脸懵。与其等报错不如从一开始就写清楚。1.2 WHERE过滤的常见陷阱与运算符优先级查数据不过滤等于耍流氓WHERE就是过滤的核心。别以为过滤就是把条件写上这么简单运算符优先级、NULL处理、隐式转换哪一个都能让你在深夜加班排查。-- 带过滤条件的查询 SELECT user_id, user_name FROM users WHERE register_time 2024-01-01 AND status 1;这里先提醒一个优先级问题AND的优先级高于OR。所以如果你写WHERE a 1 OR b 2 AND c 3实际执行的是a 1 OR (b 2 AND c 3)如果业务上想要(a 1 OR b 2) AND c 3那就必须加括号。这种低级错误我见过不止一次在生产事故里出现。我的建议很简单只要混合了AND和OR一律加括号别贪图省事。IN和NOT IN也是高频用法但有一个大坑必须说当NOT IN后面的子查询结果包含 NULL 时整个查询会返回空结果。这不是bug是SQL的三值逻辑决定的。NULL 既不是真也不是假而是“未知”所以NOT IN (2, 3, NULL)的结果是“未知”一行都匹配不上。这个坑在线上数据里极其隐蔽因为数据量大的时候你很难察觉结果少了。我自己后来养成的习惯是只要用NOT IN就额外加一个IS NOT NULL过滤或者干脆用NOT EXISTS替代。1.3 LIKE模糊查询与字符转义LIKE是模糊查询的主力在用户搜索、名称匹配、日志筛选这些场景里出现频率极高。-- 查询名字里包含张的用户 SELECT user_id, user_name FROM users WHERE user_name LIKE %张%;%表示任意长度的字符_表示任意单个字符。这里有一个容易忽略的问题当你要搜索的内容本身包含%或_时它们会被当成通配符处理所以需要转义。SQL Server里用ESCAPE关键字来自定义转义字符。-- 查询包含%字符的记录 SELECT * FROM logs WHERE message LIKE %\%% ESCAPE \;顺带提一嘴索引问题。LIKE abc%这种前缀匹配是可以走索引的但LIKE %abc%因为前面有通配符绝大多数情况下索引就失效了。数据量大时这种查询会直接变成全表扫描慢到怀疑人生。业务上如果经常要做这种后缀模糊搜索建议考虑全文索引或者干脆引入搜索引擎别硬扛。1.4 排序与分页LIMIT/OFFSET与TOP的差异排序是查询的基本功ORDER BY配上ASC升序默认和DESC降序就能满足大部分需求。关键点是排序一定要指定排序字段否则结果顺序是不保证的。在分页场景里如果你不指定ORDER BY同样的查询翻到第二页可能重复第一页的数据。分页这块MySQL和PostgreSQL用LIMIT/OFFSETSQL Server用OFFSET/FETCH或者老式的TOP。-- MySQL/PostgreSQL: 每页20条取第2页 SELECT * FROM orders ORDER BY order_id DESC LIMIT 20 OFFSET 20; -- SQL Server: 同样效果 SELECT * FROM orders ORDER BY order_id DESC OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY;分页的一大坑是越往后翻越慢因为OFFSET就像是顺序跳过了前面的所有行数据量极大的时候深层分页性能会很差。如果业务上只需要“上一页/下一页”这种模式可以改成基于游标的方式也就是记住当前页最后一条的order_id然后用WHERE order_id 上次的值去查效率完全不一样。2. 聚合、分组与去重把数据变为结论2.1 聚合函数COUNT/SUM/AVG/MAX/MIN的使用细节拿到一堆数据光看原表意义不大得算出结论才有价值。聚合函数就是干这个的。COUNT、SUM、AVG、MAX、MIN这五个是绝对的高频但细节上的差异往往决定了结果对不对。先说COUNT。有两个常见写法COUNT(*)和COUNT(列名)很多人以为它们是同一个东西其实不一样。COUNT(*)统计的是行数哪怕某一列全是NULL它也会把行数算进去。而COUNT(列名)只统计该列不为NULL的行数。如果你的业务是“统计有多少人填了手机号”那就必须COUNT(phone)用COUNT(*)会给出虚高的数字。SUM和AVG会自动忽略NULL值。这一点好用但也要注意如果一个订单表里有10条记录其中2条金额是NULLAVG(amount)只对8条非NULL的求平均分母是8而不是10。如果你业务上希望NULL按0处理需要先用COALESCE(amount, 0)包一层。还有一个常见场景计算去重后的数量。比如统计有多少个不同的城市无法直接COUNT(city)得配合DISTINCTSELECT COUNT(DISTINCT city) FROM users;2.2 GROUP BY分组的逻辑与常见错误分组是分析类需求的核心没有GROUP BY聚合函数只能在全局生效。分组本质上就是把相同值的行归到一个桶里然后对每个桶做聚合计算。-- 按状态统计订单数和总金额 SELECT status, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders GROUP BY status;这里最大的坑是SELECT里出现的列要么是分组列要么必须包裹在聚合函数里。比如下面这条SQL在严格模式的数据库里会直接报错-- 这是错误的写法 SELECT user_id, order_id, SUM(amount) FROM orders GROUP BY user_id;因为order_id既不在GROUP BY里也没被聚合函数包裹这会导致语义矛盾每个分组里可能有多个order_id数据库不知道该显示哪一个。MySQL在默认配置下可能不会报错而是随机取一个这才是最危险的——不报错但结果是错的而且是那种发了线上才发现的错。所以我建议开发环境把ONLY_FULL_GROUP_BY模式打开宁可让它在测试时炸出来也别在生产里悄悄算错。2.3 HAVING与WHERE的本质区别过滤分组后的结果必须用HAVING这是WHERE做不到的。两者逻辑上的区别是WHERE在分组之前过滤行HAVING在分组之后过滤组。-- 查订单数超过5个的用户 SELECT user_id, COUNT(*) AS order_cnt FROM orders WHERE status paid -- 先过滤掉未支付订单 GROUP BY user_id HAVING COUNT(*) 5; -- 再过滤出订单数5的用户很多人会问干嘛不直接用WHERE COUNT(*) 5因为聚合函数在WHERE阶段还没执行SQL的执行顺序是FROM - WHERE - GROUP BY - HAVING - SELECT - ORDER BYWHERE看不到聚合结果。理解这个顺序很重要很多疑难问题都源于对执行顺序的误解。2.4 DISTINCT去重与NULL的纠缠去重需求从来没少过DISTINCT是最直接的实现方式。注意DISTINCT作用于整个查询结果的组合不是单个字段。所以SELECT DISTINCT a, b是对(a, b)的组合去重不是对a去重后顺便留下任意一个b如果需要“按某个字段去重且保留其他字段”用DISTINCT是做不到的这时候要么用窗口函数要么用GROUP BY。-- 查所有不重复的城市名 SELECT DISTINCT city FROM users; -- 查每个城市下的人数 SELECT city, COUNT(*) FROM users GROUP BY city;另外要说的是DISTINCT会把NULL也当成一个“值”来处理。也就是说如果某列有5条NULL和3条非NULLSELECT DISTINCT 该列会返回最多4行结果3个不同值 1个NULL。某些场景下这可能符合预期但如果你想去掉NULL再统计老老实实加WHERE 列 IS NOT NULL。2.5 窗口函数分组排名的进阶解法严格说窗口函数不算基础语句但现在它真的太常用了尤其是排行和组内比较。它可以做到“按组计算但每一行保留自己的明细”这比GROUP BY灵活得多。窗口函数的基本结构是函数() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)。比如给每个用户按下单时间排个序SELECT user_id, order_id, amount, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_id DESC) AS rn FROM orders;ROW_NUMBER生成组内连续的行号RANK和DENSE_RANK用于生成并列排名区别在于RANK有间隔如1,1,3DENSE_RANK没有如1,1,2。做“取每个用户最近一笔订单”这种需求窗口函数比任何自连接都简洁。MySQL 8.0以上和SQL Server 2005以上都支持窗口函数如果你还在用老版本是时候考虑升级了。3. JOIN关联查询与集合运算多张表如何拼出完整信息3.1 四种JOIN的区别及使用场景真实业务中数据几乎永远分散在多张表里JOIN就是把这些碎片拼起来的工具。四种常见JOIN的区别我用最直白的方式描述一下INNER JOIN只取两边都匹配得上的行这是最常用的。LEFT JOIN保留左表的全部行右表没有匹配就补NULL。RIGHT JOIN反过来保留右表全部行。FULL OUTER JOIN两边都保留没有匹配的补NULL。业务里RIGHT JOIN很少用因为把右表换到左边再LEFT JOIN就能达到相同效果。可读性上整个查询习惯性地从左往右读的时候LEFT JOIN更符合直觉。-- 查每个用户最近一笔订单连同用户名 SELECT u.user_id, u.user_name, o.order_id, o.amount FROM users u LEFT JOIN orders o ON u.user_id o.user_id;3.2 JOIN条件与WHERE过滤的执行顺序差异这个坑极其隐蔽。在LEFT JOIN里如果把对右表的过滤条件写进WHERE实际效果可能会变成INNER JOIN。我举个例子-- 预期保留所有用户即使没有有效订单 SELECT u.user_id, o.order_id FROM users u LEFT JOIN orders o ON u.user_id o.user_id WHERE o.status paid;问题在于WHERE在JOIN之后执行一旦右表没有匹配行o.status就是NULLNULL paid的结果是“未知”于是这些行就被过滤掉了。最终结果等价于“只保留有有效订单的用户”左表被“掏空”了。正确的写法是把过滤条件挪到ON后面SELECT u.user_id, o.order_id FROM users u LEFT JOIN orders o ON u.user_id o.user_id AND o.status paid;也就是说ON里的条件控制“怎么匹配”WHERE里的条件控制“匹配完之后留哪些行”。这两者逻辑上要分清。3.3 自连接的应用场景自连接是拿一张表跟自己关联听起来奇怪实际场景不少。最常见的是“查父子关系”的数据比如组织架构表里manager_id指向user_id。-- 查员工和其上级姓名 SELECT e.user_name AS employee_name, m.user_name AS manager_name FROM users e LEFT JOIN users m ON e.manager_id m.user_id;自连接还有一种典型的用法是“行转列”或“错位比较”。比如查相邻两笔订单之间的时间差也可以用自连接实现。这类写法本质上不容易直观理解但一旦用上比写一堆子查询高效得多。3.4 UNION与UNION ALL纵向拼接结果UNION把多个查询纵向拼成一个结果集并自动去重UNION ALL则是全量拼接不去重性能明显更好。如果业务上能确定两个结果集之间不存在重复直接用UNION ALL就好省掉了数据库做去重排序的开销。-- 合并两个表的用户名单不去重 SELECT user_name FROM users_2023 UNION ALL SELECT user_name FROM users_2024;使用UNION时两侧的列数必须一致数据类型最好也要兼容。经常有人问能不能把一个字符串和一个数字直接拼接SQL Server里可能不报错MySQL里通常就直接报类型错误了。稳妥起见先把类型统一成同一种。4. 数据更新与变更INSERT/UPDATE/DELETE的底线操作4.1 INSERT的三种写法与批量插入性能对比插入数据虽然语法简单但写法上有讲究。最基础的是单行插入然后是显式指定列名批量插入。-- 单行插入 INSERT INTO users (user_name, status, register_time) VALUES (张三, 1, 2024-01-01 10:00:00); -- 多行批量插入MySQL/PostgreSQL支持 INSERT INTO users (user_name, status, register_time) VALUES (李四, 1, 2024-01-01 10:00:00), (王五, 1, 2024-01-01 10:00:00);强调一点写INSERT时永远显式列出列名。如果直接INSERT INTO users VALUES (...)一旦表结构变了加了一列这条语句就会炸。而且显式列名让自己也能一眼看出每个值对应哪个字段减少“值串位”的可能。批量插入在大数据量下逐条提交和一次性多行插入的性能差距是数量级的。MySQL中一条SQL插入1000行往往比循环1000次单行插入快几十倍。SQL Server没有多行VALUES语法可以改用INSERT ... SELECT ... UNION ALL或表值构造器来达到类似效果。4.2 UPDATE的条件更新与批量修改的教训UPDATE是风险很高的操作因为你很难一眼看出影响的范围。不写WHERE的UPDATE会把整张表改掉这是我反复提醒团队新人的第一红线。-- 安全的更新写法先SELECT确认影响范围再执行UPDATE SELECT * FROM users WHERE user_id 123; UPDATE users SET status 2 WHERE user_id 123;我个人的工作习惯是在测试环境里先把UPDATE改成等价的SELECT看看影响哪些行确认无误后再执行真正的更新。线上更新数据尤其是核心表最好提前备份一下目标表或至少记下当前的MAX(id)和影响行数。批量更新还有一种常见需求根据另一张表的值来更新目标表。SQL Server里语法是UPDATE ... FROM ... JOIN ...MySQL用的是多表UPDATE。-- SQL Server: 根据订单表更新用户的消费总额 UPDATE u SET total_amount t.sum_amount FROM users u INNER JOIN ( SELECT user_id, SUM(amount) AS sum_amount FROM orders GROUP BY user_id ) t ON u.user_id t.user_id;4.3 DELETE与TRUNCATE的本质差别删除数据的两种方式DELETE和TRUNCATE看着都是清数据底层完全不一样。DELETE是DML操作会逐行删除可以加WHERE会写日志可以配合事务回滚。TRUNCATE是DDL操作直接释放整个表的数据页不能加WHERE不会逐行写日志速度极快但无法回滚。-- 精确删除某部分数据可回滚 BEGIN TRAN; DELETE FROM orders WHERE order_id 1000; ROLLBACK; -- 清空整表数据不可回滚 TRUNCATE TABLE orders;线上环境清空表前先把TRUNCATE当成“连后悔药都没得吃”的操作来对待。哪怕是个临时表我也会先看一眼有没有下游任务在依赖它。这类“删库”事件每年都在各大公司上演多数时候不是坏人搞破坏而是自己人没想清楚就执行了。4.4 事务的四个特性ACID与实操注意事项数据变更操作一旦涉及多步必须考虑事务。事务保证一组操作要么全部成功要么全部失败不会出现中间状态。ACID特性分别是原子性、一致性、隔离性、持久性。BEGIN TRAN; UPDATE accounts SET balance balance - 100 WHERE account_id 1; UPDATE accounts SET balance balance 100 WHERE account_id 2; -- 检查两行是否都更新成功 COMMIT; -- 如果中途出错执行 ROLLBACK;实操中有个重要提醒事务不要开得太大。一个事务里塞几千条UPDATE锁的资源和持续的时间都会成倍增长容易导致其他查询阻塞。合理的事务粒度应该控制在几百毫秒内能完成的范围。还有事务里如果出现报错要及时捕获并ROLLBACK否则连接一直挂着未提交的事务锁不释放数据库很快就被拖垮。5. 实用工具函数与字符串处理写SQL的日常武器5.1 空值处理COALESCE与NULLIF的组合用法NULL在SQL里无处不在处理NULL是基本功。COALESCE返回第一个非NULL值NULLIF则是让两个值相等时返回NULL。这两个函数搭配起来可以优雅地处理很多边界场景。-- 手机号为空时显示默认值 SELECT user_name, COALESCE(phone, 未填写) AS phone FROM users; -- 除以零保护分母为零时返回NULL SELECT amount, quantity, amount / NULLIF(quantity, 0) AS avg_price FROM orders;NULLIF(quantity, 0)的意思是当quantity 0时返回NULL这样除法结果就是NULL而不会报除零错误。这个技巧特别实用做财务分析或者报表统计时除零的坑几乎人人都会遇到。5.2 字符串函数截取、拼接、替换与大小写处理字符串处理是SQL里无法回避的内容。报表里经常要拼接字段搜索时要统一大小写。常用函数就是一套组合拳-- 拼接用户名和城市 SELECT user_name - city AS user_info -- SQL Server FROM users; SELECT CONCAT(user_name, - , city) AS user_info -- MySQL FROM users; -- 截取前三位 SELECT LEFT(phone, 3) AS area_code FROM users; -- 替换 SELECT REPLACE(content, 旧词, 新词) FROM articles; -- 大小写转换做不区分大小写的搜索时很好用 SELECT * FROM users WHERE LOWER(user_name) LOWER(ZhangSan);有一件事需要留心不同数据库对“拼接”的语法不一样。SQL Server用MySQL用CONCAT()函数PostgreSQL用||。写跨库兼容代码时最好统一用CONCAT()因为它能处理NULL传统的拼接一旦遇到NULL结果就变成NULL了。5.3 日期与时间函数格式转换和区间计算日期处理也是逃不开的命题。统计“今天”“最近7天”“上个月”的数据几乎每个报表都有。-- 查询最近7天的注册用户 SELECT * FROM users WHERE register_time DATEADD(DAY, -7, GETDATE()); -- SQL Server -- MySQL写法 SELECT * FROM users WHERE register_time DATE_SUB(NOW(), INTERVAL 7 DAY);日期虽不算难但坑在时区和格式。比如你以为存的是北京时间结果库里存的是UTC时间差8小时报表数据就开始飘了。我遇到过很多次线上排查最后发现是时区设置问题。特别提醒应用层传过来的时间字符串最好在SQL里统一转换成日期类型再比较不要直接拿字符串跟日期字段比。5.4 类型转换的隐式陷阱SQL里类型转换分显式和隐式两种。显式好办CAST和CONVERT随便选。麻烦的是隐式转换——数据库会在你无意识时自动把某列转成另一个类型去比较一旦转错了索引失效性能掉一大截。-- 显式转换可控 SELECT CAST(amount AS DECIMAL(10,2)) AS amount FROM orders; -- 隐式转换的坑避免这里用法 SELECT * FROM users WHERE user_id 100;如果user_id是整型而传入的是字符串100数据库可能会试图把整型列隐式转成字符串。在MySQL里这往往导致索引失效然后全表扫描。报错是没有的慢是真的慢。排查慢SQL时如果发现SQL逻辑没问题但就是慢优先检查有没有隐式类型转换。6. 索引、性能优化与维护让SQL飞起来的底层逻辑6.1 索引的本质与生效原则索引是数据库查询加速的核心机制它以空间换时间通过额外的数据结构通常是B树MySQL的InnoDB就是典型代表让查找由全表扫表变成对数级别的索引查找。但索引不是越多越好每多一个索引写操作的成本就高一分。索引生效的几个基本原则最左前缀原则复合索引(a, b, c)只有在查询条件里包含a时才可能生效跳过了第一个字段索引就是用不上的。范围条件之后索引失效比如WHERE a 100 AND b 5如果索引是(a, b)b的索引用不上。对索引列做函数运算索引会失效WHERE YEAR(register_time) 2024会导致全表扫描改成WHERE register_time 2024-01-01 AND register_time 2025-01-01才能命中索引。6.2 慢SQL排查的常用手段慢SQL是每张数据表数据量上来之后的必然遭遇。排查慢SQL的第一步是找到它在哪。MySQL里靠慢查询日志SQL Server里靠sys.dm_exec_query_stats这类动态管理视图。-- SQL Server: 查看耗时最高的查询 SELECT TOP 10 qs.total_elapsed_time / qs.execution_count AS avg_ms, qs.execution_count, qt.text FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt ORDER BY avg_ms DESC;找到慢SQL后常规步骤是先看执行计划找全表扫描、key lookup、RID lookup这些高成本操作。检查是否缺少索引或者索引顺序不对。检查统计信息是否过期数据量变化大时统计信息会失真。改写SQL比如把子查询改成JOIN把OR拆成UNION ALL把IN改成EXISTS。顺带提一句并不是加了索引就一定快索引的选择性和数据分布决定效果。比如“性别”这种只有两三个值的列建索引很难带来质的提升因为选择性太差了。真正值得加索引的是“用户ID”“订单号”“手机号”这类高区分度的列。6.3 参数化查询与防SQL注入的底线写到SQL优化就不得不提SQL注入。这本来是个安全话题但跟日常SQL写法脱离不开。SQL注入的本质是应用程序把用户输入直接拼接到SQL里改变了SQL的语义。经典的注入案例就是用 or 11 --这类输入去绕过登录验证。原理不用展开反正很危险。核心防御手段就一条所有SQL操作都用参数化查询PreparedStatement/参数绑定永远不要拼接用户输入。在应用层写SQL时Java的PreparedStatement、Python的?占位符、Node.js的$1参数这些都是在传输层就把数据和SQL结构分离开数据库端先编译再传参注入无从下手。# Python示例安全写法 cursor.execute( SELECT * FROM users WHERE user_id %s, (user_input,) )如果你还在写这样的代码# 绝对不要这样写 cursor.execute(fSELECT * FROM users WHERE user_id {user_input})趁早改掉这是最危险的习惯之一。哪怕你自己觉得“不会有恶意用户”也防不住扫描器和自动化脚本它们能把你的系统反复犁一遍。6.4 常见维护操作更新统计信息、重建索引数据库运行久了索引会碎片化统计信息会失真。这时候SQL写得再好也快不起来。常规维护就是定期更新统计信息和重建索引。-- SQL Server: 更新统计信息 UPDATE STATISTICS users; -- 重建索引索引碎片严重时 ALTER INDEX idx_users_user_name ON users REBUILD; -- MySQL: 重建表优化 OPTIMIZE TABLE users;做个类比索引相当于书的目录。目录乱了查书自然慢统计信息失真相当于目录标注的页码不准确查询优化器就会选择错误的执行计划。把这些维护当成日常值班任务来做别等线上报警才开始看数据库健康度。7. 客户端工具与部署环境的实操经验7.1 SQL Server的安装与SSMS版本兼容SQL Server的安装一直是热搜里的高频问题尤其是刚接触数据库的同学第一步就被安装卡住了。SQL Server的版本选择、SSMSSQL Server Management Studio连接失败这些基本都是“第一次用必然遇到”的问题。SSMS 18.x 和 SQL Server 2008 R2 共存的问题我直接给结论SSMS 18 以上版本的连接机制跟老版本兼容性尚可但也遇到过证书链验证报错尤其是 SQL Server 2008 默认只支持老版 TLS 协议而新版 SSMS 默认强制启用 SSL 校验这就会报“证书链是由不受信任的颁发机构颁发的”一类错误。解决办法有两个方向一是给SQL Server配置有效的SSL证书二是在连接字符串里加上TrustServerCertificateTrue注意这是测试环境的做法生产环境不要这样绕过证书校验风险太大。安装SQL Server 2022时常见的“对秘钥无访问权限”这个报错通常跟实例目录权限有关。解决思路是用管理员权限运行安装程序或者手动给SQL Server的数据目录添加Network Service账户的读写权限。这类问题不算疑难杂症但第一次遇到确实很劝退。7.2 数据库备份与还原跨版本兼容的坑热搜里问到“SQL Server 2012的数据库备份2008能用吗”答案基本是否定的。数据库备份只能恢复到同版本或更高版本的实例上低版本无法直接还原高版本的备份文件。这在跨版本升级时是个大坑。如果确实需要从高版本导入低版本常规思路有几种用高版本生成脚本Generate Scripts把Schema和数据脚本化然后去低版本执行。用第三方工具做数据同步。如果只是部分表可以用导出导入向导做数据迁移。这类操作最怕的是半途而废。我建议在做任何跨版本迁移之前先列一个清单目标实例版本、兼容级别、字符集排序规则、所有对象类型表、视图、存储过程、触发器、作业这些缺一个迁移后都可能出问题。7.3 连接字符串与SSL报错的排查思路“驱动程序无法通过使用安全套接字层(SSL)加密与SQL Server建立安全连接”这个报错现在真的太常见了因为新版ODBC驱动如ODBC Driver 18 for SQL Server默认开启加密连接。报错里经常带着“证书链是由不受信任的颁发机构颁发的”。排查步骤确认目标SQL Server是否配置了证书。没配置时驱动会尝试用自签名证书但客户端不信任它。如果确认是本机或测试环境可以在连接字符串里加TrustServerCertificateTrue来跳过证书链校验。如果生产环境别用绕过方式老老实实给SQL Server装一个受信任的CA签发的证书并在连接字符串里指定EncryptTrue。这类报错本身不难解决难的是判断“什么时候可以绕过什么时候不能”。我的原则是仅限测试验证用生产环境必须解决证书配置问题。7.4 数据库内存占用异常的排查SQL Server有个经典现象内存占用越来越高看起来像是内存泄漏。其实不是泄漏而是SQL Server默认会尽量占用可用内存做缓冲池不让操作系统回收它。这在数据库运维里有个术语叫“懒回收”。网上有不少人说SQL Server占内存太多需要限制最大内存。这个操作是有的但要用对地方-- 查看当前最大内存配置 SELECT name, value_in_use FROM sys.configurations WHERE name LIKE %max server memory%; -- 调整最大服务器内存单位MB EXEC sp_configure max server memory, 8192; RECONFIGURE;但这个调整别凭感觉来。如果服务器有32GB内存就给SQL Server分8GB剩下的全给操作系统这其实没必要。正确做法是看数据库实际缓冲池需要多少留一些内存给OS做文件缓存就行通常预留10%-20%给系统。调整完还要观察一段时间的页面预期寿命Page Life Expectancy这类关键指标如果低于300秒说明内存不够用得再调高。8. 写在最后的几句实在话SQL是个熟练工写得多了自然顺。上面这些语句大部分我都在生产环境反复用过每一段踩过的坑也都标出来了。坦白说基础语句本身不难背难的是理解每种写法背后的执行逻辑和边界条件。我的建议很简单工作中遇到新的SQL用法不要只复制粘贴停下来想一想这条语句的执行顺序是什么、如果数据量放大100倍会不会还撑得住、有没有更优的写法。慢SQL优化报告里出现的每一个案例都是别人踩过坑之后总结出来的经验值得多看两遍。这个大全只是一个起点。SQL的进阶方向还很多比如查询计划的分析、索引的底层结构、事务隔离级别的细节、分区表的设计甚至存储过程和触发器这类高级对象。不过基础打好了这些东西学起来都不会太吃力。希望这份梳理对你有实际帮助。如果哪块想深入聊评论区见。
网站建设高端定制企业官网