Wenn Sie sagen, „ohne Trigger zu verwenden“, meinen Sie alle Trigger oder nur Zeile- für -Zeile - Trigger auf Tabellen?
Ich frage, weil Sie möglicherweise in der Lage sind, mit einer vernünftigen Verwendung der CONTEXT_INFO()
Funktion das zu erreichen, was Sie wollen , aber Sie müssten sicherstellen, dass dies SET CONTEXT_INFO
korrekt aufgerufen wurde, bevor Ihre Operationen stattfinden.
Ein Ort, an dem dies möglich ist, kann ein Anmeldetrigger auf Serverebene sein (dh kein Trigger auf Datenbank- / Objektebene), wie folgt:
USE master
GO
CREATE TRIGGER tr_audit_login
ON ALL SERVER
WITH EXECUTE AS 'sa'
AFTER LOGON
AS BEGIN
BEGIN TRY
DECLARE @eventdata XML = EVENTDATA();
IF @eventdata IS NOT NULL BEGIN
DECLARE @spid INT;
DECLARE @client_host VARCHAR(64);
SET @client_host = @eventdata.value('(/EVENT_INSTANCE/ClientHost)[1]', 'VARCHAR(64)');
SET @spid = @eventdata.value('(/EVENT_INSTANCE/SPID)[1]', 'INT');
-- pack the required data into the context data binary
-- (spid is just an example of packing multiple data items in a single field: you would probably use @@SPID at the point of use, instead)
DECLARE @context_data VARBINARY(128);
SET @context_data = CONVERT(VARBINARY(4), @spid)
+ CONVERT(VARBINARY(64), @client_host);
-- persist the spid and host into session-level memory
SET CONTEXT_INFO @context_data;
END
END TRY
BEGIN CATCH
/* do better error handling here...
* logon trigger can lock all users out of server, so i am just swallowing everything
*/
DECLARE @msg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR('%s', 10, 1, @msg) WITH LOG;
END CATCH
END
Sie können dann der Tabelle die Standardeinschränkung hinzufügen, um den Kontext zu speichern (für die Geschwindigkeit des Einfügens):
ALTER TABLE cdc.schema_table_CT
ADD ContextInfo varbinary(128) NULL DEFAULT(CONTEXT_INFO())
Sobald Sie das haben, können Sie diese ContextInfo
Spalte mit ein bisschen Slice-and-Dice abfragen :
SELECT *
,spid = CONVERT(INT, SUBSTRING(ContextInfo, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(ContextInfo, 5, 64))
FROM cdc.schema_table_CT
Technisch gesehen könnten Sie dies SUBSTRING
und das CONVERT
als Teil Ihrer Standardeinschränkung tun und nur die Client-IP dort speichern, aber es kann schneller sein, den gesamten Kontext dort zu speichern (wie bei jedem INSERT
), und nur die Werte in a zu extrahierenSELECT
wenn du sie brauchst.
Ich könnte geneigt sein, alle meine SUBSTRING
und CONVERT
Aufrufe in eine einzeilige Inline-Tabellenwertfunktion zu packen, die ich CROSS APPLY
bei Bedarf tun würde . Das hält die Auspacklogik an einem Ort:
CREATE FUNCTION fn_context (
@context_info VARBINARY(128)
)
RETURNS TABLE
AS RETURN (
SELECT
spid = CONVERT(INT, SUBSTRING(@context_info, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(@context_info, 5, 64))
)
GO
SELECT *
FROM cdc.schema_table_CT s
CROSS APPLY dbo.fn_context(s.ContextInfo) c
Beachten Sie, dass dies CONTEXT_INFO
nur ein 128-Byte ist VARBINARY
. Wenn Sie mehr Daten benötigen, als Sie in 128 Byte einpassen können, würde ich eine Tabelle erstellen, die alle diese Daten enthält, als Zeile für diese 'Sitzung' in die Tabelle im Anmeldetrigger einfügen und CONTEXT_INFO
auf den Ersatzschlüsselwert dieser Tabelle setzen
Sie sollten auch beachten, dass es für einen Benutzer mit geeigneten Berechtigungen trivial ist, diese Kontextdaten in der Tabelle "In Ruhe" zu überschreiben, da dies nur eine Standardeinschränkung ist. Dies gilt natürlich auch für alle anderen Spalten in Tabellen im Audit-Stil.
Es wäre schön, wenn es sich um eine persistierte berechnete Spalte und nicht um eine Standardspalte handeln könnte, aber die CONTEXT_INFO()
Funktion ist nicht deterministisch, daher ist sie ein No-Go (Sie können möglicherweise einige FUNCTION
Tricks um a herum anwendenVIEW
, aber ich würde es nicht ).
Es ist auch trivial für diesen Benutzer mit ausreichendem Zugriff auf Anrufe SET CONTEXT_INFO
selbst und Ihren Tag durcheinander zu bringen (z. B. mit gefälschten Werten oder speziell angefertigter gespeicherter Injektion). Behandeln Sie den Inhalt daher mit Argwohn und Sorgfalt, codieren Sie ihn vor der Anzeige und behandeln Sie Ausnahmen Gut.
Was den Hostnamen betrifft, gibt Ihnen das ClientHost
Element von EVENTDATA()
die IP-Adresse (oder einen <local machine>
Indikator). Technisch gesehen könnten Sie CLR verwenden, um Reverse-DNS-Suchvorgänge zurück zum Hostnamen durchzuführen. Diese sind jedoch in der Regel zu langsam INSERT
, sodass ich dies nicht empfehlen würde zu tun.
Wenn Sie haben einen Hostnamen haben, können Sie einen SQL - Agent - Auftrag verwenden , um in regelmäßigen Abständen eine separate Tabelle mit dem aktuellen Leases von Ihrer lokalen DHCP - Server oder DNS - Zonendatei, als Out-of-Band - Prozess bevölkert , und LEFT JOIN
der in zukünftige Abfragen (oder in einen Skalar einschließen FUNCTION
, um einen Wert für eine Standardeinschränkung für den Zeitpunkt bereitzustellen).
Auch hier sollten Sie beachten, dass IP-Adressen und Hostnamen unzuverlässig sind (z. B. aufgrund von NAT), wenn die Anwendung eine öffentlich zugängliche Komponente enthält. Auch wenn es nicht öffentlich zugänglich ist, enthalten die meisten IP- / Hostnamen-Maps eine bestimmte zeitbasierte Komponente, die Sie möglicherweise berücksichtigen müssen.
Bevor Sie Ihren Anmeldetrigger implementieren, kann es sich lohnen, die dedizierte Administratorverbindung Ihres Servers zu aktivieren. Wenn der Anmeldeauslöser in irgendeiner Weise unterbrochen wird, kann er verhindern, dass sich alle Benutzer anmelden (einschließlich der Sysadmin-Konten):
USE master
GO
-- you may want to do this, so you have a back-out if the login trigger breaks login
EXEC sp_configure 'remote admin connections', 1
GO
RECONFIGURE
GO
Wenn Sie gesperrt werden, kann der DAC verwendet werden, um den Anmeldetrigger zu löschen oder zu deaktivieren:
C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO