it-swarm.com.de

SQL Server schneidet Varchars in gespeicherten Prozeduren unbeaufsichtigt ab

Nach diesem Forum Diskussion , SQL Server (Ich verwende 2005, aber ich sammeln gilt dies auch für 2000 und 2008) kürzt still all varcharname__s Sie als Parameter für gespeicherte Prozeduren auf die Länge des varchar angeben, auch wenn das Einfügen dass Eine Zeichenfolge, die direkt INSERTverwendet, würde tatsächlich einen Fehler verursachen. z.B. Wenn ich diese Tabelle erstelle:

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

wenn ich dann folgendes ausführe:

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

Ich erhalte einen Fehler:

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

Großartig. Datenintegrität bleibt erhalten und der Anrufer weiß Bescheid. Definieren wir nun eine gespeicherte Prozedur, um Folgendes einzufügen:

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

und führe es aus:

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

Keine Fehler, 1 Zeile betroffen. In die Tabelle wird eine Zeile mit testStringFieldals 'strin' eingefügt. SQL Server hat den Parameter varcharder gespeicherten Prozedur unbeaufsichtigt abgeschnitten.

Nun, dieses Verhalten mag manchmal praktisch sein, aber ich glaube, es gibt KEINE MÖGLICHKEIT, es auszuschalten. Dies ist äußerst ärgerlich, da ich die Sache fehlerhaft machen möchte, wenn ich eine zu lange Zeichenfolge an die gespeicherte Prozedur übergebe. Es scheint zwei Möglichkeiten zu geben, damit umzugehen.

Deklarieren Sie zuerst den Parameter @testStringField des gespeicherten Prozesses als Größe 6 und prüfen Sie, ob seine Länge über 5 liegt. Dies scheint ein bisschen zu hacken und beinhaltet irritierende Mengen an Boilerplate-Code.

Als zweites deklarieren Sie ALLE varchar-Parameter für gespeicherte Prozeduren als varchar(max), und lassen Sie dann die Anweisung INSERTin der gespeicherten Prozedur fehlschlagen.

Letzteres scheint in Ordnung zu sein, daher lautet meine Frage: Ist es eine gute Idee, varchar(max) IMMER für Zeichenfolgen in gespeicherten SQL Server-Prozeduren zu verwenden, wenn der gespeicherte Prozess fehlschlagen soll, wenn eine zu lange Zeichenfolge übergeben wird? Könnte es sogar eine bewährte Methode sein? Die stille Kürzung, die nicht deaktiviert werden kann, kommt mir dumm vor.

65
Jez

Es ist nur ist.

Ich habe jedoch nie ein Problem bemerkt, weil eine meiner Überprüfungen sicherstellt, dass meine Parameter mit meinen Tabellenspaltenlängen übereinstimmen. Auch im Client-Code. Ich persönlich würde erwarten, dass SQL niemals zu lange Daten sieht. Wenn ich abgeschnittene Daten sehen würde, würde es offensichtlich bluten, was sie verursacht hat.

Wenn Sie der Meinung sind, dass varchar (max) erforderlich ist, müssen Sie sich aufgrund der Priorität des Datentyps vor einem massiven Leistungsproblem schützen. varchar (max) hat eine höhere Priorität als varchar (n) (längste ist am höchsten). Bei dieser Art von Abfrage erhalten Sie einen Scan und keine Suche, und jeder Wert von varchar (100) ist CAST bis varchar (max).

UPDATE ...WHERE varchar100column = @varcharmaxvalue

Bearbeiten:

Es gibt ein offenes Microsoft Connect-Element zu diesem Problem.

Und es ist wahrscheinlich wert, in Erland Sommarkogs strenge Einstellungen (und und dazu passende Connect-Elemente ) aufgenommen zu werden.

Bearbeiten Sie 2 nach Martins Kommentar:

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;
29
gbn

Vielen Dank an StackOverflow, der sich für diese Art eingehender Diskussionen entschieden hat. Ich habe kürzlich meine gespeicherten Prozeduren durchgearbeitet, um sie robuster zu machen, indem ein Standardansatz für Transaktionen und Try/Catch-Blöcke verwendet wird. Ich stimme mit Joe Stefanelli nicht überein, dass "mein Vorschlag wäre, die Anwendungsseite verantwortlich zu machen", und stimme voll und ganz mit Jez überein: "Wenn der SQL Server die Stringlänge überprüft, wäre das viel zu bevorzugen". Für mich geht es bei der Verwendung von gespeicherten Prozeduren darum, dass sie in einer Sprache geschrieben sind, die in der Datenbank nativ ist und als letzte Verteidigungslinie dienen sollte. Auf der Anwendungsseite ist der Unterschied zwischen 255 und 256 nur eine bedeutungslose Zahl, aber in der Datenbankumgebung akzeptiert ein Feld mit einer maximalen Größe von 255 keine 256 Zeichen. Die Mechanismen zur Anwendungsvalidierung sollten die Backend-Datenbank so gut wie möglich widerspiegeln. Die Wartung ist jedoch schwierig. Ich möchte, dass mir die Datenbank ein gutes Feedback gibt, wenn die Anwendung versehentlich ungeeignete Daten zulässt. Aus diesem Grund verwende ich eine Datenbank anstelle einer Reihe von Textdateien mit CSV oder JSON oder was auch immer.

