SQL Server在存储过程中默默地截断varchar


72

根据这个论坛的讨论,SQL Server(我使用的是2005,但我收集到的信息也适用于2000和2008)varchar,即使您直接使用INSERT实际上会导致错误。例如。如果我创建此表:

CREATE TABLE testTable(
    [testStringField] [nvarchar](5) NOT NULL
)

然后当我执行以下命令时:

INSERT INTO testTable(testStringField) VALUES(N'string which is too long')

我收到一个错误:

String or binary data would be truncated.
The statement has been terminated.

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

CREATE PROCEDURE spTestTableInsert
    @testStringField [nvarchar](5)
AS
    INSERT INTO testTable(testStringField) VALUES(@testStringField)
GO

并执行它:

EXEC spTestTableInsert @testStringField = N'string which is too long'

没有错误,影响了1行。在表中插入一行,并testStringField以'strin'开头。SQL Server默默地截断了存储过程的varchar参数。

现在,这种行为有时可能很方便,但我认为没有办法将其关闭。这非常令人讨厌,因为如果我将太长的字符串传递给存储过程,我希望事情出错。似乎有两种方法可以解决此问题。

首先,将存储的proc的@testStringField参数声明为大小6,并检查其长度是否超过5。这似乎有点不合时宜,并且涉及到令人讨厌的样板代码。

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

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


1
使用Sybase派生的驱动程序(例如Perl的DBD :: Sybase)创建的预处理语句通过创建一个临时存储过程然后调用该临时存储过程来工作,因此您也可能会用预处理语句来进行无谓截断。我不知道其他驱动程序(例如ODBC)是否可以更好地处理预备语句。
Ed Avis

1
显然,函数和变量存在相同的行为。根据msdn.microsoft.com/zh-cn/library/ms187926.aspx中的注释:“在过程,用户定义的函数中传递参数或在批处理语句中声明和设置变量时,不接受SET ANSI_WARNINGS。例如,如果将变量定义为char(3),然后将其设置为大于三个字符的值,则数据将被截断为定义的大小,并且INSERT或UPDATE语句成功。”
布兰科·迪米特里耶维奇

Answers:


31

它只是

我从来没有注意到任何问题,因为我的一项检查是确保我的参数与我的表列长度匹配。在客户端代码中也是如此。就个人而言,我希望SQL永远不会看到过长的数据。如果我确实看到了被截断的数据,那就很明显是造成它的原因。

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

UPDATE ...WHERE varchar100column = @varcharmaxvalue

编辑:

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

