nvarchar(4000) 是隐形陷阱,因未显式指定长度时默认截断超长输入且不报错;必须显式声明 nvarchar(max)、客户端参数设 size=-1,并验证 datalength 与源头长度一致。

超过 4000 字符的 NVARCHAR 输入,在 SQL Server 存储过程中默认会被静默截断,不是报错,而是悄悄丢掉后面的内容——这是最危险的“没出错却错了”。
为什么 NVARCHAR(4000) 是个隐形陷阱
SQL Server 对变量和参数的 NVARCHAR 类型,若未显式指定长度,默认按 NVARCHAR(4000) 处理(NVARCHAR(MAX) 才能存超长内容)。哪怕你传入的是 8000 字符的 JSON 或 XML,只要声明为 @json NVARCHAR(4000),后半截就没了,且不警告、不报错。
- 函数如
LEN()、DATALENGTH()返回的都是截断后的长度,容易误判“输入完整” -
STRING_SPLIT()对被截断的字符串拆分,结果自然残缺 - 用
CONCAT()或动态 SQL 拼接时,缺失部分会导致逻辑错乱或语法错误
怎么声明才真正安全
所有可能接收长文本的参数,必须显式写成 NVARCHAR(MAX),不能省略 (MAX)。
- 错误写法:
@content NVARCHAR(等价于NVARCHAR(1))、@data NVARCHAR(4000) - 正确写法:
@content NVARCHAR(MAX)、@xml_data XML(XML 类型原生支持大内容) - 注意:
MAX不影响性能——SQL Server 内部对短值仍用 in-row 存储,只在超 8000 字节时转 LOB
调用方传参也要匹配长度
即使存储过程里声明了 NVARCHAR(MAX),如果应用层(如 C# 的 SqlParameter)没设 Size = -1,SQL Server 仍会按客户端声明的长度截断。
- C# 中必须:
param.Size = -1;(对应NVARCHAR(MAX)) - Python + pyodbc:用
sql_type=pyodbc.SQL_WVARCHAR并确保size未硬编码为小值 - SSMS 测试时,直接 EXEC 调用没问题;但用
DECLARE @x NVARCHAR(4000); SET @x = '...'; EXEC p @x就会复现截断问题
如何验证是否真被截断
别只信 LEN() ——它对尾部空格不敏感,且无法反映截断。用 DATALENGTH() 加日志对比更可靠:
IF DATALENGTH(@input)
- 在存储过程开头加这句,配合调用方传入的原始长度做比对
- 对关键字段(如 Base64 图片、JSON 配置),可加校验:检查是否以
}或]结尾,或用ISJSON(@input) = 0快速发现损坏 - 避免用
PRINT查看长字符串——SSMS 默认只显示前 4000 字符,误导性极强
最常被忽略的一点:表字段定义也得同步。如果参数来自 SELECT @x = long_col FROM t,而 long_col 是 NVARCHAR(4000),那源头就已截断——参数声明再正确也没用。











