Ist SELECT * in einem Trigger in Ordnung? Oder bitte ich um Ärger?

8

Ich bin in eine Debatte bei der Arbeit verwickelt und brauche Ratschläge zu möglichen Fallstricken, die ich übersehen könnte.

Stellen Sie sich ein Szenario vor, in dem ein Trigger verwendet wird, um gelöschte Datensätze in eine Überwachungstabelle zu kopieren. Der Trigger verwendet SELECT *. Jeder zeigt und schreit und sagt uns, wie schlimm das ist.

Wenn jedoch eine Änderung an der Struktur der Haupttabelle vorgenommen wird und die Überwachungstabelle übersehen wird, generiert der Auslöser einen Fehler, der die Benutzer darüber informiert, dass die Überwachungstabelle ebenfalls geändert werden muss.

Der Fehler wird beim Testen auf unseren DEV-Servern abgefangen. Wir müssen jedoch sicherstellen, dass die Produktion DEV entspricht, damit wir SELECT * in Produktionssystemen zulassen (nur Trigger).

Meine Frage lautet also: Ich werde dazu gedrängt, SELECT * zu entfernen, aber ich bin mir nicht sicher, wie ich sonst sicherstellen kann, dass wir automatisch Entwicklungsfehler dieser Art, Ideen oder Best Practices erfassen.

Ich habe unten ein Beispiel zusammengestellt:

--Create Test Table
CREATE TABLE dbo.Test(ID INT IDENTITY(1,1), Person VARCHAR(255))
--Create Test Audit Table
CREATE TABLE dbo.TestAudit(AuditID INT IDENTITY(1,1),ID INT, Person VARCHAR(255))

--Create Trigger on Test
CREATE TRIGGER [dbo].[trTestDelete] ON [dbo].[Test] AFTER DELETE
NOT FOR REPLICATION
AS
BEGIN
    SET NOCOUNT ON;
    INSERT  dbo.TestAudit([ID], [Person])
    SELECT  *
    FROM    deleted
END

--Insert Test Data into Test
INSERT INTO dbo.Test VALUES
('Scooby')
,('Fred')
,('Shaggy')

--Perform a delete
DELETE dbo.Test WHERE Person = 'Scooby'

UPDATE (Frage umformulieren):

Ich bin ein DBA und muss sicherstellen, dass Entwickler keine schlecht durchdachten Bereitstellungsskripte bereitstellen, indem sie zu unserer Best-Practice-Dokumentation beitragen. SELECT * verursacht einen Fehler in DEV, wenn der Entwickler die Audit-Tabelle übersieht (dies ist ein Sicherheitsnetz), sodass der Fehler früh im Entwicklungsprozess erkannt wird. Aber irgendwo in der SQL-Verfassung - 2. Änderung heißt es "Du sollst SELECT * nicht verwenden". Jetzt gibt es also einen Anstoß, das Sicherheitsnetz loszuwerden.

Wie würden Sie das Sicherheitsnetz ersetzen, oder sollte ich dies als Best Practice für Trigger betrachten?

UPDATE 2: (Lösung)

Vielen Dank für all Ihre Beiträge. Ich bin mir nicht sicher, ob ich eine klare Antwort habe, da dies ein sehr graues Thema zu sein scheint. Gemeinsam haben Sie jedoch Diskussionspunkte bereitgestellt, die unseren Entwicklern helfen können, ihre Best Practice zu definieren.

Vielen Dank Daevinfür Ihren Beitrag. Ihre Antwort liefert die Grundlage für einige Testmechanismen, die unsere Entwickler implementieren können. +1

Vielen Dank CM_Dayton, Ihre Vorschläge, die zu Best Practices beitragen, können für jeden von Vorteil sein, der Audit-Trigger entwickelt. +1

Vielen Dank ypercube, Sie haben viel über die Probleme mit Tabellen nachgedacht, bei denen verschiedene Formen von Definitionsänderungen vorgenommen wurden. +1

Abschließend:

Is Select * ok in a tigger? Ja, es ist eine Grauzone. Folgen Sie nicht blind der Ideologie "Select * is Bad".

Am I asking for Trouble? Ja, wir fügen Tabellen nicht nur neue Spalten hinzu.