而且它可能值得纳入Erland Sommarkog的严格设置(和匹配的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;

5
如果您要依靠所有客户端代码检查varchar的长度,为什么还要麻烦设置varchar字段的大小?
Jez

2
varchar(max)是LOB类型:有限制。另外,我的参数与表的长度匹配。而且,我并不那么在乎:我的设计,表格和参数就是它们的本质。
gbn

4
@Jez,数据库设计的很大一部分是易于维护和搜索性能。如gbn所述,如果您可以避免(MAX)在搜索字段中使用,则可能应该这样做。话虽如此,一般来说,使该领域成为您可能考虑使用的最大领域也是一个好主意。
马修

2
@gbn:是的,但是一定要在DBMS中键入字段的主要原因(如果不是主要的话)是为了确保数据库的完整性?因此,如果SQL Server开始在INSERT语句中为varchar进行不可避免的字符串截断,我认为(尽管我不确定SQL Server可以摆脱的其他问题),对此行为将有广泛的谴责。但是,谁又使用原始的INSERT语句呢?压倒性的最佳实践是通过存储的proc插入。因此,假设您必须执行后者,则SQL Server实际上是在默默地截断INSERT中的字符串。
Jez

1
...它违反了标准:“只有两个例外,字符串表达式只能分配给字符集相同的字符串类型的站点。例外情况在第4.2.8节“通用字符集”中指定,以及其他可能由实现定义的情况;如果存储分配将由于截断而导致丢失非<space>字符,则将引发异常条件;如果检索分配或对<cast规范的求值>将由于截断而导致字符丢失,然后引发警告条件。” ...
耶兹(Jez)

16

和往常一样,感谢StackOverflow引起了这种深入的讨论。我最近一直在仔细研究我的存储过程,以使用标准的事务处理方法和try / catch块使它们更健壮。我不同意Joe Stefanelli的观点,“我的建议是让应用程序一方负责”,并完全同意Jez:“让SQL Server验证字符串长度会更可取”。对我而言,使用存储过程的全部要点是,它们是使用数据库固有的语言编写的,应作为防御的最后一道防线。在应用程序方面,255和256之间的差异只是一个无意义的数字,但是在数据库环境中,最大大小为255的字段将根本不接受256个字符。应用程序验证机制应尽其所能地反映后端数据库,但是维护很困难,因此,如果应用程序错误地允许使用不合适的数据,我希望数据库能给我很好的反馈。这就是为什么我使用数据库而不是一堆带有CSV或JSON或其他内容的文本文件的原因。

我很困惑,为什么我的一个SP抛出了8152错误,而另一个SP被默默地截断了。最后,我进行了调整:抛出8152错误的SP的参数允许比相关的表列多一个字符。表列设置为nvarchar(255),但参数为nvarchar(256)。因此,我的“错误”是否会解决gbn的担忧:“大规模性能问题”?而不是使用max,也许我们可以将表列的大小统一设置为255,将SP参数设置为仅一个字符长(例如256)。这解决了静默截断问题,并且不会造成任何性能损失。大概还有我没有想到的其他缺点,但这对我来说似乎是一个很好的折衷方案。

更新:恐怕这种技术不一致。进一步的测试表明,有时我会触发8152错误,有时会默默地将数据截断。如果有人可以帮助我找到更可靠的解决方法,我将不胜感激。

更新2:请在此页上查看Pyitoechito的答案。


1
@Pyitoechito在一个单独的答案中提到静默截断可能仅在截断空白时发生。(我认为最好在此答案上添加评论。)
Tim Goodman

4

在这里可以看到相同的行为:

declare @testStringField [nvarchar](5)
set @testStringField = N'string which is too long'
select @testStringField

我的建议是让应用程序端在调用存储过程之前负责验证输入。


3
是的,这显然是我要做的,但是由于它是一个存储过程,所以我认为假定我的代码是唯一调用它的代码并不是一个好主意。让SQL Server验证字符串长度将是更可取的。
Jez

我知道我已经在SET命令中看到了这种行为,但是我没有在sp参数中注意到这一点……然后我只使用了R2。你知道这是否仍然适用吗?(对不起,无法测试,因为我现在无法访问服务器)
Matthew

4

更新:恐怕这种技术不一致。进一步的测试表明,有时我会触发8152错误,有时会默默地将数据截断。如果有人可以帮助我找到更可靠的解决方法,我将不胜感激。

这可能是因为字符串中的第256个字符是空格。VARCHARs将在插入时截断尾随空格,并仅生成警告。因此,您的存储过程会默默地将字符串截断为256个字符,而您的插入操作将截断尾随空格(带有警告)。当所述字符不是空格时,将产生错误。

可能的解决方案是使存储过程VARCHAR具有合适的长度以捕获非空白字符。VARCHAR(512)可能足够安全。


1
好提示。感谢您的建议。
DavidHyogo 2013年

1

一种解决方案是:

  1. 将所有传入参数更改为 varchar(max)
  2. 具有正确数据长度的sp私有变量(只需复制并粘贴所有参数,然后在末尾添加“ int”
  3. 声明一个表变量,其列名与变量名相同
  4. 在表格中插入一行,每个变量以相同的名称进入该列
  5. 从表中选择内部变量

这样,您对现有代码的修改将非常少,如下面的示例所示。

这是原始代码:

create procedure spTest
(
    @p1 varchar(2),
    @p2 varchar(3)
)

这是新代码:

create procedure spTest
(
    @p1 varchar(max),
    @p2 varchar(max)
)
declare @p1Int varchar(2), @p2Int varchar(3)
declare @test table (p1 varchar(2), p2 varchar(3)
insert into @test (p1,p2) varlues (@p1, @p2)
select @p1Int=p1, @p2Int=p2 from @test

请注意,如果传入参数的长度将大于限制,而不是默默地切掉字符串,SQL Server将抛出错误。


0

您总是可以在您的sp's中抛出一个if语句来检查它们的长度,如果它们大于指定的长度,则会抛出错误。但是,这相当耗时,并且如果您更新数据大小,将很难进行更新。


-3

这不是今天能解决您问题的答案,但其中包括考虑添加的MSSQL功能建议,可以解决此问题。
将此称为MSSQL的一个缺陷很重要,因此我们可以通过提高对它的认识来帮助他们解决它。
如果您想对它进行表决,这是正式建议:
https //feedback.azure.com/forums/908035-sql-server/suggestions/38394241-request-for-new-rule-string-truncation-error-对于

我分享你的无奈。
在Parameters上设置Character-Size的重点是,其他开发人员
    在传递数据时(通过Intellisense)将立即知道大小限制是什么。
这就像将您的文档直接放入Sproc的签名中一样。

看,我明白了,在变量赋值期间的隐式转换是罪魁祸首。
但是,没有充分的理由来扩展这种能源争夺方案的数量,在这种情况下
    ,您不得不解决此功能。
如果您问我,“存储过程”和“函数”应具有相同的引擎规则(
    用于分配参数),这些规则在填充表时使用。这真的有太多要问的吗?

所有这些使用较大字符数限制
    ,然后在每个Sproc中添加对每个参数的验证的建议都是荒谬的。
我知道这是确保避免截断的唯一方法,但实际上是MSSQL吗?
我不在乎它是ANSI / ISO标准还是愚蠢的!

当值太长时-我希望我的代码每次都中断。
应该是:不要通过,并修复您的代码。
您可能有多个截断bug恶化了数年,却再也没有发现它们。
如何确保您的数据完整性?

假设仅验证所有参数之后才调用SQL代码是危险的。
我尝试将相同的验证添加到我的网站以及它调用的Sproc中,但是
    我仍然发现Sproc中的错误已从网站上漏掉了。这是一个很好的检查方法!
如果要在WebSite / WebService中重新使用Sproc并从其他
    Sprocs / Jobs / Deployment / Ad-Hoc脚本(没有验证参数的前端)中调用它,该怎么办?

MSSQL需要一个“ NO_TRUNC”选项来在任何非最大字符串变量
    (甚至那些用作存储过程和函数的参数的变量)上强制执行此选项。
它可以是Connection / Session-Scoped :(
    例如“ TRANSACTION ISOLATION LEVEL READ UNCOMMITTED”选项如何影响所有查询),也可以
专注于单个变量:(
    例如“ NOLOCK”仅是1个表的表提示)。
或您打开的跟踪标志或数据库属性,将其应用于数据库中的所有Sproc / Function参数。

我不是要颠覆数十年的旧版法规。
只是要求MS提供更好地管理我们的数据库的选项。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.