75

根据这个论坛讨论,SQL Server(我使用的是 2005,但我认为这也适用于 2000 和 2008)静默地将varchar您指定为存储过程参数的任何 s 截断为 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参数。

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

首先,将存储过程的@testStringField参数声明为大小 6,并检查其长度是否超过 5。这看起来有点像 hack,并且涉及大量的样板代码。

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

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

4

7 回答 7

32

只是

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

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

UPDATE ...WHERE varchar100column = @varcharmaxvalue

编辑:

有一个关于此问题的打开的 Microsoft Connect 项目。

它可能值得包含在Erland Sommarkog 的 Strict 设置中(以及匹配的 Connect item)。

编辑 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;
于 2011-01-07T17:24:31.830 回答
17

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

我很困惑为什么我的一个 SP 抛出了 8152 错误,而另一个却默默地被截断了。我终于 twigged:抛出 8152 错误的 SP 有一个参数,它允许比相关表列多一个字符。表列设置为 nvarchar(255),但参数为 nvarchar(256)。那么,我的“错误”难道不是解决 gbn 的担忧:“巨大的性能问题”吗?代替使用最大值,也许我们可以始终将表列大小设置为例如 255,并将 SP 参数设置为仅长一个字符,例如 256。这解决了静默截断问题并且不会导致任何性能损失。大概还有其他一些我没有想到的缺点,但这对我来说似乎是一个很好的妥协。

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

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

于 2011-03-19T09:07:48.703 回答
4

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

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

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

于 2013-06-13T13:42:32.297 回答
4

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

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

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

于 2011-01-07T17:20:33.510 回答
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 将抛出错误。

于 2012-04-19T01:51:42.400 回答
0

你总是可以在你的 sp 中抛出一个 if 语句来检查它们的长度,如果它们大于指定的长度,则会抛出一个错误。不过,这相当耗时,如果您更新数据大小,更新会很痛苦。

于 2011-01-07T21:18:59.777 回答
-4

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

我分享你的沮丧。
在参数上设置 Character-Size 的全部目的是让其他开发人员在传入数据时立即知道
    大小限制是什么(通过 Intellisense)。
这就像将您的文档直接烘焙到 Sproc 的签名中。

看,我明白了,变量分配期间的隐式转换是罪魁祸首。     尽管如此,仍然没有充分的理由在您被迫解决此功能的
情况下花费大量精力。 如果你问我,Sprocs 和 Functions 应该有相同的引擎规则,     用于分配参数,在填充表时使用。这真的是太过分了吗?


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

当值太长时 - 我希望我的代码每次都中断。
它应该是:不要通过 go,并修复您的代码。
您可能有多个截断错误,多年来一直在恶化,但从未发现它们。
确保您的数据完整性发生了什么?

假设您的 SQL 代码只有在所有参数都经过验证后才会被调用,这是很危险的。
我尝试在我的网站和它调用的 Sproc 中添加相同的验证,但
    我仍然在我的 Sproc 中捕捉到从网站溜走的错误。这是一个很棒的理智检查!
如果您想将 Sproc 重新用于 WebSite/WebService 并从其他
    Sprocs/Jobs/Deployment/Ad-Hoc 脚本(没有验证参数的前端)调用它怎么办?

MSSQL 需要一个“ NO_TRUNC”选项来对任何非最大字符串变量
    (甚至那些用作 Sproc 和函数的参数)强制执行此操作。
它可能是连接/会话范围的:(
    例如“ TRANSACTION ISOLATION LEVEL READ UNCOMMITTED”选项如何影响所有查询)
或专注于单个变量:(
    例如“ NOLOCK”是如何仅针对 1 个表的表提示)。
或者您打开的跟踪标志或数据库属性将其应用于数据库中的所有 Sproc/Function 参数。

我并不是要颠覆几十年的遗留代码。
只是要求 MS 选择更好地管理我们的数据库。

于 2019-08-12T22:00:44.240 回答