Когда вы говорите «без использования триггеров», подразумеваете ли вы какие-либо триггеры или просто построчные триггеры в таблицах?
Я спрашиваю, потому что вы, возможно , сможете получить то, что вы хотите, при разумном использовании этой 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