Ich war verblüfft, warum einer meiner SPs den 8152-Fehler warf und ein anderer lautlos abgeschnitten wurde. Schließlich habe ich gezweifelt: Der SP, der den Fehler 8152 auslöste, hatte einen Parameter, der ein Zeichen mehr erlaubte als die zugehörige Tabellenspalte. Die Tabellenspalte wurde auf nvarchar (255) gesetzt, der Parameter war jedoch nvarchar (256). Würde mein "Fehler" also nicht auf die Besorgnis von gbn eingehen: "massives Leistungsproblem"? Anstatt max zu verwenden, könnten wir die Tabellenspaltengröße beispielsweise auf 255 und den Parameter SP auf nur ein Zeichen, beispielsweise 256, festsetzen. Dies löst das Problem der stillen Abschneidung und führt zu keinerlei Performanceeinbußen . Vermutlich gibt es einen anderen Nachteil, an den ich nicht gedacht habe, aber es scheint mir ein guter Kompromiss zu sein.

Update: Ich fürchte, diese Technik ist nicht konsistent. Weitere Tests zeigen, dass ich manchmal den 8152-Fehler auslösen kann und manchmal die Daten stillschweigend abgeschnitten werden. Ich wäre sehr dankbar, wenn mir jemand helfen könnte, einen verlässlicheren Umgang damit zu finden.

Update 2: Bitte lesen Sie die Antwort von Pyitoechito auf dieser Seite.

15
DavidHyogo

Das gleiche Verhalten ist hier zu sehen:

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

Mein Vorschlag wäre, die Anwendungsseite vor dem Aufruf der gespeicherten Prozedur für die Überprüfung der Eingabe verantwortlich zu machen.

4
Joe Stefanelli

Update: Ich fürchte, diese Technik ist nicht konsistent. Weitere Tests zeigen, dass ich manchmal den 8152-Fehler auslösen kann und manchmal die Daten stillschweigend abgeschnitten werden. Ich wäre sehr dankbar, wenn mir jemand helfen könnte, einen verlässlicheren Umgang damit zu finden.

Dies ist wahrscheinlich der Fall, weil das 256. Zeichen in der Zeichenfolge Leerzeichen ist. VARCHARs schneidet beim Einfügen den nachfolgenden Leerraum ab und generiert nur eine Warnung. Ihre gespeicherte Prozedur schneidet also unbemerkt Ihre Zeichenfolgen auf 256 Zeichen ab, und Ihre Einfügung schneidet den nachfolgenden Leerraum (mit einer Warnung) ab. Wenn das Zeichen kein Leerzeichen ist, wird ein Fehler ausgegeben.

Eine Lösung wäre vielleicht, die VARCHAR der gespeicherten Prozedur auf eine geeignete Länge zu bringen, um ein Nicht-Leerzeichen zu erfassen. VARCHAR(512) wäre wahrscheinlich sicher genug.

4
Jenius

Eine Lösung wäre:

  1. Ändern Sie alle eingehenden Parameter in varchar(max).
  2. Haben Sie eine private private Variable mit der korrekten Datenlänge (kopieren Sie einfach alle Parameter und fügen Sie sie ein und fügen Sie am Ende "int" ein
  3. Deklarieren Sie eine Tabellenvariable, deren Spaltennamen den Variablennamen entsprechen
  4. Fügen Sie in die Tabelle eine Zeile ein, in der jede Variable in die gleichnamige Spalte geht
  5. Wählen Sie aus der Tabelle in interne Variablen

Auf diese Weise werden Ihre Änderungen am vorhandenen Code wie in dem folgenden Beispiel sehr minimal sein.

Dies ist der ursprüngliche Code:

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

Dies ist der neue Code:

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

Beachten Sie, dass, wenn die Länge der eingehenden Parameter den Grenzwert überschreitet, der String nicht abgehackt wird, und SQL Server einen Fehler ausgibt.

1
igorp

Sie könnten immer eine if -Anweisung in Ihre SPs werfen, die die Länge dieser überprüft, und wenn sie größer als die angegebene Länge ist, wird ein Fehler ausgegeben. Dies ist jedoch ziemlich zeitaufwändig und wäre ein Update, wenn Sie die Datengröße aktualisieren.

0
DForck42