SQL Server以静默方式截断存储过程中的varchar

Jez*_*Jez 67 sql sql-server stored-procedures truncate truncation

根据这个论坛的讨论,SQL Server(我使用2005但我收集这个也适用于2000和2008)默认将varchar你指定的任何s指定为varchar的长度,即使直接插入该字符串也是如此.INSERT实际上会导致错误.例如.如果我创建此表:

CREATE TABLE testTable(
    [testStringField] [nvarchar](5) NOT NULL
)
Run Code Online (Sandbox Code Playgroud)

然后当我执行以下内容时:

INSERT INTO testTable(testStringField) VALUES(N'string which is too long')
Run Code Online (Sandbox Code Playgroud)

我收到一个错误:

String or binary data would be truncated.
The statement has been terminated.
Run Code Online (Sandbox Code Playgroud)

大.保留了数据完整性,并且调用者知道它.现在让我们定义一个存储过程来插入:

CREATE PROCEDURE spTestTableInsert
    @testStringField [nvarchar](5)
AS
    INSERT INTO testTable(testStringField) VALUES(@testStringField)
GO
Run Code Online (Sandbox Code Playgroud)

并执行它:

EXEC spTestTableInsert @testStringField = N'string which is too long'
Run Code Online (Sandbox Code Playgroud)

没有错误,1排受影响.将一行插入表中,testStringField格式为'strin'.SQL Server以静默方式截断存储过程的varchar参数.

现在,这种行为有时可能很方便,但我认为没有办法把它关掉.这非常烦人,因为如果我将一个字符串传递给存储过程,我希望错误.似乎有两种方法可以解决这个问题.

首先,将存储过程的@testStringField参数声明为大小为6,并检查其长度是否超过5.这看起来像是一个黑客攻击并涉及大量的样板代码.

其次,只需声明所有存储过程varchar参数varchar(max),然后让INSERT存储过程中的语句失败.

后者似乎工作正常,所以我的问题是:varchar(max)在SQL Server存储过程中使用ALWAYS作为字符串是一个好主意,如果我真的希望存储过程在传递太长时间时失败?它甚至可能是最好的做法吗?无法禁用的无声截断对我来说似乎很愚蠢.

gbn*_*gbn 29

就是.

我从来没有注意到一个问题,因为我的一个检查是确保我的参数匹配我的表列长度.在客户端代码中也是如此.就个人而言,我希望SQL永远不会看到太长的数据.如果我确实看到了截断的数据,那么导致它的原因显而易见.

如果您确实需要varchar(max),请注意因数据类型优先级而导致的大量性能问题.varchar(max)的优先级高于varchar(n)(最长的是最高的).因此,在这种类型的查询中,您将获得扫描而不是搜索,并且每个varchar(100)值都是CAST到varchar(max)

UPDATE ...WHERE varchar100column = @varcharmaxvalue
Run Code Online (Sandbox Code Playgroud)

编辑:

有关此问题的打开Microsoft Connect项目.

而且它可能值得包含在Erland Sommarkog的Strict设置中(以及匹配的Connect项目).

编辑2,在马丁斯评论之后:

DECLARE @sql VARCHAR(MAX), @nsql nVARCHAR(MAX);
SELECT @sql = 'B', @nsql = 'B'; 
SELECT 
   LEN(@sql), 
   LEN(@nsql), 
   DATALENGTH(@sql), 
   DATALENGTH(@nsql)
;

DECLARE @t table(c varchar(8000));
INSERT INTO @t values (replicate('A', 7500));

SELECT LEN(c) from @t;
SELECT 
   LEN(@sql + c), 
   LEN(@nsql + c), 
   DATALENGTH(@sql + c), 
   DATALENGTH(@nsql + c) 
FROM @t;
Run Code Online (Sandbox Code Playgroud)

  • 如果您要依赖所有客户端代码检查varchar的长度,为什么还要为varchar字段设置大小呢? (5认同)
  • @Jez,数据库设计的很大一部分是易于维护和搜索性能.正如gbn所指出的那样,你可以避免在搜索字段上使用`(MAX)`.话虽这么说,一般来说,使这个领域成为你可以考虑使用它的最大领域也是一个好主意. (4认同)
  • varchar(max)是LOB类型:有一些限制.另外,我的参数与我的表长度相匹配.而且,我并不那么关心:我的设计,表格和参数就是它们的本质. (2认同)
  • @gbn:是的,但在DBMS中输入字段的主要原因(如果不是主要的)肯定是为了确保数据库的完整性?出于这个原因,如果SQL Server在INSERT语句中开始对varchar进行不可避免的字符串截断,我认为*(虽然我不确定SQL Server可以解决其他一些问题)会对此进行广泛的谴责行为.但是,谁再使用原始INSERT语句?压倒性的最佳实践是通过存储过程插入.因此,假设您必须执行后者,SQL Server*是* - 有效 - 静默地截断INSERT中的字符串. (2认同)

Dav*_*ogo 15

一如既往地感谢StackOverflow引发这种深入的讨论.我最近一直在浏览我的存储过程,使用标准的事务处理方法和try/catch块来使它们更加健壮.我不同意Joe Stefanelli的说法"我的建议是让应用程序负责",并完全赞同Jez:"让SQL Server验证字符串长度会更好".对我来说,使用存储过程的全部意义在于它们是用数据库本地语言编写的,应该作为最后一道防线.在应用程序端,255和256之间的差异只是一个无差别的数字,但在数据库环境中,最大大小为255的字段将不接受256个字符.应用程序验证机制应该尽可能地反映后端数据库,但维护很难,所以如果应用程序错误地允许不合适的数据,我希望数据库给我很好的反馈.这就是为什么我使用数据库而不是一堆CSV或JSON等文本文件.

我很疑惑为什么我的一个SP抛出8152错误而另一个默默地被截断.我终于鼓起了:抛出8152错误的SP有一个参数允许比相关表列多一个字符.表列设置为nvarchar(255),但参数为nvarchar(256).那么,我的"错误"不会解决gbn关注的问题:"大规模的性能问题"吗?而不是使用max,也许我们可以一致地将表列大小设置为255,将SP参数设置为仅更长一个字符,例如256.这解决了静默截断问题,并且不会导致任何性能损失.据推测,我还没有想到其他一些缺点,但这对我来说似乎是一个很好的妥协.

更新:我担心这种技术不一致.进一步测试表明我有时会触发8152错误,有时数据会被静默截断.如果有人能帮助我找到更可靠的方法来解决这个问题,我将非常感激.

更新2:请在此页面上看到Pyitoechito的答案.

  • @Pyitoechito 在一个单独的答案中提到,当它只截断空格时,可能会发生无声截断。(我认为对这个答案添加评论会很好。) (2认同)