Когда вы говорите «без использования триггеров», подразумеваете ли вы какие-либо триггеры или просто построчные триггеры в таблицах?
Я спрашиваю, потому что вы, возможно , сможете получить то, что вы хотите, при разумном использовании этой CONTEXT_INFO()функции, но вам нужно убедиться, что он SET CONTEXT_INFOбыл вызван правильно, прежде чем ваши операции будут выполнены.
Одним из мест, где это можно сделать, может быть триггер входа на уровне сервера (т. Е. Не триггер уровня базы данных / объекта), например:
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
Затем вы можете добавить ограничение по умолчанию к вашей таблице, чтобы сохранить контекст (для скорости вставки):
ALTER TABLE cdc.schema_table_CT
ADD ContextInfo varbinary(128) NULL DEFAULT(CONTEXT_INFO())
Как только вы это сделаете, вы можете запросить этот ContextInfoстолбец с небольшим количеством фрагментов:
SELECT *
,spid = CONVERT(INT, SUBSTRING(ContextInfo, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(ContextInfo, 5, 64))
FROM cdc.schema_table_CT
Технически, вы можете сделать это SUBSTRINGи CONVERTпрочее как часть вашего ограничения по умолчанию и просто сохранить там IP-адрес клиента, но может быть быстрее сохранить весь контекст там (как это делается для каждого INSERT) и извлекать значения только в SELECTкогда они вам нужны.
Я мог бы быть склонен обернуть все мои вызовы SUBSTRINGи CONVERTвызовы в одну строковую встроенную табличную функцию, что я и сделал бы, CROSS APPLYкогда это необходимо. Это сохраняет логику распаковки в одном месте:
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
Обратите внимание, что CONTEXT_INFOэто всего 128 байт VARBINARY. Если вам нужно больше данных, чем умещается в 128 байтов, я бы создал таблицу для хранения всех этих данных, вставил в качестве строки для этого «сеанса» в таблицу в триггере входа в систему и установил CONTEXT_INFOзначение суррогатного ключа этой таблицы
Следует также отметить, что, поскольку это только ограничение по умолчанию, для пользователя с соответствующими привилегиями тривиально перезаписать эти данные контекста в таблице покоя. Конечно, то же самое верно и для всех других столбцов в таблицах в стиле аудита.
Было бы неплохо, если бы это был сохраняемый вычисляемый столбец, а не значение по умолчанию, но эта CONTEXT_INFO()функция не является детерминированной, поэтому она не нужна (вы могли бы использовать некоторые FUNCTIONхитрости вокруг a VIEW, но я бы не стал ).
Это также тривиально для пользователя с достаточным доступом, чтобы вызвать SET CONTEXT_INFOсебя и испортить свой день (например, с поддельными значениями или специально созданным сохраненным введением), поэтому относитесь к содержимому с подозрением и осторожностью, кодируйте его перед отображением и обрабатывайте исключения Что ж.
Что касается имени хоста, я думаю, что ClientHostэлемент EVENTDATA()дает вам IP-адрес (или <local machine>индикатор). Хотя технически вы могли бы использовать CLR для обратного поиска DNS по имени хоста, они, как правило, слишком медленны для каждого INSERT, поэтому я бы рекомендовал этого не делать.
Если вам нужно иметь имя хоста, вы можете использовать задание агента SQL для периодического заполнения отдельной таблицы текущими арендными данными с вашего локального сервера DHCP или файла зоны DNS, как внеполосного процесса, и LEFT JOINдля этого в будущие запросы (или переход в скаляр FUNCTIONдля предоставления значения ограничения по умолчанию на определенный момент времени).
Опять же, вы должны отметить, что, если приложение имеет какой-либо общедоступный компонент, IP-адреса и имена хостов ненадежны (например, из-за NAT). Даже если он не является общедоступным, для большинства карт IP / имен хостов существует определенный компонент, основанный на времени, который вам может потребоваться учитывать.
Наконец, перед реализацией триггера входа в систему, возможно, стоит включить выделенное административное соединение вашего сервера. Если триггер входа в систему нарушается каким-либо образом, это может предотвратить вход всех пользователей (включая учетные записи sysadmin):
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
Если вы заблокированы, ЦАП может быть использован для сброса или отключения триггера входа в систему:
C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO