SQL Server迁移实战:T-SQL批处理兼容性排查与改造指南
接手这个项目的时候,我第一反应不是数据量有多大,也不是停机窗口够不够,而是那几千个存量 T-SQL 批处理脚本能不能在新环境里“原样跑起来、跑得和以前一样快”。这次迁移的目标实例版本标识是 V9R4C019,对应 SQL Server 2019 这条产品线上的一个内部构建基线,项目里所有兼容性策略都围绕这个版本对齐。说实话,数据库迁移里数据本身反而好办,备份、还原、同步、校验有一套成熟流程,真正让人睡不着觉的,永远是那些散落在生产库里的存储过程、作业步骤、定时任务,以及被 BAT 脚本包装起来的 SQLCMD 批处理——它们才是业务的心脏,一旦跳动异常,整个业务系统都会跟着出问题。
这篇文章就是这次迁移实战的完整复盘。我会从迁移前的资产盘点讲起,把老批处理最容易踩的兼容性差异、版本行为变化、外部脚本改造、回归验证方案一个个拆开,最后把我在现场遇到的高频问题整理成一份排查手册。无论你是正在准备 SQL Server 跨版本升级,还是接手了类似 V9R4C019 这种带内部版本标识的迁移项目,这份经验应该能帮你少走不少弯路。
1. 迁移前先别急着动手:给存量 T-SQL 批处理做一次全面体检
很多团队做数据库迁移,第一反应是准备新服务器、测网络带宽、估数据拷贝时间,结果等数据过去了才发现,新环境里老存储过程报错、作业全部失败、批处理跑出来的结果和旧环境不一致。所以说,迁移第一步不是搬数据,而是先把存量 T-SQL 批处理的家底摸清楚。
1.1 先搞清“批处理”到底包含哪些东西
日常说的“批处理”,范围比很多人想象的要宽。这次项目里我按来源把所有 T-SQL 批处理分成五类,每一类都要单独收集和评估:
- 数据库内部对象:存储过程、函数、触发器、视图中的 T-SQL 代码,这些是最核心的批处理资产,数量和复杂度都最高。
- SQL Agent 作业:包括作业步骤里的 T-SQL 命令、维护计划、操作员通知逻辑,很多隐性批处理藏在这里。
- 外部脚本文件:.sql 文件、.bat/.cmd 写 SQLCMD 调用的批处理、PowerShell 脚本、SSIS 包里的执行 SQL 任务。
- 应用层 SQL:虽然不在数据库里,但应用连接串、ORM 映射、报表系统里硬编码的 SQL 片段,也属于需要接住的“存量批处理”。
- 数据库层面的隐式批处理:比如复制代理、Service Broker 队列触发的存储过程。
我常用的盘点脚本长这样,直接把当前库所有模块对象的定义抓出来,统计行数和代码量,先建立一个“批处理资产清单”:
注意千万别只统计数量,我看过不少团队建了个 Excel 表,写了“存储过程 358 个、视图 126 个”,然后就觉得工作量可控。实际上真正的复杂度在代码长度和对象关联度上——358 个存储过程里如果有 200 个超过 200 行,还互相嵌套调用,那和只有 20 个复杂过程的库压根不是一个量级。
1.2 V9R4C019 版本基线的理解与对齐
V9R4C019 这个版本标识,从形式上看不是标准的 SQL Server 发布版本号(标准的一般是 15.0.xxxx 类似格式),更像厂商产品线或者内部构建系统的版本命名。但不管叫什么,迁移前最重要的一件事就是和团队、供应商确认清楚它对应的 SQL Server 主版本、累积更新级别,以及“兼容性级别”目标。
原因很简单:T-SQL 的行为不仅受数据库引擎主版本影响,还受数据库的 COMPATIBILITY_LEVEL(数据库兼容级别)直接影响。SQL Server 2019 默认新建数据库是 150,SQL Server 2017 是 140,2016 是 130,2012 是 110,2008 是 100。老库迁到新环境,如果不主动改兼容性级别,数据库会保持在原来的级别,这样大多数语法能继续用,但新功能也用不了;反过来,如果把兼容性级别直接拉到新版,那么期不兼容行为会立刻冒出来。
这次项目里我们定的策略,我先写到前面供参考:
| 阶段 | 兼容性级别 | 目标 |
|---|---|---|
| 数据搬迁期 | 保持原版本级别(例如 100/110) | 先把数据和服务跑起来,降低迁移风险 |
| 批处理改造期 | 分库逐个提升到 130 | 替换废弃语法,解决排序规则、隐式转换问题 |
| 稳定验证期 | 整体提升到 150 | 启用新特性,观察执行计划和性能变化 |
| 长期运行期 | 维持 150,按需开启 4199/9481 等追踪标志 | 在性能和新特性之间找平衡点 |
这个分层处理的核心思路是“不要一步到位”。如果你在迁移当天就把所有库切到最高兼容性级别,等于把所有兼容性问题集中引爆,出了问题你连定位都来不及。
1.3 体检的三个手段:官方评估、DMV扫描、静态代码分析
摸清家底之后,要做的就是兼容性体检。我这次用了三条线并行,效果比单一工具强得多:
第一,用微软官方的 Data Migration Assistant(DMA)。它有个专门针对 SQL Server 升级的评估模式,能扫出“破坏性变更”和“行为变更”两类问题。比如它会提示哪些存储过程用了老语法、哪些功能在当前版本不受支持,还会针对每个问题给出官方的说明链接和解决方案建议。这个工具适合跑第一遍,先把明显的问题找出来。
第二,用 DMV 和默认追踪做线索收集。比如通过 sys.dm_exec_query_stats 和 sys.dm_exec_sql_text 找出运行次数最多的批处理,优先评估这些最活跃的过程;也可以通过扩展事件(Extended Events)抓一段时间的批处理记录,看实际生产环境在跑哪些代码。注意,这时候抓到的只是“运行过”的代码,还有很多过程是月批、季批,甚至年底才跑一次的,光靠短时间抓取会漏掉一大堆。
第三,也是最容易被忽略的——静态代码特征扫描。我写了一个简单的关键词扫描脚本,把模块定义里凡是出现老式语法、废弃系统表、不再支持的函数等特征的行全部筛出来,人工逐条确认。后面 2.1 小节我会列出这次扫描中命中率最高的关键词清单,可以当模板直接用。
体检结束后,你会得到一份问题清单。别急着改,先按“阻断性问题”“行为差异问题”“性能回退风险”三个等级分类,阻断性问题必须在上线前改完,行为差异问题要逐条和业务方确认预期结果,性能回退风险则放进回归测试重点验证。
2. 兼容性差异拆解:老批处理在新环境里“翻车”的典型场景
这一节是整篇文章的核心。我从这次项目中挑出真实命中的六类兼容性差异,每一类都对应至少一个生产环境报错或数据异常。提前说明,下面这些场景不是新版本“故意不兼容”,更多是以前的行为本身就不合理,新版本把它纠正了,但存量系统早就依赖了不合理行为,纠正确实会出问题。
2.1 语法和系统对象:已经废弃的用法
T-SQL 里有一批语法在早期版本能用,但在 SQL Server 2019(V9R4C019 基线)上已经彻底移除或不再支持。我静态扫描时用的关键词列表如下,你可以在自己项目里直接用:
*=和=*外连接语法:这是老程序员从 Sybase 时代带过来的写法,2008 之后就不支持了,必须改成 LEFT JOIN / RIGHT JOIN。WRITETEXT和READTEXT:用于操作 TEXT / NTEXT / IMAGE 字段的老式文本指针操作,这些数据类型本身已被废弃,改成 VARCHAR(MAX)/NVARCHAR(MAX) 加常规 UPDATE 写法。DUMP和LOAD:老式备份还原命令,新版本只支持 BACKUP 和 RESTORE。RAISERROR的旧式格式:RAISERROR 50001 'some message'这种老写法在新版本里虽然还能用但强烈不推荐,建议统一改成带逗号参数的新语法RAISERROR(N'some message', 16, 1)或THROW。sp_dbcmptlevel:老式修改兼容性级别的存储过程,文档里已经标记为不推荐,应用ALTER DATABASE SET COMPATIBILITY_LEVEL替代。sysprocesses等系统表直接查询:虽然是兼容视图,但新版本里更推荐sys.dm_exec_sessions、sys.dm_exec_requests等 DMV,因为很多扩展信息在兼容视图里拿不到。
扫描脚本可以这样写:
注意,这个扫描结果只能作为“待确认清单”,绝对不能直接当问题清单。因为 LIKE 会命中注释内容、字符串常量里的文本,比如某个过程在代码注释里写了“-- fixed in WRITETEXT version”,扫描会误报。我这次统计出来 47 个疑似命中项,人工确认后真正有问题的只有 3 个,命中率不算高但能保证不遗漏。
2.2 数据类型和隐式转换行为变化
批处理里最常见的隐藏杀手是数据类型隐式转换。老版本里有很多“宽容”行为,到了新版本直接变报错或者结果不一致。
举一个最典型的案例:把空字符串 '' 转换成 DATETIME。在 SQL Server 2019 之前,很多过程喜欢用 WHERE create_time > '' 或者 CAST('' AS DATETIME),老引擎会默默把空串当成 1900-01-01 00:00:00,不会报错。但在 SQL Server 2019 里,这种转换直接抛错:“Conversion failed when converting date and/or time from character string”。原因是从 2019 开始,日期时间类型的字符串转换规则更严格了,空串不再被隐式接受。如果你库里有一堆兼容 LEVEL 还停在 110/120 的老过程,这种问题可能还不暴露,一旦升到 150 就全部炸掉。
处理方式很简单,也建议写进迁移规范:
- 在全库范围内搜索
CAST('' AS datetime)、CONVERT(datetime, '')、@date_var = ''这类模式,不要手工去猜。 - 统一改成显式默认值
CAST('1900-01-01' AS DATETIME)或直接改用DATETIME2类型。 - 对过程里的变量赋值做一次代码审查,凡是日期变量接收空串的都改为 SET @date_var = NULL 或默认日期。
再有一个容易踩的数据类型坑是 TEXT / NTEXT / IMAGE。很多老批处理用 TEXTPTR() 配合 WRITETEXT 做局部更新,或者对 TEXT 字段执行 SUBSTRING,这些在新版本里要么报错要么返回意外结果。我的建议是迁移时顺手把这些字段改成 VARCHAR(MAX)/NVARCHAR(MAX)/VARBINARY(MAX),因为 MAX 类型支持所有常规字符串函数,行为也和普通字符串完全一致,省去一堆兼容性问题。
2.3 排序规则冲突
排序规则(Collation)冲突是跨实例迁移最容易忽略的坑,尤其是两个库的默认排序规则不一样,或者一个库里的表列排序规则被单独改过,临时表 JOIN 时就会报错。最常见报错是:
这种问题在新版本里更严重,因为 SQL Server 2019 引入了一个 UTF-8 排序规则家族,如果老库用的是 Chinese_PRC_CI_AS,新库在安装时选了 Chinese_PRC_CI_AS 或者默认的 SQL_Latin1_General_CP1_CI_AS,临时表连接、比较时很容易炸。
遇到这种问题,我先说查法,再说修法。定位方式是把带排序规则的列、库默认排序规则、临时表排序规则先查出来,然后用一条 COLLATE 子句解决一次性冲突:
如果项目时间紧,临时加 COLLATE 是快速规避手段,但长期迁移方案还是要统一到同一个排序规则。我的经验是:在目标环境建库之前,先确认全公司 SQL Server 实例的排序规则标准,能统一就统一,不能统一的至少把业务核心库统一掉。你不想在迁移后每个月都接到“为什么这个 JOIN 报错”的问题。
2.4 查询引擎行为变化:基数估算、并行与提示的坑
数据搬过去、语法也兼容,但批处理跑得慢,这是另一种“翻车”,而且最难排查。因为问题不在代码本身,而在优化器行为的变化。
SQL Server 的基数估算(Cardinality Estimation,CE)模型在 2014 年换过一次代,老模型叫 CE 70,新模型叫 CE 120/130/140/150。CE 70 的假设比较简单暴力,CE 140/150 的假设更贴近真实数据分布。问题在于,存量批处理很多是当年按照老的基数模型调优出来的,执行计划也依赖老的估算。迁移后即便兼容级别不变,只要实例全局启用了新 CE(比如你设置了 ALTER DATABASE SET COMPATIBILITY_LEVEL = 150),优化器会给过程生成全新的执行计划,可能比原来快,也可能比原来慢几倍。
这次项目里有个真实的例子:某个每日清算批处理,在源库跑 12 分钟,迁到新环境直接变 1 小时 20 分钟。排查下来发现是某张表的数据分布非常倾斜,老 CE 70 对它的估算“碰巧”选中了一个好索引,新 CE 150 的估算更“准确”反而走了一个坏的嵌套循环。解决办法不是回退 CE,而是给这条关键 SQL 加提示:
或者按数据库维度强制老 CE:
但这里要提醒一句:把整个数据库兼容级别降回 110,会丢失新版本所有特性,比如 STRING_AGG、TRIM、METADATA_VISIBILITY 改进等,属于“壮士断腕”方案。我在这类项目里更喜欢用 QUERYTRACEON 9481 配合特定的存储过程级别,比如用 sp_executesql 加 OPTION (QUERYTRACEON 9481) 只对最关键的几条 SQL 生效,而不是全局回退。
另外注意 QUERYTRACEON 在某些场景下需要开启“全局追踪标志”权限,生产环境不一定允许。所以更稳妥的办法还是逐条优化计划,用 FORCE ORDER、HASH JOIN、RECOMPILE 等常规提示去引导优化器,而不是依赖老 CE 凑数。
2.5 新增函数和语法:老脚本没见过的“新武器”
这个话题看起来和“兼容性问题”相反,但其实特别容易被忽略。很多存量批处理在旧版本里实现复杂逻辑,靠的是循环、游标、递归 CTE,代码又臭又长性能又差。迁移到新版本后,这些代码能跑,但不代表值得保留。
SQL Server 2017 开始支持 STRING_AGG,2016 开始有 STRING_SPLIT,2017 开始有 TRIM,2019 进一步增加了 STRING_SPLIT 的 ordinal 参数(见 2022,不过 V9R4C019 基线里部分函数可用)。我在这次改造里发现大量原来的自定义 Split 函数、循环拼串代码都可以用这些内置函数替换,性能能提升一个数量级。
举个例子,老批处理里常见的“把一个表的同一分组多条记录拼接成逗号分隔字符串”的活,最好也只是用 FOR XML PATH('') 实现,复杂且容易踩 XML 转义坑。新版本直接写:
这类改造不属于“必须做”,但既然迁移后要回归验证,顺手把热点 SQL 的重写机会利用起来,对后续维护是很大一笔收益。
2.6 权限和安全性变化:批处理跑不动的隐性原因
最后这个差异点特别容易被搜索到“sql server 2008不能删除数据库”“sql server windows nt占用内存”之类的热词,但实际想表达的是同一类问题:新版本把安全默认值收紧了很多。老库上很多批处理跑得通,是因为旧版本权限粗放、默认开启了很多不安全配置;换到 SQL Server 2019(V9R4C019)之后,默认值收紧了,原来不会报错的操作直接失败。
比如:
- 2017 起
CLR默认禁用,需要sp_configure 'clr enabled', 1显式开启。 - 2019 起
EXTERNAL_ACCESS程序集权限默认关闭。 - 2016 起加密数据库的 TDE 密钥管理更严格。
- 数据库的
TRUSTWORTHY默认 OFF,老批处理里如果依赖跨库模拟,可能会失败。
还有一个很容易踩的坑是 SQL Server 代理作业的历史记录清理和运行权限变了。老环境里 SQLAgentUserRole 就能干活,新环境代理角色权限拆分得更细,批量任务在非 sysadmin 账号下跑经常报错“无法执行步骤”。
我的建议是:在迁移准备阶段,就把生产账号权限矩阵列出来逐一验证,不要等到作业跑失败才发现账号在新实例上权限不够。降权是新版本的主流方向,迁移项目中“补权限”会是常态,提前规划能省下大量上线后的沟通成本。
3. 实操:把存量批处理整体搬到新环境(流程复现)
这部分把项目的实际操作流程从头到尾复现一遍。你可以直接按这个流程走,里面每个步骤都踩过一遍,能避开的坑我都提前标出来了。
3.1 脚本批量收集与版本管理
第一步是统一收集所有数据库对象脚本并纳入版本管理。我这次用的是 PowerShell 加 SMO 批量导出,把每个对象导出成独立 .sql 文件,然后提交到 Git。这样后续任何一条改动都有记录,对比、回滚都方便。
注意导出时指定 -Encoding utf8。如果导出文件编码不对,导入目标库时中文注释、中文字符串字面量可能变成乱码,这种问题非常难排查。
导出的脚本不只是“备份”,它会强制你建立一个规范目录。推荐目录结构:
09_ChangeLog 非常重要。每一个被改过的批处理脚本,都要在这里记录:改动人、改动时间、改动原因、上游依赖。这个项目到后期有五六个人同时在改脚本,没有 ChangeLog 根本没法协作。
3.2 BAT/CMD 调用 SQLCMD 的外部批处理改造
这次项目里,存量外部批处理的数量远超预期。很多老系统不光数据库里存存储过程,还有一堆部署在 Windows 服务器上的 BAT/CMD 脚本,每天定时执行,脚本逻辑是先用 osql 或 sqlcmd 连库,把一批 SQL 文件依次执行,再根据结果生成日志或发邮件。这些脚本在旧环境里跑得好好的,换到新环境就有一堆问题。
先说版本工具切换。osql 这个老客户端从 SQL Server 2012 以后就不推荐使用了,虽然还带着,但很多参数行为已经变得很奇怪,比如错误信息格式变了、兼容性差。迁移时建议把所有 osql 调用统一改成 sqlcmd。
这里给一个典型改造前后的 BAT 脚本对比:
改造前:
改造后:
关键的增强是加了 -b 参数。这个参数让 sqlcmd 在 SQL 语句执行出错时返回非零错误码,BAT 脚本才能正确捕获失败状态。很多老脚本没加这个参数,导致 SQL 报错了,BAT 脚本还“傻乎乎地”返回成功,作业状态永远显示成功,问题被无声吞掉。这是迁移批处理时最值得改的一个细节。
另外注意 sqlcmd 的路径不同版本不一样,建议在脚本开头统一用 %ProgramFiles% 环境变量动态定位,避免不同服务器上路径不一致:
3.3 兼容性级别的选择与强制行为
数据库迁到新实例后,兼容性级别是决定 T-SQL 行为的最关键开关。千万不要“拍脑袋”直接调到 150。我的建议是:
第一,先把所有库保持原级别,让批处理在新环境里能用原行为跑起来,排除基础设施问题。 第二,逐库分析升级影响。具体做法是,挑一个低峰时段,把某个库的兼容级别临时改成 150,然后跑一批核心批处理,对比结果和耗时,再跑一遍问题扫描工具,看有没有新增报错。确认没问题后再改下一个库。 第三,上线切换时,优先切换风险低的库,把高风险的大库留到最后。
实际项目中经常遇到的情况是:某个老库的代码已经写得“烂到根”,升到 150 肯定一堆问题,业务方却要求必须在特定日期前迁完。这时候我一般建议妥协方案:兼容性级别先保持在 110,同时把改造计划排进后续迭代,按优先级逐条推进,而不是在上线当天强行同步完成。迁移的数据风险是可控的,把 T-SQL 行为变化一次性全引进来反而不可控。
3.4 回归验证:输入输出对比方案
批处理改造完,必须做输入输出对比验证,否则无法证明“行为一致”。这次项目里我用了三层验证法:
第一层:静态对比。把源库和测试库的存储过程定义做逐字对比,除了预期改动之外,不允许出现额外差异。用 Git diff 或对比工具在脚本层面把关,这是最便宜也最快的一层。
第二层:动态跑批对比。准备一组典型输入数据(业务方提供),分别在源库和测试库执行同一个批处理,对比输出结果是否一致。注意,对输出结果不能只看“是否报错”,要精确对比结果集。我写过一个简单的 PowerShell 脚本,把两个库的查询结果都导出成 CSV,然后用文件哈希比较,发现不一致再定位:
第三层:真实场景回放。把源库的 SQL Profiler 或扩展事件捕获的批处理流量,在测试库重放一轮,关注有没有新增错误、超时、死锁、资源争用。这一步最接近生产环境,能提前暴露并发和锁相关的问题。
我这次项目里最终统计,三层验证下来接近一半的批处理需要至少一次修正,主要问题集中在排序规则、空字符串转日期、老语法三块。如果没有回归验证直接上线,后果不堪设想。
4. 常见问题与排查技巧实录:一次迁移的踩坑笔记
迁移过程中找问题的时间往往比解决问题的时间长。这里我把这次项目里遇到的高频问题整理成一份速查手册,每一条都带排查思路和解决命令,你遇到类似情况可以直接照着做。
4.1 命名管道打不开:ODBC Driver 18 的默认加密策略
迁移后第一天登录测试库,开发团队反馈一批老 BAT 脚本连不上新实例,报错信息非常典型:
这个报错看着吓人,其实原因不复杂:新版 ODBC Driver 18 默认开启了强制加密(Encrypt=MANDATORY),所有客户端连接都必须走 TLS 加密。而很多老 BAT 脚本连接串里根本没写加密选项,或者目标 SQL Server 实例本身没在服务端开启加密,两者一组合,连接直接被拒绝。
解决方式有三种,按推荐优先级排列:
- 在连接字符串里加
Encrypt=Optional;或TrustServerCertificate=Yes;,允许客户端在服务端未配置证书时降级。 - 如果脚本是
sqlcmd调用,加-C参数(信任服务器证书)。 - 在内网且安全要求允许的前提下,直接修改 ODBC 驱动默认设置,把加密改为 Optional。
改完连接串之后记得让 BAT 脚本输出错误日志重新跑一遍,确认不是别的权限问题。
4.2 跑批突然变慢:CE 模型和统计信息的锅
这是本场最难查的问题,也是最典型的行为变更。现象是:同一套存储过程,同样的数据量,旧库跑 12 分钟,新库跑了 1 小时 20 分钟,数据结果完全一样。我排查时先看了执行计划,发现两边的计划完全不一样,新计划里有一个关键表走了嵌套循环,预估行数只有 100 行,实际行数有 12 万行。
追根溯源是:
- 新库升级到 2019 后,兼容性级别被改到 150,优化器从老 CE 70 换成新 CE 150。
- 表上的统计信息在数据搬迁后没有立即更新,采样数据过旧。
- 老批处理里有个“魔数”常量(比如硬编码日期),导致参数嗅探后在新的统计信息下生成了糟糕计划。
解决步骤我建议按顺序来:
- 第一时间更新相关表的统计信息:
UPDATE STATISTICS dbo.Orders WITH FULLSCAN; - 给关键存储过程加
WITH RECOMPILE观察是否恢复正常。 - 如果还慢,对比新旧执行计划,看是不是 CE 模型的差异。可以先用
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'))验证判断是否正确,确认后再做长期优化。
这个案例给我最大的教训是:数据搬迁后,更新统计信息必须是常规操作,不能只在建完索引后做一次。
4.3 空字符串转日期失败:2019 严格模式的冲击
几个每天都跑的批处理在迁移后同时报错,错误信息是:
查代码后发现,批处理里有大量 WHERE update_time > '' 这类写法。2008/2012 时代默默把空串当 1900-01-01 处理,2019 直接报错。
这个问题的修复方案分三步:
- 全局搜索所有类似
> ''、< ''、CAST('' AS datetime)的代码,手段就是 2.1 的静态扫描脚本,把 date 类型变量和空串比较的都列出来。 - 和业务方确认空串的语义:是“无时间限制”,还是“取默认日期”。不同业务理解不同,不能替业务方做决定。
- 统一改为显式日期,比如:
这个问题的本质是“隐蔽的隐式转换”。建议项目里定一条红线:所有字符串到日期时间的转换必须显式 CAST 或 CONVERT,杜绝隐式行为。
4.4 排序规则差异导致的 JOIN 失败
迁移后某报表批处理执行时报错:
查下来是源库某个表列被设计成了 SQL_Latin1_General_CP1_CI_AS,目标库新库默认排序规则是 Chinese_PRC_CI_AS。老环境里都是同一套排序规则,临时表连接没事,新环境混用就报错了。
快速解决是用 COLLATE 强制统一,但长期要看要不要重建表。我的建议是:如果这个表是业务核心表,而且列上排序规则的差异纯粹是历史原因,就趁机改成目标库统一规则;如果只是边缘表,则用 COLLATE 临时规避。排序规则这种东西,统一得越早,后续维护成本越低。
4.5 批处理并发和锁行为变化:排队比执行还久
还有一个常见现象是:迁移后单条批处理跑得不慢,但多个批处理同时跑时排队时间很长。这通常是两个因素叠加:
- 老实例并发设置比较宽松,新实例默认
MAXDOP或者cost threshold for parallelism设置不同。 - 迁移后没有开启
READ_COMMITTED_SNAPSHOT,导致读和写互相阻塞。
排查时用这个 DMV 查当前阻塞情况:
如果发现大量等待类型是 LCK_M_*,大概率是锁竞争。解决方案是在目标库开启 READ_COMMITTED_SNAPSHOT(RCSI),这样读操作不走共享锁,写和读不再互相阻塞:
注意,开启 RCSI 会增加 tempdb 的压力,建议顺便检查 tempdb 文件数量和大小,避免迁移后 tempdb 变成新瓶颈。还有,不是所有业务都适合 RCSI,尤其是有大量“读己之写”需求的批处理,改之前先和业务方确认行为是否符合预期。
5. 热词背后的真实需求:那些搜“SQL Server迁移”的人到底在找什么
读者可能在搜索引擎里输入“SQL Server 数据库迁移”“V9R4C019”“T-SQL 批处理”这些关键词找到这篇文章。我这一节顺着真实搜索意图,把散落在各种热词里的高频问题拉出来集中回答一遍,省得你再去翻一堆资料。
5.1 “SQL Server 2008 不能删除数据库”的迁移真相
很多人搜“sql server 2008不能删除数据库”,其实是在从 2008 往新版本迁移时遇到了阻碍:想删掉老库释放空间,但一直提示“数据库正在使用”,或者通过界面操作删除一直转圈。
我的经验是:删除前先查有没有活动连接,强制断开所有连接再删:
这里有一个容易忽略的点:如果数据库参与了 AlwaysOn 可用性组,或者有数据库镜像关系,直接从主副本删除会失败。要先从可用性组里移除再删。迁移项目里经常有这个坑。
5.2 “SQL Server 时间函数”和“正则表达式”在批处理中的常见用法
热词里有“sql server 时间函数”和“sql server 正则表达式”,说明很多人的批处理代码是在和时间、字符串匹配打交道。这次项目里我也遇到不少这类代码,顺便总结几句。
时间函数方面,老批处理爱用 GETDATE()、DATEADD、DATEDIFF,这些在迁移后基本不变。但要留意 SYSDATETIME() 比 GETDATE() 精度高(DATETIME2 级别),如果代码里混用了两种函数,可能在时间边界产生微秒级差异。另外 FORMAT() 函数虽然是 2012 年引入,但在大数据量批处理里性能非常差,我见过用它格式化千行日期就把批处理拖慢好几倍的,迁移时如果见到 FORMAT() 建议评估改回 CONVERT 加格式码。
正则表达式上,T-SQL 本身没有内置正则函数,老代码一般用 LIKE 和 PATINDEX 处理匹配需求。新版本的公共语言运行时(CLR)集成可以做正则,但项目里我不推荐为了一个匹配需求开 CLR,成本高、安全风险也高。更好用的方案是使用 SQL Server 2016 引入的 STRING_SPLIT 和 STRING_AGG,它们解决的是拆/合字符串的痛点,和正则场景有一半重叠,大大减少批处理里自定义字符串函数的数量。
5.3 “SQL Server 查询如何优化”:迁移后性能回退的预防性检查清单
不少人是迁移后发现查询慢,才开始搜“sql server 查询如何优化”。其实迁移过程中的性能优化应该前置,建议对照这份清单逐项自查:
- 是否更新了全部统计信息?迁移后第一次跑批必须
UPDATE STATISTICS。 - 是否重建了索引?碎片率高的表在搬迁后重建聚簇索引可以改善顺序 IO。
- 是否检查了缺失索引 DMV?
sys.dm_db_missing_index_details会告诉你批处理里哪几条查询缺索引。 - 是否评估过
MAXDOP和cost threshold for parallelism?服务器 CPU 核数变了,并行策略也要调整。 - 是否监控了 tempdb 争用?开启 RCSI 或者大量排序会放大 tempdb 压力。
- 是否检查了查询计划缓存里的“坏计划”?迁移后清一次计划缓存,让过程在新统计信息下重新编译,比踩坑后再清效率高得多。
5.4 安装部署阶段的版本选择:2019 还是 2022
最后说一个大家在搜索“sql server 2019 安装教程”“sql server 2022 下载”时经常纠结的问题:找资料时发现网上一堆 2019 和 2022 的教程,到底该选哪个?我的建议是看项目的新特性诉求和运维团队熟悉度。如果现有运维体系和监控工具都是基于 2019 打磨的,V9R4C019 这类内部基线又在 2019 上验证过,就不要轻易跳到 2022。版本越新、特性越多,但迁移兼容性问题也会越多。对于存量 T-SQL 批处理很重的系统,“稳定高于一切”。
这个结论不是我保守。见过太多团队为了“上新版本”强行升级,结果新版本改了某个默认行为,业务批处理数据错乱,最后花几周去回滚。迁移的本质是风险控制,版本的选择应该由现有资产结构决定,而不是由热度决定。
在这次的 V9R4C019 迁移项目里,我最后给出的一条建议,是想办法沉淀一套“批处理兼容性回归基线”。具体做法是把所有核心 T-SQL 批处理、对应的输入样例、期望输出结果、执行耗时上限整理成一套可重复执行的测试套件。每次修改数据库配置、升级补丁、调整兼容性级别,先在这套基线上跑一遍。这次迁移如果说有什么值得长期保留下来的资产,不是那一堆改好的脚本,而是这套能持续验证行为一致性的测试基线。数据库迁移从来不是“搬完数据就算结束”,新环境上线后的第一个季度才是真正的考验期。把验证做扎实,后面才能睡得着觉。