friedlich
quelle
Sie beantworten sich in der Frage. select * wird unterbrochen, wenn die Quelltabelle geändert wird. Verwenden Sie eine Form der Quellcodeverwaltung, um sicherzustellen, dass dev und prod identisch sind.
Bob Klimes
etwas breitere Frage: Wie oft löschen Sie Datensätze und wie viele Prozent der Tabelle? Eine Alternative zu Triggern wäre ein Bit-Flag, das Zeilen als gelöscht markiert, und ein Agentenjob, der nach einem Zeitplan ausgeführt wird, um sie in eine Protokolltabelle zu verschieben. Sie können in die Agentenjobprüfungen integrieren, um festzustellen, ob das Tabellenschema übereinstimmt, und der Job schlägt einfach fehl, wenn bei diesem Schritt ein Problem auftritt, bis es behoben ist.
Tanner
Normalerweise stimme ich der SELECT *Faulheit zu, aber da Sie einen legitimen Grund haben, es zu benutzen, ist es grauer als schwarz-weiß. Was sollten Sie versuchen, etwas zu tun , wie diese , aber es nur einstellen , die gleiche Spaltenanzahl nicht hat, sondern dass die Spaltennamen und Datentypen gleich sind (da jemand Typen Daten ändern könnte und immer noch zu Problemen in der db normalerweise nicht gefangen mit Ihrem SELECT *'Sicherheitsnetz'.
Daevin
3
Ich mag die Idee, SELECT *als Sicherheitsnetz zu verwenden, aber es wird nicht alle Fälle erfassen. Zum Beispiel, wenn Sie eine Spalte löschen und erneut hinzufügen. Dadurch wird die Reihenfolge der Spalten geändert, und (sofern nicht alle Spalten vom gleichen Typ sind) die Einfügungen in die Prüftabelle schlagen fehl oder führen aufgrund der impliziten Typkonvertierungen zu Datenverlust.
Ypercubeᵀᴹ
2
Ich frage mich auch, wie Ihr Audit-Design funktioniert, wenn eine Spalte aus einer Tabelle entfernt wird. Löschen Sie auch die Spalte aus der Prüftabelle (und verlieren Sie alle vorherigen Prüfdaten)?
Ypercubeᵀᴹ

Antworten:

10

Typischerweise wird es als "faule" Programmierung angesehen.

Da Sie hier speziell zwei Werte in Ihre TestAuditTabelle einfügen , würde ich darauf achten, dass Ihre Auswahl auch genau zwei Werte erhält . Denn wenn diese TestTabelle aus irgendeinem Grund eine dritte Spalte hat oder jemals bekommt, schlägt dieser Trigger fehl.

Nicht direkt mit Ihrer Frage verbunden, aber wenn Sie eine Prüftabelle einrichten, würde ich Ihrer TestAuditTabelle auch einige zusätzliche Spalten hinzufügen, um ...

  • Verfolgen Sie die Aktion, die Sie überwachen (in diesem Fall löschen, gegen Einfügungen oder Aktualisierungen).
  • Datums- / Zeitspalte, um zu verfolgen, wann das Überwachungsereignis aufgetreten ist
  • Benutzer-ID-Spalte, um zu verfolgen, wer die Aktion ausgeführt hat, die Sie überwachen.

Das führt zu einer Abfrage wie:

INSERT dbo.TestAudit([ID], [Person], [AuditAction], [ChangedOn], [ChangedBy])
SELECT [ID], [Person], 
   'Delete', -- or a 'D' or a numeric lookup to an audit actions table...
   GetDate(), -- or SYSDATETIME() for greater precision
   SYSTEM_USER -- or some other value for WHO made the deletion
FROM deleted

Auf diese Weise erhalten Sie genau die Spalten, die Sie benötigen, und Sie prüfen, worum es bei dem Überwachungsereignis geht.

Nocken
quelle
"Benutzer-ID" Diese ist bei der Überwachung schwierig. In der Regel entsprechen Datenbankkonten nicht den tatsächlichen Benutzern. Viel häufiger entsprechen sie einer einzelnen Webanwendung oder einer anderen Art von Komponente, wobei ein einzelner Satz von Anmeldeinformationen von dieser Komponente verwendet wird. (Und manchmal teilen sich die Komponenten auch Anmeldeinformationen.) Daher sind die Datenbankanmeldeinformationen als Kennung dafür, wer was getan hat, ziemlich nutzlos, es sei denn, Sie sind nur daran interessiert, welche Komponente es getan hat. Die Weitergabe von Anwendungsdaten, die das "Wer" identifizieren, ist meines Wissens mit einer Triggerfunktion nicht ganz einfach.
jpmc26
siehe Update zur Frage.
Pacreely
Ein weiteres Problem, das bei SELECT * im Allgemeinen auftreten kann (obwohl dies wahrscheinlich nicht in Ihrem Beispiel der Fall ist), besteht darin, dass das Einfügen fehlschlägt, wenn die Spalten der zugrunde liegenden Tabelle nicht in derselben Reihenfolge wie Ihre Einfügespalten liegen.
CaM
3

Ich habe Ihre Frage kommentiert, aber ich dachte, ich würde versuchen, tatsächlich eine Codelösung zu präsentieren.

Normalerweise bin ich damit einverstanden SELECT *, faul zu sein, aber da Sie einen legitimen Grund haben, es zu verwenden, ist es grauer als schwarz-weiß.

Was Sie sollen (meiner Meinung nach ) versuchen, etwas zu tun , wie diese , aber passen Sie die Spaltennamen und Datentypen gleich sind , um sicherzustellen , (da jemand Typen Daten ändern könnte und immer noch zu Problemen in der db normalerweise nicht mit Ihrer gefangen SELECT *Sicherheit Netz'.

Sie können sogar eine Funktion erstellen, mit der Sie schnell überprüfen können, ob die Überwachungsversion der Tabelle mit der Nicht-Überwachungsversion übereinstimmt:

-- The lengths are, I believe, max values for the corresponding db objects. If I'm wrong, someone please correct me
CREATE FUNCTION TableMappingComparer(
    @TableCatalog VARCHAR(85) = NULL,
    @TableSchema VARCHAR(32) = NULL,
    @TableName VARCHAR(128) = NULL) RETURNS BIT
AS
BEGIN
    DECLARE @ReturnValue BIT = NULL;
    DECLARE @VaryingColumns INT = NULL;

    IF (@TableCatalog IS NOT NULL
            AND @TableSchema IS NOT NULL
            AND @TableName IS NOT NULL)
        SELECT @VaryingColumns = COUNT(COLUMN_NAME)
            FROM (SELECT COLUMN_NAME,
                        DATA_TYPE -- Add more columns that you want to ensure are identical
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_CATALOG = @TableCatalog
                        AND TABLE_SCHEMA = @TableSchema
                        AND TABLE_NAME = @TableName
                EXCEPT
                    SELECT COLUMN_NAME,
                            DATA_TYPE -- Add more columns that you want to ensure are identical
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE (TABLE_CATALOG = @TableCatalog
                            AND TABLE_SCHEMA = @TableSchema
                            AND TABLE_NAME = @TableName + 'Audit')
                            AND (COLUMN_NAME != 'exclude your audit table specific columns')) adt;
    IF @VaryingColumns = 0
        SET @ReturnValue = 1
    ELSE IF @VaryingColumns > 0
        SET @ReturnValue = 0

    RETURN @ReturnValue;
END;

Das SELECT ... EXCEPT SELECT ...Auditzeigt Ihnen, welche Spalten in der Tabelle nicht in der Audit-Tabelle enthalten sind. Sie können sogar die Funktion ändern, um den Namen von Spalten zurückzugeben, die nicht identisch sind, anstatt nur zuzuordnen, ob sie zugeordnet sind oder nicht, oder sogar eine Ausnahme auslösen.

Sie können dies dann ausführen, bevor Sie für jede Tabelle in der Datenbank von einem Server DEVzu PRODUCTIONServern wechseln.

SELECT TABLE_NAME
    FROM INFORMATION_SCHEMA.TABLES
    WHERE NOT (TABLE_NAME LIKE '%Audit')
Daevin
quelle
1
siehe Update zu Frage
pacreely
Froh, dass ich helfen konnte. Wir danken Ihnen, dass Sie alle Antworten gelesen und an Ihr Team zurückgesandt haben, um Vorschläge zu erhalten. Anpassungsfähigkeit und Verbesserungsbereitschaft sorgen dafür, dass die technischen Abteilungen die Unternehmen am Laufen halten und reibungslos funktionieren! : D
Daevin
0

Die Anweisung, die den Trigger aufruft, schlägt fehl und der Trigger schlägt fehl. Es ist besser, den Trigger- und Audit-Trail zu dokumentieren, damit Sie die Abfrage so ändern können, dass die Spalten hinzugefügt werden, anstatt das * anzugeben.

Zumindest sollten Sie den Trigger so ändern, dass er beim Protokollieren von Fehlern in einer Tabelle ordnungsgemäß fehlschlägt, und möglicherweise eine Warnung in die Tabelle einfügen, in der der Trigger die Fehler protokolliert.

Dies erinnert auch daran, dass Sie einen Auslöser oder eine Warnung setzen können, wenn jemand die Tabelle ändert und weitere Spalten hinzufügt oder Spalten entfernt, um Sie zu benachrichtigen, den Auslöser anzuhängen.

Ich glaube, dass die Leistung * nichts ändert, sondern nur die Wahrscheinlichkeit von Fehlern in der Zukunft erhöht, wenn sich die Dinge ändern, und auch zu Netzwerklatenz führen kann, wenn Sie bei Bedarf mehr Informationen über das Netzwerk abrufen. Es gibt eine Zeit und einen Ort für *, aber ich denke, wie oben beschrieben, haben Sie bessere Lösungen und Tools, die Sie stattdessen ausprobieren können.

Shaulinator
quelle
0

Wenn sich Ihre ursprüngliche oder Ihre Audit-Tabellenstruktur überhaupt ändert, stellen Sie sicher, dass bei Ihrer Auswahl * ein Problem auftritt.

INSERT INTO [AuditTable]
(Col1,Col2,Col3)
SELECT * 
FROM [OrigTable] or [deleted];

Wenn sich einer der beiden ändert, tritt ein Fehler auf.

Du könntest es tun:

INSERT INTO [AuditTable]
SELECT * 
FROM [OrigTable];

Aber wie CM_Dayton sagt, ist das faul programmiert und öffnet die Tür für andere Inkonsistenzen. Damit dieses Szenario funktioniert, müssen Sie unbedingt sicherstellen, dass Sie die Struktur beider Tabellen gleichzeitig aktualisieren.

MguerraTorres
quelle